Dojos für Entwickler 2. Stefan Lieser
Чтение книги онлайн.
Читать онлайн книгу Dojos für Entwickler 2 - Stefan Lieser страница 3
sut.Result += x => result = x;
Der Inhalt dieses Feldes result wird dann in den einzelnen Tests jeweils nach Aufruf der Process-Methode durch ein Assert überprüft.
So weit zur Mechanik der Tests. Was mich an den Tests etwas stört, ist die Tatsache, dass auch die Kursiv-Eigenschaft in den TextElement-Objekten überprüft wird. Diese Eigenschaft zu prüfen ist sinnvoll, um sicherzustellen, dass die Funktionseinheit tatsächlich nur die Fettschrift aktiviert. Dennoch stellt sich die Frage, was kursive Schrift mit fetter Schrift zu tun haben mag. Das ist für mich ein Hinweis darauf, dass die Datenstruktur möglicherweise nicht optimal gewählt ist, denn so kann es zu Missverständnissen kommen. Eine alternative Implementation könnte die beiden booleschen Eigenschaften Fett und Kursiv zu einer einzigen zusammenfassen. Damit weiterhin alle vier möglichen Kombinationen von zwei booleschen Eigenschaften abgebildet werden, könnte ein enum-Typ verwendet werden, der die vier Kombinationen explizit enthält:
T Normal,
T Fett,
T Kursiv,
T FettUndKursiv.
So wäre klar, welche Bedeutung die Eigenschaft hat. Allerdings ist es nun etwas schwieriger, den vorhandenen Wert der Eigenschaft so zu ändern, dass kursiv ergänzt wird. Denn aus Normal müsste dann Kursiv werden, während aus Fett dann FettUndKursiv werden müsste. Ob die Lösung so besser aussieht, wollte ich wissen und habe es ausprobiert. Dabei zeigte sich, dass der Umgang mit einem enum-Typ die Lösung deutlich aufwendiger machen würde, daher habe ich den enum-Typ verworfen. Eine andere Idee wäre, den Schriftstil als zusammengesetzten Typ wie in Listing 3 zu definieren.
Listing 3
Eine Klasse für den Schriftstil.
public class SchriftStil { public bool Fett { get; set; } public bool Kursiv { get; set; } }
Doch auch das würde die Sache nicht einfacher machen, allein schon weil in den Tests der Vergleich zweier Objekte dieses zusammengesetzten Typs nicht mehr einfach so funktionieren würde. Am Ende entschied ich mich daher, es bei den beiden booleschen Eigenschaften Fett und Kursiv zu belassen.
Implementation
Die Implementation ging zunächst leicht von der Hand. Text, der in Fettschrift ausgegeben werden soll, muss in Markdown in doppelte Sternchen eingefasst werden. Folglich suche ich in den eingehenden Text-Element-Objekten nach doppelten Sternchen. Bei jedem Treffer wird ein Flag, welches festhält, ob gerade Fettschrift ausgegeben werden soll, umgeschaltet. Ferner wird der bis dahin eingelesene String in ein neues TextElement-Objekt verpackt und als Output der Funktionseinheit ausgegeben.
Die Herausforderung bestand darin, auch solche Fälle korrekt zu behandeln, die nicht direkt auf der Hand liegen. So kann es beispielsweise sein, dass das öffnende und das schließende Doppelsternchen in zwei unterschiedlichen TextElement-Objekten liegen. Um auch damit korrekt umzugehen, musste ich das Flag aus der Methode in die Klasse verschieben, damit der Zustand TextElement übergreifend gehalten wird. Ohne automatisierte Tests wäre ich hier aufgeschmissen gewesen. Es passierte nämlich ab und zu, dass ein neuer Spezialfall funktionierte, dafür aber ein anderes Szenario nicht mehr korrekt lief. Durch die Tests habe ich das jeweils schnell erkennen und beheben können. Listing 4 zeigt meine Implementation.
Listing 4
Fette Markierungen erkennen.
public class Extrahiere_Fett { private bool inFett; public event Action<IEnumerable< TextElement>> Result; public void Process(IEnumerable< TextElement> textElements) { Result(ProcessElements(textElements)); } private IEnumerable<TextElement> ProcessElements(IEnumerable< TextElement> textElements) { foreach (var textElement in textElements) { foreach (var element in ProcessElement( textElement)) { yield return element; } } } private IEnumerable<TextElement> ProcessElement(TextElement textElement) { const string fettTag = "**"; var result = new TextElement {Fett = inFett}; var input = textElement.Text; while (input.Length > 0) { if (input.Contains(fettTag)) { result.Text = TextBisZumTag( input, fettTag); if (result.Text.Length > 0) { yield return result; } inFett = !inFett; result = new TextElement {Fett = inFett}; input = TextNachDemTag(input, fettTag); } else { result.Text = input; input = ""; yield return result; } } } private static string TextBisZumTag(string input, string tag) { return input.Substring(0, input.IndexOf(tag)); } private static string TextNachDemTag( string input, string tag) { return input.Remove(0, input.IndexOf(tag) + tag.Length); } }
Die Methode ProcessElement ist zwar etwas lang geraten, doch ich habe keine elegantere Variante finden können. Für meinen Geschmack ist die Methode gerade so an der Grenze der Verständlichkeit. Sollte hier einmal eine Erweiterung anstehen, müsste ich die Methode vermutlich vorher refaktorisieren.
Die Umsetzung für das Erkennen von kursiven Texten sieht fast genauso aus. Allerdings galt es dabei eine Besonderheit zu berücksichtigen: Kursive Text-ElementObjekte können vorher bereits auf fett gesetzt worden sein. Diese Eigenschaft, also fett gesetzter Text, muss beim Extrahieren der kursiven Texte erhalten bleiben. Umgekehrt gilt dies nicht, da die Erkennung der fett gesetzten Texte ja in jedem Fall vor den kursiven Texten stattfindet. Dies ergibt sich zwingend daraus, dass zuerst die Doppelsternchen aus dem Text entfernt werden müssen, bevor auf einzelne Sternchen geprüft wird. Die Reihenfolge der beiden Funktionseinheiten Extrahiere_Fett und Extrahiere_Kursiv ist im Flow also nicht beliebig. Beim Extrahieren der kursiven Texte wird die Fett-Eigenschaft aus den Eingangsdaten übernommen. Wird ein Text-Element in mehrere aufgeteilt, müssen sie alle die Fett-Eigenschaft aus den Eingangsdaten übernehmen.
Platine
Der nächste Schritt bestand darin, die drei Funktionseinheiten zu einem Fluss zusammenzustecken. Diesmal habe ich dazu nicht das Tooling aus dem ebclang-Projekt verwendet [3], sondern die Platine „zu Fuß“ in C# implementiert. Ich wollte auf diese Weise einmal überprüfen, ob mir etwas fehlt, wenn ich nicht per Tooling eine Visualisierung des Flows erhalten kann. Listing 5 zeigt die Platine.
Listing 5
Teile auf der Platine integrieren.
public class Zerlege_MarkDown_Text { private Action<string> process; public Zerlege_MarkDown_Text() { var verpacke_in_Text = new Verpacke_in_TextElement(); var extrahiere_fett = new Extrahiere_Fett(); var extrahiere_kursiv = new Extrahiere_Kursiv(); process += verpacke_in_Text.Process; verpacke_in_Text.Result += extrahiere_fett.Process; extrahiere_fett.Result += extrahiere_kursiv.Process; extrahiere_kursiv.Result += textElements => Result(textElements); } public void Process(string input) { process(input); } public event Action<IEnumerable< TextElement>> Result; }
Auch zur Platine habe ich Tests geschrieben. Ich wollte auf diese Weise sicherstellen, dass auch Kombinationen aus fett und kursiv gesetzten Texten möglich sind. Das war nämlich bei meiner