Читать книгу: «Dojos für Entwickler 2»

Шрифт:

Einleitung


Auch in Teil 2 der Dojos für Entwickler gewinnt der, der auch übt. Was kann man gewinnen? Die Antwort ist kurz und bündig: Wissen. Stefan Lieser hat weitere 15 Aufgaben und ihre Lösungen formuliert. Stefan ist freiberuflicher Trainer und Berater und Fan von intelligenten Entwicklungsmethoden, die für Qualität der resultierenden Software sorgen. Er denkt sich die Aufgaben aus und gibt dann auch seine Lösung zum Besten. Er wird auch mitteilen, wie lange er gebraucht und wie viele Tests er geschrieben hat.

Alle Originalquellcodes in diesem devbook finden Sie zum Download unter http://www.dotnet-developer-conference.de/downloads/DojosII.zip

Aber bevor es los geht noch schnell 3 Fragen an Stefan:


Stefan, wie bist du zum Programmieren gekommen?

Mein Vater hat einmal einen programmierbaren Taschenrechner mit nach Hause gebracht. Der hat mich sehr fasziniert. Als Ferienjob habe ich dann Datenerfassung auf einer DEC PDP-11 gemacht und dabei "nebenbei" FORTRAN gelernt. Übrigens im selben Büro mit meinem Vater. Das hat mir tiefe Einblicke in sein Berufsleben gegeben. So ging es dann ständig weiter. In der Schule Informatikunterricht, dann Informatik studiert. Und schon vor Studiumsbeginn die ersten Programmierjobs.

Mich hat C# als Sprache fasziniert. Ich fand sie von Anfang an in manchen Details eleganter als Java. Ich beschäftige mich neben .NET allerdings auch mit anderen Umgebungen.

Wie lernt man am schnellsten zu programmieren?

Auch für die Softwareentwicklung gilt, dass man es erst mit 10.000 Stunden "deliberate practice" zur Meisterschaft bringt. Insofern würde ich sagen, am schnellsten geht es, wenn man täglich übt. Damit meine ich nicht, täglich seinen Job als Programmierer zu machen, sondern wirklich täglich zu üben. Der Unterschied besteht für mich darin, dass ich beim Üben Fehler zulassen kann. Als Trompeter vergleiche ich das gerne mit dem Spielen eines Musikinstruments. Immer nur Auftreten geht nicht. Ich muss vor allem Üben. Und das klingt nicht immer schön ;-)

Welche Tipps zum schnellen und effektiven Lernen kannst du den Lesern dieses DevBooks geben?

Zum Lernen ist Regelmäßigkeit und Reflexion erforderlich. Mit den Übungen des DevBooks kann man sich einige Zeit beschäftigen. Jede Woche eine Aufgabe lösen wäre z.B. ein Weg. Sinnvollerweise sollte die im Buch gezeigte Lösung erst nach der eigenen Lösung angesehen werden, um Vergleiche ziehen zu können. Dabei geht es nicht um richtig oder falsch sondern um den Erkenntnisgewinn.

Worte an den Leser…

Ich wünsche den Lesern viel Spaß mit den Übungen. Eine Anregung könnte noch sein, die Übungen und vor allem die Lösungen gemeinsam mit Kollegen zu diskutieren. Üben in der Gruppe kann viel Spaß machen. Auch beim Diskutieren der individuellen Lösungen kann man einiges lernen. Wer keine eigene Gruppe gründen möchte, kann auch zur CCD School kommen (http://ccd-school.de).

Aufgabe 1


MarkDown-Control selbst gebaut
Einfach, einfacher, MarkDown

Es muss nicht immer HTML oder RTF sein. Wer Texte auszeichnen will, kann auch MarkDown verwenden. Dieses Format ist besonders einfach und aus vielen Wikis bekannt. Also lautet die Übung: Schreiben Sie ein Control, das MarkDown-Texte darstellen kann.

Ein Text erhält Auszeichnungen, damit er auf bestimmte Art und Weise dargestellt wird. Das ist nichts Neues. Anfang der 90er-Jahre wurden beispielsweise HTML und RTF entwickelt, mit dem Ziel, Texten eine Struktur geben zu können. Mit beiden Formaten ist es möglich, einen Textabschnitt etwa kursiv oder fett auszugeben:

HTML: <em>kursiv</em> <strong>fett</strong> RTF: {\i kursiv} {\b fett}

Beide Formate haben jedoch gemeinsam, dass die Syntax für die Formatierung recht aufwendig ist. So sind die gezeigten HTML- und RTF-Beispiele auch nicht vollständig, weil sie von weiteren Elementen umschlossen sein müssen, um gültiges HTML beziehungsweise RTF darzustellen.

Die Vereinfachung von Formatierungen ist das Hauptziel von MarkDown [1][2]. Mit dieser bewusst total simpel gehaltenen Formatierung soll es möglich sein, das Schreiben von Texten deutlich zu vereinfachen, ohne dabei auf Formatierungen gänzlich verzichten zu müssen. So wird bei MarkDown die Formatierung einfach in den Text eingestreut und muss nicht in einen komplizierten syntaktischen Rahmen eingebettet werden. Die Beispiele für kursive und fette Hervor­hebungen sehen in MarkDown wie folgt aus:

*kursiv* **fett** ***fett kursiv***

Sie können nicht nur einzelne Textabschnitte kursiv oder fett ausgeben; MarkDown bietet auch Möglichkeiten, die Struktur des Textes zu beschreiben. Eine grundsätzliche Idee von MarkDown ist es, aus einem MarkDown-Text ein HTML-Dokument zu erzeugen. Daher lehnen sich die Formatierungen an das an, was mit HTML möglich ist. Überschriften können beispielsweise folgendermaßen erzeugt werden:

# Überschrift auf Ebene 1 ## Überschrift auf Ebene 2 ### Überschrift auf Ebene 3

Möglicherweise wird der eine oder andere Leser feststellen, dass er solche oder ähnliche Auszeichnungen schon verwendet hat: Wikis verwenden häufig MarkDown oder daran angelehnte Formatierungen.

Auch Aufzählungen in Form von Spiegelstrichlisten oder nummerierte Listen sind möglich:

* Erster Punkt * Zweiter Punkt * Dritter Punkt 1. Schritt eins 1. Schritt zwei 1. Schritt drei

Solche Formatierungen werden beim Konvertieren nach HTML in <ul>- oder <ol>-Listen Übersetzt.

Doch nun zur Aufgabe: Erstellen Sie ein User-Control, das einen Text im MarkDown-Format akzeptiert und als Label anzeigt. Allerdings sollen dabei MarkDown-Formatierungen berücksichtigt werden, sodass beispielsweise ein Wort in Sternchen kursiv ausgegeben wird. Wie viele der MarkDown-Elemente Sie umsetzen, ist zunächst nachrangig. Beginnen Sie ganz einfach, statt gleich eine perfekte 100-prozentige Lösung anzustreben.

Die Herausforderung liegt in zwei Bereichen: zum einen im Parsen des Textes, zum anderen in der Visualisierung. Innerhalb des Textes müssen die Auszeichnungen erkannt werden, um daraus Formatierungen abzuleiten. Im angezeigten Text dürfen die Auszeichnungen nicht mehr erscheinen. Stattdessen muss der Text entsprechend formatiert sein. Wie man das in einem User-Control erreicht, ist die zweite Herausforderung. Ob Sie dabei auf Windows Forms, Silverlight oder WPF setzen, bleibt natürlich Ihnen überlassen. Allerdings würde ich persönlich die Übungszeit nutzen, mich mit XAML auseinanderzusetzen, da in Windows Forms sicher nicht die Zukunft liegt. Wie immer wünsche ich Ihnen viel Spaß beim Üben. Machen Sie viele Fehler und lernen Sie daraus! [ml]

[1] http://de.wikipedia.org/wiki/Markdown [2] http://daringfireball.net/projects/markdown/

Lösung 1


Ein UserControl für Markdown-Textauszeichnungen
Show up Mark down

Texte mit Markdown-Auszeichnungen in einem Control darzustellen, das war die Aufgabe. Natürlich ist bei der Lösung wieder alles im Fluss. Und weil das im echten Leben auch so ist, muss wieder einmal eine Lösung her, die evolvierbar ist. So ist sichergestellt, dass spätere Anforderungen einfach umsetzbar sind.

Mit Markdown steht eine Textauszeichnung zur Verfügung, die einfach anzuwenden ist [1] [2]. Das liegt daran, dass auf Schnickschnack verzichtet wurde. Es handelt sich nicht um eine weitere Variante des Themas „Viele Klammern werden das Problem schon lösen“. Statt also wie bei HTML auf spitze Klammern oder bei RTF auf geschweifte Klammern zu setzen, verwendet Markdown Zeichen wie etwa das Sternchen, um damit Passagen eines Textes auszuzeichnen. Das macht die Sache beim Schreiben eines Textes deutlich einfacher, weil weniger Formalismen erforderlich sind. Allerdings wird, damit einhergehend, das Parsen eines Textes nicht unbedingt einfacher. So wird in Markdown beispielsweise ein einzelnes Sternchen sowohl zur Markierung von kursivem Text verwendet als auch zur Kennzeichnung von Aufzählungen. Das zu erkennen ist die Herausforderung beim Parsen eines Markdown-Textes.

Kleine Schritte

Im ersten Schritt habe ich die Aufgabe wieder klein gehalten. Mir ist es wichtig, jeweils einen Teil der insgesamt gewünschten Funktionalität komplett fertig zu kriegen. Was nützt es mir, wenn ich zwar Markdown-Texte nach allen Regeln der Kunst zerlegen kann, davon jedoch noch nichts visualisiert wird? Folglich habe ich mich erst einmal darauf beschränkt, nur kursiv und fett zu erkennen und in einem UserControl zu visualisieren. Text, der in einfache Sternchen eingefasst ist, soll *kursiv* dargestellt werden, Text in doppelten Sternchen **fett**.

Meine ersten Überlegungen drehten sich um die Frage, wie ich einen Text nach der Erkennung der Markdown-Auszeichnungen repräsentieren möchte. Wie sieht eine geeignete Datenstruktur aus? Um das Einfachste zu tun, habe ich mir überlegt, dass der Text nach der Zerlegung aus TextElementen bestehen könnte. Enthält ein Text keinerlei Markdown-Auszeichnungen, wäre der gesamte Text ein einzelnes TextElement.

Enthält der Text jedoch ein Wort in Fettschrift, würde ich diesen Text in drei TextElemente zerlegen:

 ein TextElement in normaler Schrift,

 ein TextElement in fetter Schrift,

 T wieder ein TextElement in normaler Schrift.

Der Text „Ein **fettes** Wort“ würde also in die drei TextElemente „Ein “, „fettes“, „Wort“ zerlegt. Beachten Sie die Leerzeichen: nach „Ein“ und vor „Wort“ ist jeweils ein Leerzeichen.

Durch diese simple Datenstruktur kann die Zerlegung eines Markdown-Textes schrittweise erfolgen: Zuerst werden fett hervorgehobene TextElemente extrahiert. Anschließend werden aus den bereits gebildeten TextElementen weitere gebildet, um so kursive Texte darzustellen. Dazu muss die TextElement-Datenstruktur neben dem Text die Information mitführen, ob der Text fett und/oder kursiv dargestellt werden soll. Listing 1 zeigt die entsprechende Datenstruktur.

Listing 1
Grundlegende Datenstruktur.

public class TextElement { public string Text { get; set; } public bool Fett { get; set; } public bool Kursiv { get; set; } }

Aus diesen überlegungen hat sich der in Abbildung 1 gezeigte Flow ergeben.


[Abb. 1]

Fett und kursiv gesetzte Textteile extrahieren.

Im ersten Schritt wird der eingehende String in ein TextElement-Objekt umgewandelt. Weil anschließend jeweils eine Aufzählung von TextElement-Objekten bearbeitet wird, liefert die Funktionseinheit Verpacke_in_TextElement gleich eine Aufzählung, die allerdings immer nur ein einzelnes Objekt enthält.

Anschließend werden Texte extrahiert, die in Fettschrift ausgezeichnet sind. Es wäre denkbar, dass diese Funktionseinheit später auf mehr als einem TextElement arbeiten muss. Das könnte zum Beispiel der Fall sein, wenn vor der Fettschrift die Überschriften extrahiert werden. Somit ist es sinnvoll, als Eingang der Funktionseinheit gleich eine Aufzählung von TextElement-Objekten vorzusehen.

Nach dem Extrahieren von Fettschrift folgt das Extrahieren der kursiv ausgezeichneten Texte. Wieder wird eine Aufzählung von TextElement-Objekten behandelt. Werden im eingehenden Datenstrom kursiv ausgezeichnete Texte gefunden, dann wird ein TextElement möglicherweise in mehrere TextElement-Objekte zerlegt. Die Anzahl der ausgehenden TextElement-Objekte kann also höher sein als auf der Eingangsseite.

Nun mag dem einen oder anderen Leser durch den Kopf gehen, dass das gewählte Verfahren eventuell aus Laufzeitgründen ineffizient ist. Ja, das kann sein. Aber an dieser Stelle kümmert mich das nicht. Die Lösung ist dazu gedacht, Markdown-Texte in einem Label-Control zu visualisieren. Das heißt, es werden typischerweise wenige, noch dazu kurze Texte bearbeitet. Für die Verarbeitung von Massendaten ist dieser Ansatz möglicherweise nicht optimal.

Aber das ist gerade nicht die Aufgabenstellung. Es genügt hier also, das Einfachstmögliche zu tun. Der gewählte Weg sieht mir bis hierhin in jedem Fall gut evolvierbar aus. Weitere Markdown-Elemente zu erkennen bedeutet nur, eine entsprechende Funktionseinheit zu implementieren, die für das Zerlegen zuständig ist. Diese kann dann leicht in den bestehenden Flow eingehängt werden. Spannend bleibt dabei die Frage, ob die Datenstruktur TextElement dann weiterhin ausreicht. Doch bevor ich das herausfinde, folgt nun die Implementation für das Erkennen von Fettschrift. Listing 2 zeigt einige Tests dazu.

Listing 2
Erst mal testen: Fett-Formatierungen erkennen.

[TestFixture] public class Extrahiere_Fett_Tests { private Extrahiere_Fett sut; private IEnumerable<TextElement> result; [SetUp] public void Setup() { sut = new Extrahiere_Fett(); sut.Result += x => result = x; } [Test] public void Normaler_Text_ ohne_Fettschrift() { sut.Process(new[] {new TextElement{ Text = "x"}}); Assert.That(result.Select( x => x.Text), Is.EqualTo(new[]{"x"})); Assert.That(result.Select( x => x.Fett), Is.EqualTo(new[]{false})); Assert.That(result.Select( x => x.Kursiv), Is.EqualTo(new[]{false})); } [Test] public void Fettschrift_am_ Anfang_eines_TextElements() { sut.Process(new[] { new TextElement { Text = "**fett** x" } }); Assert.That(result.Select( x => x.Text).ToArray(), Is.EqualTo(new[] { "fett", " x" })); Assert.That(result.Select( x => x.Fett).ToArray(), Is.EqualTo(new[] { true, false })); Assert.That(result.Select( x => x.Kursiv).ToArray(), Is.EqualTo(new[] { false, false })); } [Test] public void Mehrfach_ Fettschrift() { sut.Process(new[] { new TextElement { Text = "**f1****f2** y**f3**" } }); Assert.That(result.Select( x => x.Text).ToArray(), Is.EqualTo(new[] { "f1", "f2", "y", "f3" })); Assert.That(result.Select( x => x.Fett).ToArray(), Is.EqualTo(new[] { true, true, false, true })); Assert.That(result.Select( x => x.Kursiv).ToArray(), Is.EqualTo(new[] { false, false, false, false })); } [...] }

Die Tests spielen diverse Szenarien durch, in denen fett ausgezeichneter Text auftreten kann. Da die Funktionseinheit als Event-Based Component (EBC) realisiert ist, hat sie für die Eingangsdaten eine Methode Process und für die Ausgangsdaten einen Event Result. Mithin genügt es für das Testen nicht, eine Methode aufzurufen, vielmehr muss das Ergebnis des Events ausgewertet werden. Daher binde ich im Setup der Testklasse an den Event die Lambda-Expression, welche das Ergebnis im Feld result der Testklasse ablegt:

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.

1 435,42 ₽
Возрастное ограничение:
0+
Объем:
356 стр. 95 иллюстраций
ISBN:
9783844259261
Издатель:
Правообладатель:
Bookwire
Формат скачивания:
epub, fb2, fb3, ios.epub, mobi, pdf, txt, zip

С этой книгой читают