Dojos für Entwickler. Stefan Lieser

Чтение книги онлайн.

Читать онлайн книгу Dojos für Entwickler - Stefan Lieser страница 6

Dojos für Entwickler - Stefan Lieser

Скачать книгу

eine Lösung des Problems. Aber versuchen Sie sich zunächst selbst an der Aufgabe. [ml]

LÖSUNG INotifyPropertyChanged-Logik automatisiert testen

      Kettenreaktion

      Das automatisierte Testen der INotifyPropertyChanged-Logik ist nicht schwer. Man nehme einen Test, verallgemeinere ihn, streue eine Prise Reflection darüber, fertig. Doch wie zerlegt man die Aufgabenstellung so in Funktionseinheiten, dass diese jeweils genau eine definierte Verantwortlichkeit haben? Die Antwort: Suche den Flow!

      Wie man die INotifyPropertyChanged-Logik automatisiert testen kann, habe ich in der Aufgabenstellung zu dieser Übung bereits gezeigt [1]. Doch wie verallgemeinert man nun diesen Test so, dass er für alle Eigenschaften einer Klasse automatisiert ausgeführt wird?

      Im Kern basiert die Lösung auf folgender Idee: Suche per Reflection alle Properties einer Klasse und führe den Test für die gefundenen Properties aus. Klingt einfach, ist es auch. Aber halt: Bitte greifen Sie nicht sofort zur Konsole! Auch bei vermeintlich unkomplizierten Aufgabenstellungen lohnt es sich, das Problem so zu zerlegen, dass kleine, überschaubare Funktionseinheiten mit einer klar abgegrenzten Verantwortlichkeit entstehen.

      Suche den Flow!

      Ich möchte versuchen, die Aufgabenstellung mit einem Flow zu lösen. Doch dazu sollte ich ein klein wenig ausholen und zunächst erläutern, was ein Flow ist und wo seine Vorteile liegen.

      Vereinfacht gesagt ist ein Flow eine Aneinanderreihung von Funktionen. Ein Argument geht in die erste Funktion hinein, diese berechnet damit etwas und liefert ein Ergebnis zurück. Dieses Ergebnis geht in die nächste Funktion, auch diese berechnet damit wieder etwas und liefert ihr Ergebnis an die nächste Funktion. Auf diesem Weg wird ein Eingangswert nach und nach zu einem Ergebnis transformiert, siehe Listing 1.

      Listing 1: Ein einfacher Flow.

       var input = "input";

       var x1 = A(input);

       var x2 = B(x1);

       var result = C(x2);

      Die einzelnen Funktionen innerhalb eines Flows, die sogenannten Flowstages, sind zustandslos, das heißt, sie erledigen ihre Aufgabe ausschließlich mit den Daten aus ihren Argumenten. Das hat den Vorteil, dass mehrere Flows asynchron ausgeführt werden können, ohne dass dabei die Zugriffe auf den Zustand synchronisiert werden müssten. Ferner lassen sich zustandslose Funktionen sehr schön automatisiert testen, weil das Ergebnis eben nur von den Eingangsparametern abhängt.

      Einer nach dem anderen

      Ein Detail ist bei der Realisierung von Flows ganz wichtig: Weitergereicht werden sollten nach Möglichkeit jeweils Daten vom Typ IEnumerable<T>. Dadurch besteht nämlich die Möglichkeit, auf diesen Daten mit LINQ zu operieren. Ferner können die einzelnen Flowstages dann beliebig große Datenmengen verarbeiten, da bei Verwendung von IEnumerable<T> nicht alle Daten vollständig im Speicher existieren müssen, sondern Element für Element bereitgestellt werden können. Im Idealfall fließt also zwischen den einzelnen Flowstages immer nur ein einzelnes Element. Es wird nicht etwa das gesamte Ergebnis der ersten Stage berechnet und dann vollständig weitergeleitet.

      Im Beispiel von Listing 2 führt die Verwendung von yield return dazu, dass der Compiler einen Enumerator erzeugt. Dieser Enumerator liefert nicht sofort die gesamte Aufzählung, sondern stellt auf Anfrage Wert für Wert bereit. Bei Ausführung der Methode Flow() werden also zunächst nur die einzelnen Aufzählungen und Funktionen miteinander verbunden. Erst wenn das erste Element aus dem Ergebnis entnommen werden soll, beginnen die Enumerato-ren, Werte zu liefern. Der Flow kommt also erst dann in Gang, wenn jemand hinten das erste Element „herauszieht“.

      Listing 2: Rückgabedaten vom Typ IEnumerable nutzen.

       public void Flow() {

       var input = Input();

       var x1 = A(input);

       var x2 = B(x1);

       var result = C(x2);

       foreach(var value in result) {

       ...

       }

       }

       public IEnumerable<string> Input() {

       yield return "Äpfel";

       yield return "Birnen";

       yield return "Pflaumen";

       }

       public IEnumerable<string> A(IEnumerable<string> input) {

       foreach (var value in input) {

       yield return string.Format("({0})", value);

       }

       }

       public IEnumerable<string> B(IEnumerable<string> input) {

       foreach (var value in input) {

       yield return string.Format("[{0}]", value);

       }

       }

       public IEnumerable<string> C(IEnumerable<string> input) {

       foreach (var value in input) {

       yield return string.Format("-{0}-", value);

       }

       }

      Als erste ist die Funktion C an der Reihe. Sie entnimmt aus der ihr übergebenen Aufzählung x2 das erste Element. Dadurch kommt B ins Spiel und entnimmt ihrerseits der Aufzählung x1 den ersten Wert. Dies setzt sich fort, bis die Methode Input den ersten Wert liefern muss. Im Flow werden die einzelnen Werte sozusagen von hinten durch den Flow gezogen. Ein Flow bietet in Verbindung mit IEnumerable<T> und yield return die Möglichkeit, unendlich große Datenmengen zu verarbeiten, ohne dass eine einzelne Flowstage die Daten komplett im Speicher halten muss.

      Lesbarkeit durch Extension Methods

      Verwendet man bei der Implementierung der Flowstages Extension Methods, kann man die einzelnen Stages syntaktisch hintereinanderschreiben, sodass der Flow im Code deutlich in Erscheinung tritt. Dazu muss lediglich der erste Parameter der Funktion um das Schlüsselwort this ergänzt werden, siehe Listing 3. Natürlich müssen die Parameter und Return-Typen der Flowstages zueinander passen.

      Listing 3: Die Stages syntaktisch koppeln.

       public static IEnumerable<string> A(this IEnumerable<string> input) {

       foreach (var value in input) {

       yield

Скачать книгу