Ich würde sagen, die Regel ist, dass, wenn ein Lambda-Ausdruck ist so komplex, dass Sie das Bedürfnis verspüren, zu verspotten Bits davon, dass es wahrscheinlich zu komplex ist. Es sollte in kleinere Stücke zerlegt werden, die zusammen komponiert werden, oder vielleicht muss das Modell angepasst werden, um es für die Zusammensetzung zugänglicher zu machen.
Ich werde sagen, dass Andrey Chaschev's answer, die vorschlägt, eine Abhängigkeit zu parametrisieren, eine gute ist und wahrscheinlich in einigen Situationen anwendbar ist. Also, +1 dafür. Eine könnte diesen Prozess fortsetzen und bricht die Verarbeitung in kleinere Stücke nach unten, etwa so:
public List<Item> processFile(
String fileName,
Function<String, BufferedReader> toReader,
Function<BufferedReader, List<String>> toStringList,
Function<List<String>, List<Item>> toItemList)
{
List<String> lines = null;
try (BufferedReader br = toReader.apply(fileName)) {
lines = toStringList.apply(br);
} catch (IOException ioe) { /* ... */ }
return toItemList.apply(lines);
}
ein paar Beobachtungen zu diesem Thema, aber. Erstens funktioniert das nicht wie geschrieben, da die verschiedenen Lambdas pesky IOExceptions
werfen, die überprüft werden, und der Function
Typ wird nicht deklariert, um diese Ausnahme zu werfen. Die zweite ist, dass die Lambdas, die Sie zu dieser Funktion übergeben müssen, monströs sind. Obwohl dies nicht funktioniert (wegen geprüfter Ausnahmen) habe ich es geschrieben:
void processAnActualFile() {
List<Item> items = processFile(
"file.csv",
fname -> new BufferedReader(new FileReader(fname)),
// ERROR: uncaught IOException
br -> {
List<String> result = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
result.add(line);
}
return result;
}, // ERROR: uncaught IOException
stringList -> {
List<Item> result = new ArrayList<>();
for (String line : stringList) {
result.add(new Item(line));
}
return result;
});
}
Ugh! Ich glaube, ich habe neuen Code Geruch entdeckt:
Wenn Sie eine For-Schleife oder While-Schleife in einem Lambda schreiben müssen, tun Sie etwas falsch.
Ein paar Dinge sind hier los. Erstens besteht die E/A-Bibliothek tatsächlich aus verschiedenen Teilen der Implementierung (InputStream
, Reader
, BufferedReader
), die fest miteinander verbunden sind. Es ist wirklich nicht sinnvoll, sie auseinander zu brechen. In der Tat hat sich die Bibliothek so entwickelt, dass es einige Hilfsfunktionen (wie das NIO Files.readAllLines
) gibt, die eine Reihe von Beinarbeit für Sie erledigen.
Der wichtigere Punkt ist, dass das Entwerfen von Funktionen, die Aggregate (Listen) von Werten untereinander weitergeben und diese Funktionen zusammensetzen, wirklich der falsche Weg ist. Es führt jede Funktion dazu, eine Schleife darin schreiben zu müssen. Was wir wirklich machen wollen, ist das Schreiben von Funktionen, die jeweils mit einem einzigen Wert arbeiten, und dann lässt die neue Streams-Bibliothek in Java 8 die Aggregation für uns erledigen.
Die Schlüsselfunktion, um hier aus dem Code zu extrahieren, der durch den Kommentar "mach etwas mehr Magie" beschrieben wird, der List<String>
in List<Item>
konvertiert. Wir wollen die Berechnung extrahieren, die einString
in eine Item
, wie dies konvertiert:
class Item {
static Item fromString(String s) {
// do a little bit of magic
}
}
Sobald Sie diese haben, dann können Sie die Bäche und NIO-Bibliotheken lassen einen Haufen Arbeit für Sie tun:
public List<Item> processFile(String fileName) {
try (Stream<String> lines = Files.lines(Paths.get(fileName))) {
return lines.map(Item::fromString)
.collect(Collectors.toList());
} catch (IOException ioe) {
ioe.printStackTrace();
return Collections.emptyList();
}
}
(Beachten Sie, dass mehr als die Hälfte dieser kurzen Verfahren ist mit dem IOException
für den Umgang.)
Jetzt, wenn Sie etwas Unit Testing machen wollen, was Sie wirklich testen müssen, ist das ein bisschen Magie. So dass Sie es in einen anderen Stream-Pipeline wickeln, wie folgt aus:
void testItemCreation() {
List<Item> result =
Arrays.asList("first", "second", "third")
.stream()
.map(Item::fromString)
.collect(Collectors.toList());
// make assertions over result
}
(Eigentlich, auch dies nicht ganz richtig ist, Sie würden für Unit-Tests schreiben möchten eine einzige Zeile in einer einzigen Item
Umwandlung Aber.. vielleicht haben Sie einige Testdaten irgendwo, so dass Sie es auf eine Liste von Elementen auf diese Weise umwandeln würde, und dann globale Aussagen über das Verhältnis der erhaltenen Elemente in der Liste.)
wanderte ich habe ziemlich weit von Ihrer ursprünglichen Frage, wie man ein Lambda auseinander bricht. Bitte vergib mir, dass ich mich selbst verwöhnt habe.
Das Lambda im ursprünglichen Beispiel ist ziemlich unglücklich, da die Java-E/A-Bibliotheken ziemlich umständlich sind und es neue APIs in der NIO-Bibliothek gibt, die das Beispiel in einen Einzeiler verwandeln.
Die Lektion hier ist jedoch, dass statt Funktionen, die Aggregate verarbeiten komponieren Funktionen komponieren, die einzelne Werte verarbeiten, und lassen Ströme die Aggregation behandeln. Auf diese Weise können Sie testen, indem Sie die Stream-Pipelines auf unterschiedliche Weise zusammenstecken, anstatt durch Testen von Bits eines komplexen Lambda zu testen.
Ein Lambda kann einen Kontext wie alles andere laden (wenn Sie versuchen, darin zu stubben). aber bedenken Sie, dass ein Lambda ohne das Erfassen von Variablen höchstwahrscheinlich ein Singleton ist. – aepurniet