Follow my new blog

Posts mit dem Label Coding Dojo werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Coding Dojo werden angezeigt. Alle Posts anzeigen

Freitag, 1. November 2013

Mitmachen: Flow-Designs gesucht

Für Programmieraufgaben gibt es ja nicht nur eine Lösung. Aber wie bunt die Lösungswelt ist, ist andererseits auch nicht ganz klar. Deshalb würde ich mich freuen, wenn Sie mitmachen würden bei der Sammlung von Lösungen.

Zu einem kleinen Programmierproblem möchte ich Lösungen in Form einer kleinen Bilderausstellung veröffentlichen. Dann können wir darüber diskutieren. Allerdings geht es mir nicht um Quellcode, sondern um visuelle Lösungen.

imageAlso:

Ich suche Lösungen zum Problem “Ordered Jobs” in Form von Flow-Designs.

Datenflüsse sollen im Mittelpunkt stehen. Aber wer die mit weiteren Bildern garnieren möchte, ist dazu herzlich eingeladen.

Die Ausdrucksmittel des Flow-Designs habe ich in vielen Publikationen beschrieben und über die Jahre verfeinert. Leider bin ich noch nicht dazu gekommen, einen aktuellen Stand an einem Ort zusammenzufassen. Mea culpa. Aber es gibt einen Spickzettel, der größtenteils aktuell ist und die Notation knapp beschreibt. Alternativ beschreiben einige Artikel unter der Überschritz “OOP as if you meant it” nochmal in Kürze meine aktuelle Sicht zu Flow-Design.

Wer mitmachen will, schreibt am besten einen Kommentar zu diesem Artikel mit einem Link zu einem PDF mit seinem Entwurf. Oder es können Links zu yuml.me Grafiken sein; mit den Aktivitätsdiagrammen kann man Flow-Designs recht ordentlich beschreiben. Oder schicken Sie mir eine Email mit einem PDF.

Am Ende bastle ich dann eine Zusammenschau mit ein paar Anmerkungen zu dem, was mir auffällt. Und ich lege auch meine Vorstellung von einer Lösung vor. Dann gibt es eine “Ausstellungseröffnung” hier im Blog :-)

Zu gewinnen gibt es nichts – außer Erkenntnissen.

Wer macht mit? Einsendeschluss ist der 30.11.2013.

Ich bin gespannt auf die Vielfalt der Lösungsansätze.

Und hier nun die Aufgabe in epischer Länge. Sie ist auch im Fundus der Programmier-Katas der CCD School zu finden.

Class Kata Ordered Jobs by Clean Code Developer School

Montag, 27. Mai 2013

Futter für das Coding Dojo

Wie soll man eigentlich Profi in der Anwendung der Prinzipien und Praktiken von Clean Code Developer werden? Durch Übung :-) Und zwar zunächst einmal durch Übung an Übungen, d.h. nicht am Produktionscode des Tagesgeschäftes.

Woher aber solche Übungen nehmen? Es gibt einige Übungsvorschläge, die sog. Code Katas. Doch die sind meist sehr, sehr überschaubar. Es sind vor allem Übungen im Entwickeln kleiner Algorithmen. Das ist nicht schlecht – aber am Ende zu wenig. Damit kann man ein paar Prinzipien und auch die Methode TDD üben… Doch realistisch sind solche Übungen nicht. Sie bereiten insofern nur begrenzt auf das reale Leben vor.

Deshalb haben Stefan Lieser und ich nun angefangen, Übungen zu sammeln, die ein breiteres Spektrum abdecken. Wir setzen sie schon seit mehreren Jahren in unseren Trainings der Clean Code Developer Akademie ein. Und jetzt “lassen wir sie frei”. Ein kleines Geschenk an die Community.

image

Im “Coding Dojo” der Clean Code Developer School bauen wir ein Verzeichnis von Code Katas und anderen auf. Wir teilen dabei die Übungen in verschiedene Kategorien je nach Umfang. Am unteren Ende stehen “Function Katas”, bei denen die Aufgabe darin besteht, nur eine Funktion zu entwickeln, um einige Anforderungen zu erfüllen. Darüber liegen “Class Katas” und “Library Katas”. Und weiter geht es mit “Application Katas” und “Architecture Katas”. Sie stehen für umfangreichere Aufgaben, zu deren Lösungen Benutzerschnittstellen und Ressourcenzugriffe oder gar Verteilung gehören. Es sollte also für jeden Geschmack und jedes Zeitbudget etwas dabei sein.

Auf der Seite des “Coding Dojo” sind die Aufgaben in diesen Kategorien gelistet; die zugehörigen Dokumente befinden sich hingegen bei scribd.com, wie oben im Bild zu sehen. Wir nutzen gern die Cloud als Speicehrort ;-) Hier ein Beispiel:


Das Verzeichnis der Übungsaufgaben soll natürlich ständig wachsen. Wir haben noch einige in petto, wir werden weitere bekannte und beliebte Aufgaben im Web “ernten” und aufarbeiten. Aber wir freuen uns auch, wenn Sie uns Ideen für Übungen mitteilen. Von einem umfangreichen und vielfältigen Verzeichnis an Katas können die Communities aller Plattformen nur profitieren.

Und nun: Auf zum Üben! Auf in ihr persönliches Coding Dojo!

Nehmen Sie sich (am besten regelmäßig) allein oder mit Kollegen ein bisschen Zeit dafür. Oder besuchen Sie uns in der Clean Code Developer School. Dort zeigen wir Ihnen in Ruhe, was wir unter sauberer und nachhaltiger Softwareentwicklung verstehen.

image

Montag, 12. November 2012

Aspektvolles Programmieren

Neulich habe ich kritisiert, Lösungen für Code Katas seien oft schwer verständlich. Als Ursache dafür habe ich einen Mangel an herausgearbeiteten Aspekten identifiziert.

Mit Code Katas wird üblicherweise TDD geübt. Das hatte Dave Thomas zwar nicht speziell im Sinn, als er zu Code Katas ermunterte, doch es ist die de facto Praxis in Coding Dojos. Explizit hat Dave Thomas gesagt:

“Some involve programming, and can be coded in many different ways. Some are open ended, and involve thinking about the issues behind programming.” (meine Hervorhebung)

Dass über die Themen hinter der Programmierung nach-ge-dacht würde… Nein, tut mir leid, das habe ich in keinem Coding Dojo bisher erlebt, weder in einem offenen, noch in einem inhouse Dojo. Der Goldstandard ist vielmehr, dass schon Rechner und Beamer eingerichtet sind, kurz das Organisatorische besprochen wird, man sich flux über die Aufgabe austauscht – max. 15 Minuten – vor allem aber zügig das (Pair) Programming beginnt. Und damit endet dann ein eventueller “Nachdenkmodus” endgültig.

Klar, es gibt Diskussionen über die nächsten Schritte. Aber so richtig ins Nachdenken über “issues behind programming” kommt da niemand mehr. Ist ja auch nicht das ausgelobte Ziel. Das lautet vielmehr, die Lösung möglichst weit mit TDD voran zu bringen. [1]

Und so sehen dann die Lösungen aus. Man bekommt, was die Regeln vorgeben: grüne Tests. Das ist nämlich das einzige, was irgendwie “gemessen” wird. Darüber gibt es keinen Zweifel. Schon die Qualität der Tests ist nicht mehr leicht zu ermitteln. Sie ergibt sich eher implizit dadurch, ob der Fortschritt leicht oder schwierig ist. Denn sind die Tests schlecht geschnitten und ungünstig priorisiert, wird das Codieren zäh.

Aber wie denn anders?

Nun, es kommt erstmal aufs Ziel an. Was soll denn mit einer Code Kata erreicht werden? Wenn das ausschließlich lautet “TDD-Fingerfertigkeit entwickeln”, dann ist ja alles gut. Weitermachen mit Dojos wie bisher.

Nur finde ich das inzwischen erstens langweilig und zweitens am Hauptproblem vorbei. Langweilig, weil nach 5+ Jahren Coding Dojos auch mal was anderes dran sein dürfte. Oder findet das sonst niemand zum einschlafen, wenn erwachsene Männer womöglich Monat für Monat zusammenkommen, um diese Fingerfertigkeit zu entwickeln. Mit Verlaub: Das ist doch viel, viel simpler, als aus einer Karate Kata das Letzte herauszuholen.

Vor allem löst diese spezielle Variante automatisierter Tests nicht das Hauptproblem der Codeproduktion. Das ist mangelhafte Evolvierbarkeit von Code.

Nicht, dass es keine Teams gäbe, die nicht von automatisierten Tests profitieren könnten. Davon gibt es noch viele. Die sollen da auch möglichst schnell Fuß fassen. Nur ist deren Einführung erstens technisch kein Hexenwerk: NUnit runterladen, referenzieren, los geht´s. Und zweitens ist die konkrete Praxis automatisierter Tests relativ unbedeutend. Ob nur “irgendwie” test-first oder richtiggehendes TDD oder Tests nach der Implementation geschrieben… klar, die Ergebnisse sind unterschiedlich, doch entscheidend ist nicht dieser Unterschied durch die Stile, sondern der zur vorherigen Testlosigkeit.

Warum fällt es denn aber Teams auch heute noch schwer, überhaupt automatisierte Tests einzusetzen? Weil sie keine TDD-Fingerfertigkeit haben? Nein, weil sie keine Codebasis haben, die das (nachträgliche) Anbringen von Tests erlaubt. Und das kommt daher, dass sie nur wenig Vorstellung davon haben, wie eine saubere, testbare Codebasis überhaupt aussieht.

Damit sind wir beim blinden Fleck der TDD-Coding-Dojo-Praxis. Über dieses “issue behind programming” wird nicht nachgedacht. Das Refactoring im TDD-3-Schritt red-green-refactoring ist für das übliche Coding Dojo eine Black Box. Deren Deckel wird höchstens ein wenig imageangehoben. Besser aber man lässt sie geschlossen. Es könnte eine Büchse der Pandora sein. Wenn die erstmal geöffnet ist, käme man mit dem Codieren ja gar nicht mehr voran – oder würde gar nicht erst beginnen.

Das halte ich für sehr bedauernswert. Das möchte ich ändern.

TDD ist gut und schön. Wem das noch nicht langweilig ist, wer glaubt, dass alles besser wird, wenn er darin nur perfekt ist… der soll gern TDD hoch und runter üben. Mir käme das allerdings so vor, als würde jemand meinen, ein großer Kung Fu Meister zu werden, weil er den ganzen Tag am Mu ren zhuang arbeitet.

Zur Programmierkunst gehört aber doch mehr. Wenn nicht, wäre sie armselig und könnte schon bald von jedermann ausgeübt werden.

Was das ist, das mehr dazugehört? Damit meine ich nicht die unendliche Vielfalt an Technologien. Die ist sicherlich eine große Herausforderung. Doch letztlich ist das eher eine Frage der Masse. Das sind komplizierte Oberflächlichkeiten.

Viel, viel schwieriger sind jedoch die “issues behind programming”. Da geht es um Paradigmen. Da geht es um Methoden. Da geht es um eine Vielzahl von Qualitäten über die “simple” Korrektheit von Code hinaus.

TDD adressiert die vermeintlich. Aber in Wirklichkeit adressiert sie nicht TDD selbst, sondern das Refactoring, auf das sich TDD stützt. Nur wird eben genau das nicht geübt. [2]

Nun aber genug mit dem TDD bzw. Coding Dojo Bashing ;-) Ich will mich ja auch nicht zu sehr wiederholen.

Wie stelle ich es mir denn nun anders vor? Wie hätte ich mir ein Vorgehen zum Beispiel in Bezug auf die Kata Word Wrap gewünscht?

1. Domäne spezifizieren

Als erstes sollte gefragt werden, was denn wirklich, wirklich die Problemdomäne ist. Worum geht es? Und das sollte dann in einem Satz knackig formuliert werden.

Schon bei der Kata Word Wrap ist das nicht ganz einfach. Ich habe mich da auch erst verlaufen. Der Titel ist nämlich fehlleitend. Er passt nicht zur Aufgabenstellung.

Im Titel kommt Word Wrap vor. Damit scheinen Worte und ihr Umbruch im Kern der Ubiquitous Language zu stehen. Interessanterweise tauchen dann Worte in der Aufgabenbeschreibung eigentlich nicht mehr auf. Da steht zwar “You try to break lines at word boundaries.” – doch zentral ist nicht “word”, sondern “break lines”.

Es geht nicht um Word Wrap, sondern um Zeilen(um)bruch. Das mag zunächst im Gespräch ein feiner Unterschied sein – doch in der Implementation würde der sich ausweiten.

Ich habe mich auch verleiten lassen, eine Wortumbruchlösung zu suchen, statt einer für den Zeilenumbruch. Doch der geht an der Aufgabe vorbei. [3]

Mein Vorschlag für eine knackige Beschreibung der Problemdomäne wäre dann zum Beispiel diese:

Der Wrapper zerlegt einen einzeiligen Text in mehrere Zeilen einer Maximallänge zerlegen - wobei Worte nicht zerschnitten werden sollen, wenn es sich vermeiden lässt.

Diese Beschreibung betont die Zeilenorientierung. Worte kommen darin nur insofern vor, als dass sie nicht “beschädigt werden sollen”. Die Zerlegung soll zu keinen “Kollateralschäden” führen.

Ob diese Formulierung die beste ist, sei dahingestellt. Aber sie ist besser als keine. Die Kraft überhaupt einer knackigen Formulierung ist nicht zu unterschätzen. Sie richtet das weitere Denken aus, sie spannt einen Lösungsraum auf – und zieht damit gleichzeitig eine Grenze. So kann konstruktive Diskussion über einen Lösungsansatz entstehen. Denn die kann, um sich fokussiert zu halten, immer wieder auf diese knappe Spezifikation verweisen. Der Zweck ist also ähnlich der der Metapher im XP.

2. Lösung skizzieren

Mit der Beschreibung der Problemdomäne als Leitstern und der Anforderungsdefinition als Karte, sollte als nächstes ein Lösungsansatz erarbeitet werden. Das geschieht im Wesentlichen durch Nachdenken. [4]

Die Frage lautet: Wie würde ich das Problem lösen, wenn ich es von Hand tun müsste?

Bei der Beantwortung soll natürlich jede Art von Erfahrung eingebracht werden. Alles ist erlaubt. Das Ergebnis kann ebenfalls alles mögliche sein – nur kein Produktionscode.

Früher gab es für algorithmische Aufgabenstellungen die ehrenhaften Mittel Flowchart, Structogramm und vor allem Pseudocode. Heute scheint das nicht mehr hip zu sein. Überhaupt sehen Lösungsskizzen seltsam holprig aus, wo sie denn überhaupt externalisiert werden können. Meist existieren sie ja nur sehr diffus in den Köpfen der Entwickler – und niemand weiß so recht, ob alle denselben Lösungsansatz meinen.

Ich sehe da ein großes Defizit bei unserer Zunft. Die Kunst, Lösungen zu beschreiben, ohne sie gleich zu codieren, ist sehr schwach ausgeprägt.

Dabei verlange ich nichts Spezielles. Mir ist es im Grunde egal, wie man einen Lösungsansatz beschreibt. Er soll nur auf einem höheren Abstraktionsniveau als die spätere Implementierung liegen. Sonst hat man ja nichts gewonnen. Nur über “Skizzen” lässt sich effizient und effektiv diskutieren; nur sie lassen sich in einer Gruppe gemeinsam entwickeln. Code selbst ist dagegen zäh. Man verliert sich auch zu schnell in technischen Details und verliert das Big Picture der Lösung aus dem Blick.

Für die Kata Word Wrap könnte die Lösungsskizze zum Beispiel in einer Schrittfolge bestehen. Nix Grafik, nix Tool, nix großer Aufwand. Einfach nur mal hinschreiben, wie man schrittweise das Problem eines Zeilenumbruchs lösen könnte. Beispiel:

    1. Vom gegebenen Text bricht man eine Zeile von Maximallänge vorne ab. Es entstehen eine Zeile und ein Resttext.
    2. Dann schaut man, ob diese Zerlegung dazu geführt hat, dass ein Wort zerschnitten wurde. Falls ja, macht man die rückgängig: Es wird der abgeschnittene “Wortkopf” am Ende der Zeile zurück an den Anfang des Resttextes zum “Wortrumpf” versetzt.
    3. Die Zeile kann jetzt an den bisher schon erzeugten umgebrochenen Text angefügt werden.
    4. Sofern noch Resttext übrig ist, den als neuen gegebenen Text ansehen und damit genauso wie bis hierher verfahren.

Diese Lösungsschritte zu finden, ist doch kein Geniestreich, oder? Aber nun kann jeder im Team prüfen, ob er versteht, was die Aufgabe ist und wie eine Lösung aussehen könnte. Es kann Einigkeit im Team hergestellt werden. Ein Ziel kann anvisiert werden.

Und wer diese Lösung nicht gut genug findet, der kann jetzt aufstehen und eine Diskussion anzetteln. Gut so! Das ist nämlich auf dieser konzeptionellen Ebene viel einfacher, als wenn erst Berge an Code produziert sind.

Sobald die IDE angeworfen ist, sind alle im Codierungsbewusstseinszustand. Dann ist Diskussion über Grundsätzliches nicht erwünscht. Das finde ich auch total verständlich. Denn Lösungsfindung und Codierung der Lösung sind zwei ganz unterschiedliche Aspekte von Softwareentwicklung. [5]

Nicht nur bei Code ist es also klug, Aspekte zu trennen, sondern auch im Entwicklungsprozess.

3. Lösungsaspekte identifizieren

Aber erstmal zu den Aspekten der Lösung. Wie lauten die? Wie umfangreich sind sie? Wie stehen sie im Zusammenhang?

Im vorliegenden Beispiel sind die Aspekte:

  • Textzerlegung und
  • Textzusammenbau.

Zur Zerlegung gehört das Abschneiden einer Zeile vom gegebenen Text und die eventuell nötige Korrektur dieses Schnitts. Der Textzusammenbau besteht dann nur noch aus dem Anhängen der endgültigen Zeilen an einander.

Durch die Identifikation der Aspekte wird die Lösung nochmal auf ein höheres Abstraktionsniveau gehoben. Hier kann das Team wieder prüfen, ob sie sich stimmig anfühlt. Ja, es geht ums Gefühl, um Intuition, um Erfahrung. Denn bisher ist kein Code geschrieben.

Aber das macht nichts. Entwickler geben ja immer soviel auf ihre Erfahrung. Dann ist hier der richtige Zeitpunkt, sie mal einzusetzen.

Dass diese “Gedankenblasen” nicht crashen… Klar, das ist so. But it´s not a bug, it´s a feature. Würden sie crashen, wären sie Code. Und Code ist vergleichsweise starr – egal ob mit TDD oder sonstwie entwickelt.

Nachdenken, eine konzeptionelle Lösung finden, ist erstens eine Tätigkeit, die das Team zusammenführt. Und zweitens ist es eine qualitätssichernde Maßnahme. Es wird damit nämlich die Qualität des Input für den Engpass “Implementierung” im Softwareentwicklungsprozess angehoben.

Codieren ist kein Selbstzweck. Also sollten wir nicht möglichst viel Code, sondern möglichst wenig Code schreiben. Damit meine ich aber nicht möglichst wenige LOC, sondern möglichst wenige Überarbeitungen. Sozusagen “LOC in place” vs “LOC over time”.

Hier der Überblick über die Aspekte und ihre Zusammenhänge:

2012-11-10 08.35.34

So ein Bild kann man leicht kommunizieren. Es enthält die Essenz der verbalen Lösungsskizze. Und es zeigt deutlich die Aspekte. Wenn jetzt zum Beispiel ein neues Teammitglied an Bord käme, könnte es anhand dieses Bildes leicht in den Lösungsansatz eingeführt werden. Warum den also nicht dem Code beigeben? Oder versuchen, ihn im Code möglichst nachvollziehbar festzuhalten?

Ja, genau, dieser Entwurf sollte sich im Code widerspiegeln. Das ist nicht umsonst ein Clean Code Developer Prinzip. Denn wenn der Entwurf nur für den einmaligen Codierungsgebrauch gemacht würde und danach verschwände, müssten Entwickler in Zukunft wieder Codearchäologie betreiben, um herauszufinden, wie das denn alles gemeint war.

Zu sehen ist im Bild eine Aspekthierarchie. Zuoberst der umfassende Aspekt der Domäne, d.h. der Gesamtheit der Anforderungen.

Darunter die beiden aus der Lösungsskizze abstrahierten Aspekte.

Und auf der untersten Ebene deren Sub-Aspekte, die konkreten Operationen. Die Doppelpfeile deuten an, wie sie horizontal zusammenhängen, um die funktionalen Anforderungen zu erfüllen.

Das ist doch nicht schwer zu verstehen, oder? Da muss man kein UML-Studium absolviert haben. Man muss auch nicht OOP- oder FP-Jünger sein. Gesunder Menschenverstand reicht aus. Eine umfassende Aufgabe wurde hier nachvollziehbar zerlegt in kleinere Aufgaben. Das ist ein probater Ansatz bei einer so algorithmischen Aufgabenstellung.

4. Lösung codieren

Mit der Lösungsskizze in der Hand, kann man sich ans Codieren machen. Große Überraschungen sollten sich bei der geringen Komplexität nicht einstellen. Das Codieren kann geradlinig ablaufen. Ob mit TDD oder ohne? Ich sag mal: Zuerst einen oder mehrere Tests für eine Operation zu definieren, bevor man sie implementiert, ist schon eine gute Sache. Das kleinschrittige Vorgehen wie bei TDD jedoch, halte ich in diesem Fall nicht für zwingend. Also: Test-First, ja; TDD, nein.

Und mit welcher Funktionseinheit beginnen?

Das Schöne an der Lösungsskizze ist, dass man beliebig reingreifen kann. Man kann zum Beispiel mit dem Anfügen einer Zeile an den schon umgebrochenen Text beginnen. Hier die Tests zuerst:

image

Und hier die Implementation:

image

Das ist überschaubar. Das bringt schnell ein Erfolgserlebnis. Das lässt sich auch sofort in eine Wrap()-Funktion einsetzen…

image

um erste Integrationstests zu erfüllen:

image

Ist ja nicht so, dass bei diesem Vorgehen nicht in Durchstichen, d.h. in Nutzeninkrementen gedacht werden soll.

Aber warum nicht den Code direkt in Wrap() schreiben und später raus-refaktorisieren? Weil wir uns auch nicht dümmer stellen müssen, als wir sind. Wir wissen schon, dass das ein eigener Aspekt ist und andere hinzukommen werden. Und Aspekte kapselt man mindestens in eigene Methoden.

Der aspekteigene Test ist ein Unit Test. Wenn später mal etwas schiefgehen sollte, dann zeigen solche Unit Tests viel besser als Integrationstests, wo das Problem liegt. Der Test von Wrap() hingegen ist ein Integrationstest, weil er viele Aspekte auf einmal prüft.

Und so geht es dann weiter: Jeder Aspekt kann für sich codiert werden. Naja, jede Operation, also die Aspekte auf der untersten Ebene bei der obigen Zeichnung. Die darüber liegenden Aspekte integrieren diese, sie sind abhängig. Deshalb ist wohl tendenziell eine bottom-up Codierung angezeigt.

In jedem Fall entstehen Operationsmethoden, die jede für sich getestet sind. Wenn Sie die mit TDD implementieren wollen… ist das keine schlechte Idee. Ich bin ja nicht gegen TDD. Nur schmeckt mir der “Allmachtsanspruch” von TDD nicht. Im Rahmen eines durch Nachdenken entstandenen Lösungsansatzes kann TDD jedoch eine Hilfe bei der Implementierung dessen “Black Boxes” sein, d.h. der Operationen.

5. Ergebnissicherung

Das Ergebnis dieser Herangehensweise können Sie bei github einsehen. Tests und Produktionscode liegen in einem Gist. An dieser Stelle möchte ich daraus nur einen Teil zitieren, um zu zeigen, was ich unter Verständlichkeit verstehe:

image

Diesen Code kann man von oben nach unten lesen. Das Abstraktionsniveau sinkt von Methode zu Methode. Jede steht für einen Aspekt. Die Ebenen der obigen Zeichnung sind erhalten; der Code spiegelt damit den Entwurf. Oder umgekehrt: der Entwurf kann aus dem Code herausgelesen werden.

Wie funktioniert Word Wrap?

  1. In Wrap() hineinschauen und erkennen, dass dort einfach nur an eine Methode delegiert wird. Was sagt uns das? Hier ist Rekursion im Spiel. Das ist ein Pattern. Wrappen ist also eine Tätigkeit, die irgendwie mehrfach ausgeführt wird, um das Gesamtergebnis herzustellen.
  2. In Wrap_remaining_text() hineinschauen und dort die Schrittfolge sehen:
    1. Zeile extrahieren
    2. Zweile an neuen Text anhängen
    3. Das Ganze wiederholen mit dem restlichen Text [6]
  3. Und wie funktioniert das mit dem Extrahieren der Zeile? Einfach in der nächsten Methode nachschauen:
    1. Zeile einfach abschneiden vom Text
    2. Heilen eines eventuell dabei “verwundeten” Wortes

Und so weiter… Je nach Interessenlage des Lesers kann die Erkundung tiefer und tiefer vordringen. Jede einzelne Funktion ist überschaubar: der Name sagt den Zweck an, die wenigen Zeilen sind leicht zu entziffern.

Das ist grundsätzlich anders bei den im vorherigen Artikel zitierten Lösungen. Die sind monolithisch. Word Wrap findet dort irgendwie statt. Erfolgreich – jedoch wenig verständlich.

Reflexion

Ob ich “die beste” Lösung gefunden habe? Keine Ahnung. Ich habe eine ausreichend funktionierende gefunden; sie ist good enough. Und ist finde sie verständlich. Nicht nur, weil ich sie entworfen und implementiert habe, sondern weil die “Gedankenstrukturen” im Code sichtbar geblieben sind. Mehr ist mit heutigen Sprachen nicht zu wünschen, glaube ich.

Aber – so mögen Sie nun einwänden – die Lösung hat so viele LOC! Sie ist viel länger als Uncle Bobs.

Ja? Und? Warum ist das ein Problem? Macht das einen Unterschied bei der Compilation oder in der Laufzeit? Nein. Müssen wir auf Hauptspeicher oder Festplattenplatz achten? Nein.

Hat die Codierung deshalb länger gedauert? Das bezweifle ich. Aber selbst wenn, wäre mir das egal. Denn es kann nicht das Ziel der Softwareentwicklung sein, möglichst schnell Code zu schreiben, der funktioniert. Ziel muss es sein, Code zu produzieren, der lesbar und evolvierbar ist – ohne natürlich die funktionalen und nicht-funktionalen Anforderungen zu vernachlässigen.

Code wird viel öfter gelesen, als geschrieben. Deshalb sollte er fürs Lesen und nicht fürs Schreiben optimiert sein. Das habe ich getan, würde ich sagen. Das war zumindest meine Absicht.

Bonus: Nagelprobe Evolution

Ob Code clean genug ist oder nicht, kann man eigentlich nicht 100% entscheiden, indem man ihn nur liest. Seine Sauberkeit erweist sich erst, wenn man ihn verändern will. Wie geschmeidig ist er dann?

Das ist auch das Problem von 99% der Literatur. Dort Code präsentiert in eingefrorenem Zustand. Wie lange jemand dafür gebraucht hat, sieht man nicht. Wie gut die Struktur im Lichte der so wichtigen Evolvierbarkeit ist, sieht man nicht.

Also versuche ich es ein bisschen besser zu machen. Hier drei neue Anforderungen an den Code:

  1. Der umzubrechende Text besteht nicht aus einer, sondern aus mehreren Zeilen. Die können als Absätze verstanden werden.
  2. Zusammengesetzte Worte können bei Bindestrichen getrennt werden.
  3. Zeilen sollen im Blocksatz formatiert werden.

Wie genau diese Funktionalität aussieht, ist zunächst nicht wichtig. Sie zu implementieren, ist eine Fingerübung. Im Sinne der Evolvierbarkeit ist interessanter, wie leicht kann ich feststellen, wo überhaupt Veränderungen angebracht werden müssen? Werden bestehende Aspekte beeinflusst; welche? Sind neue Aspekte einzufügen; wo?

Für das alles, will ich natürlich nicht Berge an Code durchsehen müssen. Ich will auch höherer Abstraktionsebene erstmal darüber grübeln.

Blocksatz

Also: Wo müsste ich beim Blocksatz ansetzen? Muss eine Funktionseinheit, ich meine eine Methode, verändert werden? Sollte ich den Blocksatz irgendwo dazusetzen?

Blocksatz ist sicherlich ein ganz eigener Aspekt. Der hat nichts mit Texttrennung zu tun und auch nicht mit dem Zusammenbau des umgebrochenen Textes. Also darf keine bisherige Operation dafür verändert werden. Stattdessen sollte der Blocksatz zwischen den bisherigen Aspekten Textzerlegung und Textzusammenbau stattfinden. Weder muss die Textzerlegung davon wissen, noch der Textzusammenbau.

image

Der Blocksatz muss sich nur auf die gerade “abgebrochene” und “geheilte” Zeile konzentrieren. Das kann so im Code aussehen:

image

Wieder lasse ich bewusst Details weg. Weil ich es kann. Weil es überhaupt mehrere Abstraktionsebenen gibt in meinem Code. Wer sich für die genaue Implementation von Justify() interessiert, kann ja weiter “reinzoomen” im Gist.

Trennung bei Bindestrich

Wie ist es mit den Bindestrichen für die Trennung? Ist das ein neuer Aspekt wie der Blocksatz oder verändert diese Anforderung einen bestehenden?

Ich würde sagen, hier geht es um eine Variation der Operation “Schnitt korrigieren” im ersten Entwurfsbild bzw. Schritt 2 in der verbalen Beschreibung des Lösungsansatzes.

Falls ein Wort zerschnitten wurde, wird sein “Kopf” nicht einfach komplett wieder an den “Rumpf” im Resttext geheftet, sondern es wird nach einem Bindestrich im “Kopf” gesucht und nur der dahinter liegende “Teilkopf” wird angeheftet.

Hier dazu die neuen Testfälle für die “Heilung” der Zeilenabspaltung:

image

Dadurch ändert sich natürlich einiges am Arbeitspferd der “Heilung”. Dieser Aspekt bekommt Sub-Aspekte – eine Zeile kann entweder mit einem zerschnittenen Wort enden oder mit einer “Silbe”:

image

…die sich dann auch im Code niederschlagen müssen:

image

Und nochmal: Wer mehr Details sehen will, “zoomt rein”. Für ein Verständnis des Lösungsansatzes sind das nicht nötig.

Mehrzeilige Texte

Für die Behandlung mehrzeiliger Texte ist zunächst zu klären, wie der API aussehen soll. Liegen die Texte als ein string vor, in dem Zeilen durch \n abgetrennt sind? Oder liegen mehrere Texte z.B. in Form eines IEnumerable<string> vor?

Ich nehme mal an, dass es weiterhin nur ein string ist, in dem jetzt auch schon Zeilenumbrüche vorhanden sein können. Damit ändert sich der API nicht.

Diese Zeilenumbrüche sollen natürlich erhalten bleiben. Das ist in einem Testfall zu spezifizieren:

image

Der Lösungsansatz sieht dafür dann im Grunde aus wie der bisherige:

  1. Etwas von einem Rest abtrennen, nämlich den nächsten Text
  2. Das Abgetrennte verarbeiten, nämlich den bisherigen Word Wrap darauf ausführen
  3. Das Verarbeitungsergebnis an das bisherige Resultat anhängen

Graphisch wächst das Modell sozusagen nach oben. Es gibt eine neue Aspekt-Ebene oberhalb des bisherigen Word Wrap: das Word Wrap (multi) :-) Es zerfällt in das Word Wrap in drei Sub-Aspekte – von denen der letzte derselbe wie beim Word Wrap ist, der Textzusammenbau.

image

Bei soviel Ähnlichkeit liegt es nahe, die Struktur der Implementation zu kopieren. Eine Rekursion ist auch hier gleichermaßen elegant wie verständnisförderlich.

image

Wie funktioniert der Code? Wrap_multi_text() verrät es. Einfach wieder von oben nach unten lesen. Der Lösungsansatz steht dort 1:1 übersetzt.

Fazit

Tut mir leid, ich kann nicht anders als zu sagen: Das war alles ganz einfach mit ein bisschen nachdenken. Think a little, code a little.

Ich hoffe, Sie sehen das genauso: Der Code ist verständlich und evolvierbar. Dass er nicht der kürzestmögliche ist, sei zugestanden. Aber ist das wirklich sooo wichtig? Nein. An LOC zu sparen, stolz auf eine kurze-knappe Lösung zu sein, halte ich für eine premature optimization.

Nichts ist natürlich gegen Eleganz zu sagen, wenn Sie die Verständlichkeit und Evolvierbarkeit erhöht. Rekursion statt Schleife finde ich deshalb für diese Kata auch so gut wie Robert C. Martin. Aber an LOC sparen zu wollen, um vielleicht hier und da ein bisschen schneller mit dem Schreiben fertig zu werden, scheint mir wenig nachhaltig. Das ist nicht in die Zukunft gedacht, in der der Code noch viele Male gelesen und verstanden werden muss.

Zum Schluss auf den Punkt gebracht. Was habe ich getan? Ich habe ganz konsequent…

  • das SRP angewandt,
  • das Prinzip SLA angewandt,
  • möglichst die grundlegenden Semantikaspekte Integration und Operation getrennt,
  • Domänenaspekte für sich getestet.

Versuchen Sie das bei der nächsten Code Kata doch auch einmal. Aspektvolles Programmieren hilft.

Fußnoten

[1] Es täte mir leid, wenn ich damit zu pauschal urteilen würde. Wenn irgendwo die Dojo-Praxis davon deutlich abweicht, dann melde man sich. Ich werde mich dann bemühen, persönlich mal dabei zu sein und zu lernen, wie es auch anders gehen kann.

[2] Dass es inzwischen hier und da Coding Dojos oder Code Retreats gibt, die sich mit Legacy Code befassen, ist löblich. Die Frage ist nur, inwiefern dort konzeptionell an die Sache herangegangen wird. In welchem Rahmen wird dort über Pathogenese und auch Salutogenese von Software nachgedacht?

[3] Ob ein Wortumbruch statt eines Zeilenumbruchs sinnvoller wäre, lässt sich nicht entscheiden. Es gibt keinen Kontext für die Code Kata. Die Aufgabe fordert ihn jedenfalls nicht. Und wenn wir im Sinne von YAGNI auch lernen wollen, genau hinzusehen und nur zu implementieren, was wirklich gefordert ist, dann tun wir gut daran, uns an die Aufgabe zu halten – zumindest solange niemand da ist, mit dem wir über ihre Sinnhaftigkeit diskutieren können.

[4] Wer dabei auch mal etwas ausprobieren muss, soll das gern tun. Nachdenken darf durch Hilfsmittel jeder Art unterstützt werden. Bei den üblichen Code Katas sollte das jedoch eher nicht nötig sein. Die sind rein algorithmisch und sehr überschaubar. Ausprobieren im Sinne von Codieren sollte nicht nötig sein.

[5] Damit will ich nicht sagen, dass man die Lösungsentwicklung und Codierung personell trennen sollte. Auf keinen Fall! Aber wir sollten anerkennen, dass das eben zwei unterschiedliche Tätigkeiten sind, die auch unterschiedliche Kompetenzen brauchen. Deshalb sollten sie nicht pauschal vermischt werden, wie es bei TDD in der Literatur der Fall ist.

[6] Bitte beachten Sie den Gebrauch von dynamic hier. Dadurch wird der Code leserlich, ohne dass ich für den Zweck der Zusammenfassung einer Zeile mit dem verbleibenden Rest einen eigenen Typen einführen muss oder auf Tuple<> ausweiche.

Sonntag, 4. November 2012

Notorische Aspektlosigkeit

Immer wieder, wenn ich Resultate von Coding Dojos sehe, beschleicht mich ein ungutes Gefühl. Irgendetwas fehlt mir bei den Lösungen. Dabei ist es egal, ob das Coding Dojo in der Gruppe durchgeführt wurde oder sich jemand allein zum Einzeltraining ins Dojo begeben hat.

Anlässlich eines kundeninternen Coding Dojos habe ich nun nochmal länger über die gefühlte Lücke nachgedacht. Als Aufgabe wurde die Kata Word Wrap bearbeitet. Der Abend war gut vorbereitet: Laptop, Beamer, Catering, Aufgabenbeschreibung. Und sogar Testfälle waren hübsch priorisiert schon notiert, damit sich die Gruppe auf die TDD-Fingerfertigkeit konzentrieren konnte [1].

Nach knapp 90 Minuten war denn auch die Lösung fast wie aus dem Buch erarbeitet. Alles war harmonisch verlaufen. Kein Haareziehen, keine Weigerungen, mal nach vorn zu gehen und im Pair zu codieren. Alles hätte also gut sein können.

Allein… der Code sah so aus:

image

oder vielleicht auch so:

image

oder er könnte auch so ausgesehen haben:

image

Letztlich ist es egal, wie genau er ausgesehen hat. Ich habe ihn nicht mitgeschrieben, die Beispiele oben sind aus den Weiten des Internet. Alle haben aber das gemeinsam, was ich als fehlend empfunden habe.

Mir fehlen die Aspekte.

Coding Dojo Lösungen sind nach meiner Erfahrung notorisch aspektlos. Oder anders ausgedrückt: die Prinzipien SRP und SoC finden selten Anwendung. SLA kommt allerdings hin und wieder vor.

Worüber ich immer wieder gestolpert bin ist also die Unverständlichkeit der Lösungen. Wie funktioniert denn so ein Word Wrap. Schauen Sie sich einfach das erste Listing an und formulieren Sie in einem ganzen Satz, wie (!) das geht mit dem Wortumbruch.

Ich bezweifle nicht, dass die Lösungen funktionieren. So unterschiedlich sie aussehen, die kanonischen Testfälle absolvieren sie fehlerfrei. Aber das ist ja seit 50 Jahren eher nicht das Problem in der Softwareentwicklung. Wir können lauffähige Software herstellen. Und auch noch solche, die performant, skalierbar, sicher usw. ist.

Dass in der Eile da mal ein paar Bugs durch die Maschen schlüpfen… Das ist nicht schön, kriegen wir aber in den Griff, wenn wir mehr automatisiert testen. TDD hilft auch.

Aber ist der nützliche Code, den wir da produzieren auch verständlich?

Eher nicht. Und das beginnt bei so kleinen Beispielen wie den obigen.

Vielleicht stelle ich mich jetzt dumm an? Vielleicht schade ich geradezu meinem Ansehen, wenn ich meine Begriffsstutzigkeit so deutlich dokumentiere? Keine Ahnung. Ist mir auch egal. Fakt ist, dass ich es schwierig finde, aus den obigen Beispielen herauszulesen wie (!) so ein Wortumbruch funktioniert.

Vielleicht schauen Sie ja einfach drauf und sagen nach 10 Sekunden: “Achsooo! So geht das. Klar. Hätte ich auch so gemacht.” Kann sein. Dann beneide ich Sie. Ich würde das auch gern können.

Bitte verstehen Sie mich richtig: Ich kann natürlich den Code lesen und herausfinden, wie er funktioniert. Ich kann auch selbst eine Lösung herstellen.

Mir geht es aber nicht darum, ob ich irgendwie zu einem Codeverständnis gelangen kann, sondern wie schnell.

Ich habe den vielleicht verwegen klingenden Anspruch, dass sich im Code die Domäne widerspiegelt. Dass oben drüber Wrap() steht, ist mir nicht genug. Dadurch weiß ich nur, worum es irgendwie geht. Das hilft mir wenig, wenn ich den Code ändern soll, weil ein Bug drin steckt oder sich die Anforderungen geändert haben.

Sorry to say, die obigen Lösungen sind im Wesentlichen alle Monolithen. Kleine Steine, aber immer noch ziemlich strukturlose Steine. Nur Uncle Bob hat es etwas besser gemacht. Aber dazu gleich mehr.

Ich hab es einfach satt, im Steinbruch zu arbeiten. Ich will nicht mehr Archäologie betreiben und darüber rätseln, was eine Entwicklerintelligenz irgendwann mal ersonnen hat, das ich mir nun mühsam wieder erarbeiten muss. Ich will nicht bei jedem 20-Zeiler Reverseengineering betreiben. Das ist einfach teuer. Das kostet wertvolle Lebenszeit. “Was will uns der Autor sagen?” ist eine Frage für das Feuilleton oder den Deutschunterricht.

Jeder genügend obskure Code erscheint magisch ;-) (frei nach Arthur C. Clark)

Was ist stattdessen will, ist eine irgendwie lesbare Lösungsbeschreibung – im Code. Ja, genau, im Code. Mir geht es nicht um Papierdokumentationsberge und auch nicht um mehr Kommentare. Ist schon so, im Code steckt die ultimative Wahrheit. Aber wenn das so ist, dann doch bitteschön etwas leichter erkennbar als bei den Upanishaden oder der Kabbala.

Also, wie funktioniert ein Word Wrap? Keine Ahnung, wie die obigen Autoren meinen, dass es funktioniert. Aber hier mal ein Vorschlag:

  1. Der Input-Text wird in Worte zerlegt.
  2. Die Worte werden in Silben zerlegt.
  3. Die Zeilen des Output-Textes werden aus Silben zusammengesetzt, bis die maximale Zeilenlänge erreicht ist.

Das ist mein Vorschlag für eine Lösung auf hohem Abstraktionsniveau, sozusagen ein Entwurf. Ich habe das Problem in drei Aspekte zerlegt: Wortermittlung, Silbenbestimmung, Zeilenzusammenbau. Der Entwurf folgt damit meiner Interpretation der Aufgabenstellung, die da lautet “KataWordWrap”.

  1. Ich nehme den Wortumbruch ernst. Deshalb taucht in meiner Lösungsskizze der Begriff “Wort” auf.
  2. Die Silben sind meine Hinzudichtung. Die stehen nicht in der Aufgabenstellung. Mit ihnen meine ich auch nicht un-be-dingt Sil-ben, sondern Abschnitte von Worten, zwischen denen getrennt werden darf. In der Problemstellung ist das jeder Buchstabe. Den Begriff “Silbe” habe ich hinzugedichtet, um das Problem an etwas Bekanntes anzuschließen.

Mit dieser Lösungsidee im Hinterkopf schaue ich nun auf den obigen Code und sehe… nichts. Dort tauchen die Begriffe “Wort” oder “Silbe” nicht auf. Und Lösungsschritte sehe ich auch nicht.

Allerdings macht Uncle Bob eine Ausnahme. Dessen Code liefert einen gewissen Hinweis auf seinen Lösungsansatz. Allerdings lese ich auch da nichts von “Wort” oder “Silbe”, sondern von “Zeilenbruch”. Hm… das ist interessant.

Meine Interpretation: Uncle Bob löst ein anderes Problem als ich ;-) Uncle Bob löst das Problem “Zeilenlängenbegrenzung” (oder so ähnlich) und nicht das Problem “Wortumbruch”. Beides mag ähnlich klingen, ist aber etwas anderes.

Wer hat nun Recht? Im Ergebnis würden wir wohl dasselbe liefern – aber unsere Lösungen würden sich eben unterscheiden. Wir sehen unterschiedliche Domänen.

Aber auch wenn Uncle Bob eher die Domäne im Code repräsentiert, wie er sie aus der Aufgabenstellung herausgelesen hat, finde ich seinen Lösungsansatz noch nicht leicht zu lesen. Was bedeutet zum Beispiel if (s.charAt(col) == ‘ ‘)?

Klar, dass kann ich mir irgendwie zusammenreimen. Aber ich will mir nichts mehr zusammenreimen. Ich habe mal Informatik studiert und nicht Ethnologie oder Archäologie. Wenn der Code nicht intention revealing ist, dann habe ich damit ein Problem.

Code muss ich lesen und verstehen, nicht wenn und weil er läuft, sondern wenn er eben nicht läuft wie gewünscht, d.h. wenn ich an ihm etwas verändern soll. Dann muss ich verstehen, wie er seine Aufgabe heute erledigt und leicht die Ansatzpunkte identifizieren, wo ich Veränderungen im Sinne neuer Anforderungen anbringen muss.

Was könnten denn Veränderungen sein, die an “Word Wrap” Code herangetragen werden?

  • Die maximale Output-Zeilenlänge ändert sich. Es verändert sich nur eine Konstante.
  • Der umzubrechende Text liegt nicht in einer Zeile, sondern in mehreren vor. Es verändert sich also die Struktur der Quelldaten.
  • Der Quelltext hat mehrere Absätze, die im Output erhalten bleiben sollen. Eine Veränderung nicht nur in der Quelldatenstruktur, sondern auch in der Zieldatenstruktur.
  • Worte sollen nicht mehr an beliebiger Stelle geteilt werden dürfen, sondern nur noch zwischen Silben.
  • Beim Umbruch sind Satzzeichen zu berücksichtigen. So darf zum Beispiel ein Bindestrich nicht am Anfang einer neuen Zeile stehen.

Ich behaupte nun nicht, dass eine Kata-Lösung all dies und mehr vorhersehen sollte. Keinesfalls! Die Lösung soll ausschließlich den aktuellen Anforderungen genügen. Auch wenn meine Lösungsskizze oben Silben erwähnt, würde ich also keine echte Silbenerkennung implementieren, sondern den Umbruch bei jedem Buchstaben zulassen.

Dennoch ist ja eines immer wieder so überraschend wie Weihnachten: dass sich Software über die Zeit ändert.

Deshalb sollten wir Vorkehrungen treffen, damit uns das leicht fällt.

Für mich bestehen solche Vorkehrungen mindestens darin, die Aspekte des Lösungsansatzes klar im Code sichtbar zu machen. Und genau das geschieht eben nicht in den ersten beiden obigen Lösungen und nur bedingt bei Uncle Bob.

Wir können Aspekte in Methoden oder Klassen verpacken. Je nach Umfang. Das ist doch kein Hexenwerk. Und wir können auch noch die fundamentalen Aspekte Integration und Operation trennen.

Dann würden unsere Lösungen lesbarer, glaube ich.

TDD is not enough

Noch ein Wort zur Ursache der Unlesbarkeit. TDD ist da sicherlich nicht schuld. TDD ist ne gute Sache. Soll man im Coding Dojo gerne üben.

Aber man sollte eben die Heilsbringererwartungen nicht zu hoch schrauben. TDD dreht sich am Ende doch “nur” ums Testen. Auch wenn es red – green – refactor heißt, so führt das eben nicht automatisch zu lesbarem, evolvierbarem Code.

Eine gut priorisierte Liste von Testfällen plus red – green liefert Code, der in kleinen Schritten hin zu einer funktionalen Lösung wächst. Die Codeabdeckung wird groß sein. Das halte ich für das erwartbare Ergebnis, sozusagen das kleinste garantierte Resultat von TDD.

Der Rest jedoch… der hat eben nichts mit TDD zu tun. Verständlichkeit, Evolvierbarkeit, punktgenaues Testen (also Unit Tests statt Integrationstests [2])… all das ist optional bei TDD. Es entsteht nicht zwingend. Das ist meine Erfahrung aus vielen Coding Dojos und zeigt sich auch an den obigen Codebeispielen.

Damit behaupte ich nicht, dass das nie entstehen würde. Ich sage nur, das entsteht nicht durch TDD, sondern durch Refactoring-Mühe und –Kompetenz.

Refactoring mit TDD gleichzusetzen, wäre jedoch falsch. Refactoring ist eine unabhängige Disziplin. Deshalb sage ich, Verständlichkeit und Evolvierbarkeit entstehen eben nicht durch TDD. Und wenn man in einem Coding Dojo TDD übt, dann übt man de facto eben eher nicht Refactoring. Das wäre auch eine Überlastung für die Beteiligten, ein Verstoß gegen das SRP, sozusagen. Mit der Definition von Tests, mit dem red-green-Rhythmus, mit KISS bei der Implementation, mit all dem haben die Beteiligten schon genug Mühe. Wenn Refactoring da nicht quasi im Stammhirn verankert ist, also schon als Reflex existiert, dann findet Refactoring nur auf Alibiebene statt, da wo es einem schlicht ins Auge springt, weil so deutliche Anti-Muster im Code zu sehen sind.

Was dabei aber eher nicht entsteht, das ist Aspekttrennung. Und schon gar nicht entstehen getrennte Tests für Aspekte.

Indem ich das so “anprangere” will ich nicht die Praxis der Coding Dojos herabsetzen. Mir geht es nur um realistischere Erwartungen. Die Ergebnisse sind schlicht und einfach Ergebnisse der grundlegenden Wahrheit: you always get what you measure. Denn was bei TDD “gemessen” wird, worauf das Augenmerk liegt, das sind die Tests. Es heißt ja “Test-Driven Development” und nicht “Design-by Testing” oder so. Tests stehen im Vordergrund und im Mittelpunkt. Und die kriegt man. Alle sind zufrieden, wenn sie im red-green-Rhythmus arbeiten und schrittweise die Lösung hinkriegen. Und auf dem nächsten Level dann noch bei jedem green in ein VCS einchecken.

Das ist alles super. Nur eben auch nicht mehr. Es kommen Lösungen heraus, die testabgedeckt sind. Wunderbar. Man merkt ihnen an, dass sie stolz geschrieben wurden. Ans Lesen und spätere Verändern wurde dabei wenig Gedanke verschwendet. Während des Coding Dojos ist ja der ganze Kontext auch in allen Köpfen, die Lösung entsteht vor aller Augen. Niemand hat ein Problem mit der Verständlichkeit. Noch nicht.

Deshalb erhärtet sich bei mir weiter der Verdacht: TDD is not enough. Wenn wir das sehr löbliche Üben auf TDD beschränken, dann tun wir zuwenig.

Dass es darauf beschränkt ist, finde ich ja verständlich. red – green + KISS ist so schön greifbar. Da sieht man den Erfolg. Und KISS ist ganz im Sinne des alten Eleganzgedankens.

Leider sind das Ergebnis dann lokale Optima. Knapper testabgedeckter funktionaler Code ist im Sinne dieses Vorgehens optimiert – aber hat nicht das Big Picture im Blick. Zu dem gehören für mich Verständlichkeit und Evolvierbarkeit.

In diesem Sinne ist auch die Freude aller zu verstehen, wenn die Lösung kleiner wird. Weniger LOC sind gut. Als sei darauf ein Preis ausgelobt.

Es gilt jedoch nicht Verständlichkeit == Kürze. Verständlichkeit mag mit Kürze korrelieren, deshalb ist jedoch der kürzeste Code nicht automatisch der verständlichste. Für mich darf also etwas “Fluff” in einer Lösung sein, gar etwas Redundanz, wenn dadurch Verständlichkeit und Evolvierbarkeit steigen.

Versuchen Sie es doch mal. Einfach so als Experiment. Beim nächsten Coding Dojo könnten Sie sich bewusst mal die Frage stellen, welche Aspekte Sie erkennen – und dann überlegen Sie, wie Sie die in der Lösung deutlich sichtbar machen. Darauf aufbauen könnten Sie nach einer Formulierung der Lösung suchen, die ihre Funktionsweise leicht erkennbar macht. Wie arbeiten die Aspekte einer Lösung zusammen, um die Anforderungen zu erfüllen?

Fußnoten

[1] Dass Testfälle schon ausgearbeitet vorlagen, fand ich allerdings nicht so schön. Das schien mir dem Lerneffekt eher abträglich. Denn darin liegt ja eine der zentralen Herausforderungen bei TDD: relevante Testfälle überhaupt erst einmal erkennen und dann noch in eine Reihenfolge bringen, die ein kleinschrittiges Wachstum des Codes begünstigt.

[2] Selbst bei Uncle Bob gibt es nur Tests für wrap(). Die herausgelöste Funktion breakline() wird nicht separat getestet, obwohl sie ein Arbeitspferd der Lösung darstellt. Damit sind alle Tests seiner Lösung Integrationstests – auch wenn sie auf einem höheren Abstraktionsniveau die Unit wrap() testen.

Mittwoch, 22. Juni 2011

Lernen mit Format

Das Dojo ist wieder im Gespräch. Ilker hat einen (selbst)kritischen Artikel dazu in seinem Blog geschrieben. Seiner Zurückweisung der Sicht, “dass ein Coding Dojo doch nur ein ‘nettes Rahmenprogramm’ sei” stimme ich zu. Es ist ein “lockeres Mittel zum Training und Erfahrungsaustausch” – auch wenn es mal im Rahmenprogramm einer anderen Veranstaltung auftauchen mag. Gemeinsam lernen: darum geht es beim Coding Dojo.

Aber wie kann es sein, dass das nicht ganz einfach von allen Teilnehmern (oder Ferngebliebenen) auch erkannt wird? Ist das Coding Dojo ein so kryptisches Format?

Meine Vermutung ist, die Missverständnisse hängen weniger am Rahmen, in dem ein Coding Dojo stattfindet – z.B. Konferenz oder Recruiting Day –, als vielmehr an der Art, wie im Dojo mit Lernen umgegangen wird.

Wenn Lernen das Ziel eines Dojos ist, dann sollte eine klare Vorstellung davon existieren, wie Lernen funktioniert. Nur dann ist zu erwarten, dass Lernen auch passiert.

Was gehört also zum Lernen? Was sind die minimalen Zutaten, um ein Dojo zu einem Lernfest zu machen? Ja, ich meine Lernfest, also Lernen im Rahmen eines Festes, nicht nur feste lernen ;-) Lernen soll, nein, muss Spaß machen, sonst ist es nicht effizient und effektiv. Entertainment darf also passieren bei einem Coding Dojo.

Für mich sind die drei unverbrüchlichen Bestandteile von Lernen diese:

  • Klare Aufgabe: Ein Dojo braucht eine klare Aufgabe. Alle Teilnehmer müssen wissen, was sie tun sollen. Dazu gehört die Code Kata als zu lösendes Problem, aber auch das Format (z.B. der Modus, Hilfsmittel, Zeitlimit).
  • Klares Ziel: Die Aufgabe ist ein Mittel, um ein Ziel zu erreichen. Aber welches? Was soll in einem Coding Dojo gelernt werden? Worauf sollen die Teilnehmer während der Aufgabenlösung achten? Ein wiederkehrendes Ziel ist, sich in der Kunst des TDD zu verbessern. Ein anderes Ziel könnte sein, Pair Programming auszuprobieren. Alles Mögliche kann als Lernziel ausgerufen werden: Methoden, Technologien, Konzepte. Wichtig ist lediglich, dass das Lernziel klar und allseits akzeptiert ist.
  • Reflexion: Lernen passiert nicht einfach so. Oder wenn, dann nur unzuverlässig. Lernen braucht vielmehr eine Reflexion. Es braucht Feedback und Nachdenken über den Lernprozess durch die Lernenden. Das nennt sich dann “deliberate practice”. Hier setzt auch das Lehren im Dojo an. Selbst wenn kein ausdrücklicher Lehrer anwesend ist, lehrt die gemeinsame Erfahrung, wenn sie denn reflektiert wird. Die Teilnehmer sind ihre eigenen Lehrer, auch wenn keiner ein Experte in Bezug auf das Lernziel ist. (Experten sind also keine Lernvoraussetzung, sondern eher Lernkatalysatoren; sie beschleunigen das Lernen.)

Wer nun ein Dojo ausrichtet, der ist dafür verantwortlich, dass es ein Lernfest wird. Er muss sich ums Entertainment kümmern – doch das ist nur die Kür. Pflicht hingegen ist, einen Lernerfolg für alle sicherzustellen.

Zur Pflicht gehört es also, erstens eine klare Aufgabe zu formulieren, zweitens ein klares Lernziel anzupeilen und drittens am Ende die Reflexion anzuleiten.

Aufgabe und Lernziel müssen allerdings nicht schon vorher im Kopf des Veranstalters fest sein, können es aber. Ich finde es nicht so wichtig, ob beides vorgegeben ist oder nicht, wenn die Teilnehmer damit kein Problem haben. Enttäuschung über ein Coding Dojo wird eher nicht entstehen, nur weil nicht die persönliche Lieblingskata eines Teilnehmers die Aufgabe ist oder das persönliche Lernziel grad nicht dran kommt. Enttäuschung entsteht, wenn Aufgabe und Ziel nicht erreicht werden – und am Ende unklar ist, warum.

Eine klare Aufgabe, ist selten das Problem eines Coding Dojos. Ein klares Ziel hingegen ist schon seltener. Und noch seltener ist eine gewinnbringende Reflexion. Nach all dem Adrenalin, das während 1-2 Stunden geflossen ist, mag die schwer fallen, doch sie ist unumgänglich. Sie durchzuführen hat nichts Spießiges oder Spaßverderberisches an sich, sondern ist schlicht dem Anspruch eines Dojos geschuldet. Wenn Lernen stattfinden soll, dann ist Reflexion/Feedback nötig.

Hierüber stolpern die meisten Dojos. Niemand mag sich recht zum Moderator einer Reflexion aufschwingen oder die Zeit ist leider schon um. Und so gehen die Teilnehmer immer wieder nach Hause, ohne zu wissen, ob und was sie denn nun gelernt haben.

Eine Besprechung der Aufgabe ist übrigens nicht genug der Reflexion. Es geht vor allem um den kritischen Blick auf die Lernziele. Wie ist man mit TDD umgegangen, wie war das mit Pair Programming oder wie hat man die Technologie X eingesetzt oder die Methode Y?

Und natürlich soll die Reflexion auch auf der Metaebene stattfinden: Wie ist es mit dem Dojo gelaufen? Hat es seinen Zweck erfüllt? Wie war die “Performance” des Veranstalters?

Wo die Reflexion fehlt, da wird ein Dojo zur “Pseudeowissenschaft”, d.h. es wird immun gegen Kritik, da es keinen Rahmen für die Falsifizierung seiner These gibt, die da lautet, dass es ein “lockeres Mittel zum Training und Erfahrungsaustausch” sei, mit dem Lernen zu einem Fest werden kann.

Unterm Strich würde ich sagen: Jedes Coding Dojo ist ein gutes Coding Dojo, wenn es diese drei Bestandteile (oder Phasen) aufweist. Die Kata ist egal, das Entertainment kann minimal oder großartig sein, der Rahmen eine Konferenz oder das Treffen von Kollegen nach Dienst. Aber ohne klare Aufgabe, klares Lernziel und Reflexion am Ende geht es nicht.

Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat…

Sonntag, 22. Mai 2011

AppKata – Enter the next level

imageWer schon alle CodeKata Übungen durch hat – Kata Potter, Bowling, LOC, Poker, Fizz Buzz und wie sie alle heißen –, der kann jetzt eine neue Herausforderung annehmen: eine AppKata.

AppKatas – kurz für Application Kata – sind Übungsaufgaben wie CodeKatas, nur umfangreicher. CodeKatas stellen vor allem überschaubare algorithmische Probleme. Da ist typischerweise ein kleiner API zu entwickeln (wenn er nicht schon vorgegeben ist wie z.B. bei der Alpha-End Kata) und mit Code zu unterfüttern. Die Lösung zu finden ist zwar manchmal knifflig, allemal wenn man sie dann z.B. strikt mit TDD implementieren will – doch am Ende ist eben auch nicht mehr dran an einer CodeKata.

Das ist auf der einen Seite gut, weil CodeKatas deshalb recht zügig zu lösen sind. Mit 1-2 Stunden ist man dabei. Das ist überschaubarer Aufwand, der (hoffentlich) mit einer lauffähigen Lösung belohnt wird.

Auf der anderen Seite ist das aber beschränkend. Mehr als etwas Codieren und Testen kann man kaum üben. Wer andere Skills verbessern will, hat an CodeKatas nur magere Kost. Die mit Komponentenorientierung umzusetzen, wäre künstlich. Dafür einen automatischen Build aufzusetzen, wäre mit Kanonen auf Spatzen schießen. Architektur oder Modellierung – wozu?

Das sind aber auch alles Fähigkeiten, die man verbessern kann. Warum immer nur automatisiertes Testen üben? Im Team eine ganze Anwendung entwickeln: das ist eine Herausforderung, die den ganzen Entwickler braucht.

Aus diesem Grund haben Stefan Lieser und ich nun beschlossen, aus unserem Übungsfundus der Clean Code Developer School zu schöpfen und einige der Aufgaben als AppKatas zu veröffentlichen. Das sind immer noch recht überschaubare Übungen, bei denen es nicht um spezielle Technologien geht, sondern die Herangehensweise. Doch es sind Aufgaben, die zu “echten Anwendungen” führen. Kleinen Anwendungen, aber eben ganzen Anwendungen mit Frontend und Ressourcenzugriff usw.

Für die wird man länger brauchen als für CodeKatas, aber das macht nichts. Sie müssen ja nicht in einem Rutsch gelöst werden. Oder man konzentriert sich nur auf einen Aspekt; zum Beispiel könnte man die Modellierung herausgreifen und auf das Codieren verzichten. Je nach Zeit und Anspruch.

Für die AppKatas haben wir eine eigene Seite eingerichtet auf unserer Clean Code Advisors Homepage. Dort werden in den nächsten Wochen in kleinen Happen AppKatas erscheinen. Auch das gehört zur AppKata: die Präsentation in Iterationen. Die AppKata Aufgabe wird nie “in einem Stück” dargestellt, sondern in mehreren Blöcken, die aufeinander aufbauen. Mit jedem Block kommen Anforderungen hinzu.

Wer es also richtig ernst meint mit dem Üben, der liest immer nur die Anforderungen einer Iteration, implementiert sie und schreitet dann erst zu den Anforderungen der nächsten Iteration weiter. So stellt man die Evolvierbarkeit seines Code wirklich auf die Probe.

Die AppKatas sind für größtmögliche Allgemeingültigkeit formuliert. Wir bemühen uns, keine Plattformabhängigkeiten zu haben. Jeder soll sich mit seiner Programmiersprache daran üben können, von Ruby über C# und Java bis zu Haskell.

Und die AppKatas werden wohl meist in Englisch formuliert sein, um auch international zum Üben anregen zu können. Aber das sollte für niemanden in DACH eine Hürde darstellen – oder?

Also, auf geht´s! Wer macht mit? CodeKatas sind für Anfänger; AppKatas sind für Profis ;-) Da ist der ganze Entwickler, die ganze Entwicklerin gefragt – oder auch das ganze Coding Dojo Team. Hier gehts los…

Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat…

Montag, 22. November 2010

Vom Nutzen der Code Kata für das Entwicklerleben

imageGrad gibt es ja mal wieder Diskussion um eine Code Kata, die Kata Tennis. Siehe u.a. die Kommentare hier und hier. Anlass war ein online Coding Dojo der Online .NET User Group. Da gehen die Meinungen darüber, wie man am besten zu einer Lösung der Aufgabe kommt, nun auseinander. Das ist gut so. So kommen wir alle weiter, das macht grundsätzlich Spaß.

Allerdings hat die Diskussion für mich auch unbefriedigende Züge. Sie wird nämlich mühsam, wenn wir uns nicht auf technische Aspekte konzentrieren können, weil uns unterschiedliche Ansichten zur Form im Wege stehen, genauer sogar: unterschiedliche Ansichten zum Zweck von Code Katas.

Was soll das also mit den Code Katas?

Nutzen #1: Lernen

Code Katas sind Übungsaufgaben. An ihnen soll man irgendwas lernen. Sie bieten ein überschaubares Problem ab vom Projektalltag, an dem man Techniken, Konzepte, Tools, Vorgehensweisen ausprobieren kann, ohne Angst vor Fehlern haben zu müssen. Feedback stellt sich schnell ein, weil die Aufgaben klein sind. Hat man eine Lösung zum Laufen gebracht? Hat man die Technik, das Tool, die Vorgehensweise mit Gewinn eingesetzt? Das lässt sich in wenigen Stunden herausfinden. Und wenn es Differenzen zwischen Wunsch und Wirklichkeit gibt, kann man es dann nochmal probieren mit derselben oder einer anderen Kata.

Nutzen #2: Spaß

Code Katas sind von Thema und Umfang so geschnitten, dass es Spaß macht (oder zumindest machen sollte), sich mit ihnen allein oder in Gemeinschaft zu beschäftigen. Da ist für jeden etwas dabei. Ein bisschen Knobeln, aber nicht zuviel. Der Spaß entsteht durch Herausforderung auf einem für jeden selbst wählbaren Problemniveau mit Feedback in absehbarer Zeit.

Lernen mit Spaß: Darum gehts bei Code Katas, würde ich mal sagen. Nicht mehr und nicht weniger.

imageDas ist übrigens nicht neu. Solche kleinen Aufgaben gibt es schon seit Jahrzehnten. Die pragmatischen Programmierer haben sie also nicht erfunden. Da gibt es die legendären Programming Pearls von Jon Bentley. Oder den Bundeswettbewerb für Informatik, den International Collegiate Programming Contest der ACM oder hier eine wettbewerbsübergreifende Sammlung von Aufgaben.

Wer Übungen sucht, um seine Programmierfertigkeiten zu trainieren, der findet also reichlich Material. Wie wäre es z.B. mal mit einem APL Interpreter oder Barcodeerkennung statt der ewigen Fizzbuzzpotterbankocrtennisaufgaben? Zufinden hier. Vielleicht ist da dann auch mal was dabei, was nicht so trivial ist, dass wir ewig darüber diskutieren müssen, ob TDD allein ausreicht für eine Lösung. Denn am Ende sind die bisher im Umlauf befindlichen Katas eher so klein, dass man mit oder ohne spezielle Methoden und Konzepte zu einer lauffähigen Lösung kommt.

Übrigens: Wer Spaß am Knobeln und am Wettbewerb hat, kann mit “Code Katas” sogar Geld verdienen. Einfach mal bei TopCoder vorbeischauen.

Freiheit der Form

Womit wir bei der Frage sind, was denn unverbrüchlich zu Code Katas gehört, um den Nutzen zu erreichen? Gehört TDD dazu? Gehört Modellierung dazu? Gehört eine Gruppe dazu?

Ich würde sagen, nichts davon ist zwingend. Code Katas – anders als die anderen o.g. Aufgaben – sind zwar traditionell verbunden mit TDD, doch das halte ich nicht für unverbrüchlich. TDD zu üben, war mal ein Ausgangspunkt für bestimmte Leute zu einem bestimmten Zeitpunkt. Doch sich daran nun zu klammern, finde ich kontraproduktiv und dogmatisch.

Für mich ist daher die Durchführung einer Code Kata völlig frei in der Wahl der Form. Man kann sie allein oder zu mehreren im Coding Dojo bearbeiten. Man kann mit TDD oder F# oder UML oder auch ganz anders dran arbeiten. Nur der Nutzen sollte am Ende entstehen.

Minimaler Rahmen

Damit ein Nutzen sichergestellt ist, halte ich zweierlei dann aber doch für zwingend:

  1. Vor Beginn der Code Kata ist festzulegen, was genau gelernt werden soll. Lernziel und Erwartungshorizont sind zu definieren. Soll TDD geübt werden? Soll das Modellieren geübt werden? Soll Modellieren + TDD geübt werden? Soll die Lösung mit UML entworfen werden? Soll BDD geübt werden? Soll die Formulierung der Lösung in F# geübt werden ganz ohne Tests? Soll besonders auf SOLID oder KISS oder LOC geachtet werden? Egal. Es muss nur festgelegt werden. Ein für die Code Kata gültiger Grundsatz, ein Lernziel ist zu finden.
  2. Auf die Kata folgt eine Reflexion. In der wird besprochen, inwiefern der Nutzen realisiert wurde. Hatten alle Spaß? Wurden die vorher festgelegten Lerninhalte zielstrebig verfolgt? Was wurde davon gelernt? Wo gab es Schwierigkeiten? Wo wurde Unerwartetes gelernt?
Reflexion der Diskussion

Wenn ich diesen minimalen Rahmen für Code Katas mal in Anschlag bringe, sehe ich die Diskussion um die Kata Tennis ein wenig mit anderen Augen. Teile der Diskussion scheinen mir nun um ein unausgesprochenes Missverständnis zu gehen: die Lerninhalte der Diskutanten.

Über den Nutzen von Code Katas sind wir uns alle einig, glaube ich. Aber bei den Lerninhalten gehen unsere Meinungen auseinander.

Da gibt es manche, die vor allem eine Lösung finden wollen. Mehr oder weniger egal wie. Hauptsache, am Ende läuft es. Andere sind auch zufrieden, wenn keine lauffähige Lösung entsteht, es aber Spaß gemacht hat und irgendwas vorher nicht Spezifiziertes gelernt wurde. Wieder andere wollen vor allem TDD üben. Und noch andere wollen ein umfassenderes Vorgehen üben, das womöglich für die trivialen Code Katas überkandidelt erscheinen mag.

Ich denke, in zukünftigen Diskussionen über Code Katas sollten wir uns klarer darüber werden, was unsere Lerninhalte sind. Wir sollten dann Lösungen einfach so kritisieren, bei denen ein anderer als unser Lerninhalt im Vordergrund stand. “Warum hast du nur TDD benutzt?” ist nur eine valide Kritik, wenn z.B. “Softwareentwicklung üben” der Lerninhalt war, nicht jedoch wenn der lautete “TDD üben”. Kritik sollte sich entweder auf das Lernziel direkt beziehen, “Warum übst du A und nicht B?” Oder sich innerhalb des Lernziels bewegen, “Ich würde zwar B üben statt A, aber wenn du schon A verwendest, dann solltest du es anders machen.”

Vor meiner Kritik anderer Tennis-Lösungen hätte ich also z.B. erst fragen sollen, “Was war dein Lernziel, Björn?” Wenn er dann geantwortet hätte, “Ich wollte nur TDD üben.” oder “Ich wollte das State-Pattern üben.”, dann wäre meine weitere Agrumentation anders verlaufen. Dann wäre sie weniger Kritik gewesen als vielmehr schlichte Beschreibung.

So aber hatte ich angenommen, Björns Ziel sei dasselbe wie meines gewesen: “Üben, eine Lösung für ein Programmierproblem zu finden im Rahmen eines systematischen Vorgehens”. Da für mich zum systematischen Vorgehen auch Modellierung gehört, die bei Björn und anderen aber nicht sichtbar war, habe ich Kritik statt schlichter Beschreibung geäußert.

Nach ein wenig mehr Nachdenken weiß ich das nun. Zukünftig werde ich deshalb versuchen, zuerst mehr über die Ziele anderer herauszufinden. Wenn ich die kenne, kann ich entweder bewusst kritisieren, wenn ich anderer Meinung bin, oder eben nur Alternativen beschreiben. Das können alternative Ziele sein oder alternative Wege zum selben Ziel. Oder beides. Soviel als Beitrag von mir für heute zu mehr Harmonie und Frieden auf dem Entwicklererdball… :-)

Samstag, 20. November 2010

Wider die Patternmania

Heute morgen habe ich hier im Blog meinen Ansatz für die Kata Tennis beschrieben. Den kommentierte Björn Rochel mit

“Warum so abstrakt? Warum so viel Zeremonie? Eine Alternative wäre bsp. das State-Pattern. Finde ich persönlich deutlich einfacher und lesbarer.”

Nach meinem Antwortkommentar bin ich daraufhin aber nicht recht zur Ruhe gekommen. Sein Einwand hat an mir genagt; ich fand meine Entgegnung noch nicht fundiert genug. Nun kann ich meine Position aber besser formulieren, glaube ich. Für einen Kommentar ist sie mir allerdings zu wichtig. Also ein weiterer Blogartikel.

Zu abstrakt?

Ist ein Zustandsautomat zu abstrakt? Dass er abstrakt ist im Vergleich zu Code, ist klar. Aber ist er zu abstrakt, d.h. unnötig abstrakt? Gibt es also ein absolutes, für alle Entwickler der Kata Tennis bestes Abstraktionsniveau, auf dem sie denken sollten? Ich behaupte, nein.

Für mich einfachen Sterblichen ist ein Zustandsautomat zur Beschreibung der Tennis Zählregeln nicht zu abstrakt, sondern gerade richtig. Wenn ich die irgendwie formalisieren will, um eine bessere Übersicht zu bekommen, ist ein Zustandsautomat sehr, sehr praktisch. (Ob ich den grafisch entwerfe oder als Tabelle, ist egal.)

Was wäre denn auch die Alternative? Eine Reihe von Wenn-Dann-Regeln? Das kann Björn doch nicht wirklich meinen.

Meine Vermutung ist eher: Björn ist Tennisprofi und atmet die Regeln täglich, deshalb muss er darüber nicht mehr nachdenken. Deshalb findet er einen Zustandsautomaten zu abstrakt.

Dagegen halte ich, dass es nicht darum geht, ob ein Entwickler meint, Anforderungen verstanden zu haben, sondern er muss das dem Auftraggeber – auch einen imaginierten wie bei einem Dojo – demonstrieren. Als Entwickler müssen wir also darlegen können durch eine eigene Beschreibung der Anforderungen, dass wir wissen, worum es geht. Zum Anforderungstext zu nicken, ist zu wenig.

Wie hat Björn das aber dargelegt? Keine Ahnung. Sein Code bei github gibt darüber keine Auskunft. Wie hätte ich nach Darlegung der Anforderungen in Björn Vertrauen haben können, dass er korrekten Code schreibt? Weil er nach TDD vorgeht? Kaum. Das interessiert mich nämlich als Auftraggeber nicht. Vertrauen hätte aufbauen können, dass Björn mir in seinen Worten und/oder mit einer eigenen (abstrakteren) Darstellung widerspiegelt, was er verstanden hat. Meine Zustandsautomatengrafik ist so eine Darstellung, finde ich. Die könnte sogar ein Auftraggeber verstehen; aber wenn nicht, dann machts auch nichts. Dann kann ich mit dem Automaten in der Hand dem Auftraggeber erklären, wie ich danach die Regeln verstehe.

Ergo: Zu abstrakt finde ich den Zustandsautomaten nicht, sondern als Beschreibung meines Verständnisses der Anforderugen unabhängig vom Code absolut nötig.

Zu viel Zeremonie?

Nicht nur soll der Zustandsautomat zu abstrakt sein, nein, er soll auch zu zeremoniell sein. Damit meint Björn wohl, dass er unabhängig von der Beschreibung unnötig ist und nicht zum Kern der Lösung gehört.

Das verstehe ich nicht. Was ist an einem Zustandsautomaten unnötig für die Lösung? Er stellt vielmehr den Kern der Lösung dar. Er formalisiert das Tennis Zählregelwerk. Er fasst an 1 Ort zusammen, wie es geht. Auf dem Papier formuliert er kurz und knapp die Essenz der Zählung. Und in Code umgesetzt braucht er ganze 5 Zeilen

private readonly int[,] _stateTransitions = new[,] {
                           
  {1,2,3,6,5,6,-1,4,-1},
                              {0,1,2,4,7,4,-1,8,-1} };
internal int _currentState;

var transition = position == ScoringPositions.YouWin ? 0 : 1;
_currentState = _stateTransitions[transition, _currentState];

Ich bitte um Erhellung, wie das Regelwerk der Zählung knapper und auch übersichtlicher und auch leichter anzupassen hätte gefasst werden können. Björn braucht in seinem Code, der ohne Modellierung auskommt, immerhin mindestens 57 Zeilen und weitere, die ich auf die Schnelle nicht erkenne. Ist das ein Vorteil von weniger Zeremonie? Eine Zehnerpotenz mehr Codezeilen und weniger Verständlichkeit durch Verteilung der Verantwortlichkeit für die Zählung auf mehrere Klassen? Hm… ich bin im Zweifel.

Zu wenig Pattern?

Und schließlich eines meiner Lieblingsargumente – vor allem aus der Java-Ecke gehört: “Was du da machst, folgt nicht dem Pattern XYZ. Das ist nicht gut.” Oder in der allgemeineren Form: “Was du da machst, ist nicht wirklich objektorientiert.” Da muss ich immer schmunzeln. Als ob mehr Patterns oder mehr Objektorientierung irgendein Qualitätskriterium seien…

Patterns und Objektorientierung sind nur Mittel zu einem Zweck. Die Frage muss also immer sein: Wird ein Zweck mit einem Pattern oder der Objektorientierung am besten erreicht?

Diese Frage stelle ich an Björn:  Bist du wirklich der Meinung, dass du durch Befolgen eines Patterns, das dich 57+ Zeilen kostet – im Gegensatz zu meinen 5 Zeilen – irgendwie den Zweck “Tennis Zählregeln implementieren” besser erreichst?

Das kann ich nicht glauben.

Mein simpler “Beweis”: Wenn sich an der Zählregel etwas ändert, dann habe ich genau 1 Eingriffspunkt, nämlich die Tabelle mit den Zustandsübergängen. Ich muss mich also nur auf 2 Zeilen Code konzentrieren. Du hingegen müsstest schauen, ob eine oder mehrere deiner State-Klassen verändert werden müssten oder gar eine neue hinzukommen muss.

Und nun noch fundamentaler: Patterns und Objektorientierung sind Implementationsdetails. Ob und wie ich sie einsetze muss für die Lösung relativ egal sein. Denn die Lösung sollte nicht allein und schon gar nicht sofort in Code formuliert werden. C# ist auch ein Implementationsdetail.

Stattdessen – wie könnte es anders sein ;-) - sollte die Lösung zuerst modelliert werden. D.h. sie sollte unabhängig von Implementationsdetails formuliert werden. Keine Klassen, kein State-Pattern, keine Arrays… Genau das habe ich getan. Ich habe mir sprachunabhängig Gedanken gemacht, wie die Lösung aussehen könnte. Ein Zustandsautomat ist nicht an C# gebunden und das Datenflussdiagramme auch nicht.

Und erst als ich auf Modellebene zuversichtlich war, die Lösung formuliert zu haben, habe ich mich an die Übersetzung gemacht. Da sind dann 5 Zeilen für den Zustandsautomaten rausgekommen und ca. 60 Zeilen für die gesamte Logik – also knapp nur 50% der LOC bei Björn. Die habe ich sehr wahrscheinlich dann auch schneller getippt und schneller getestet.

Schneller und einfacher geändert sind sie auch, da es erstens viel weniger Funktionseinheiten (Klassen, Methoden) gibt und zweitens deren Verantwortlichkeiten nicht weniger klar sind als bei Björn.

imageNebenbei: Wer meint, meine Lösung enthalte zu wenig Pattern, der schlage in der Literatur nach. Dort wird man finden, dass ein Zustandsautomat das um Jahrzehnte ältere Pattern im Vergleich zum State-Pattern und jedem anderen Designpattern ist. Ich habe also sehr wohl auf Patterns gesetzt; nur eben nicht die seit 15 Jahren hippen Entwurfsmuster. Entwurfsmuster entlasten also nicht von der Mühe, sich mit Modellierung auseinanderzusetzen. Eine Leseempfehlung dazu: “Modellierung” von Kastens und Büning.

Fazit

Kritik der Art “zu viel von …” oder “zu wenig von …” finde ich wenig hilfreich. Sie ist letztlich nur eine Gefühlsäußerung und nicht argumentbasiert. Denn Argumente beziehen sich auf eine Differenz zwischen Zweck und Mittel. Die habe ich aus Björns Kritik nicht wirklich herauslesen können.

Aber ich bin offen: Wer mag, kann mir darlegen, warum meine gewählten Mittel Modellierung im Allgemeinen, Zustandsautomat und EBC-Diagramm im Besonderen und ihre Übersetzung in Code die Zwecke Testbarkeit und Evolvierbarkeit weniger gut erfüllen als eine andere Herangehensweise.

Spiel, Satz, Sieg fürs Nachdenken

imageGerade hat die .NET Online User Group die Kata Tennis beim online Coding Dojo bearbeitet. Leider konnte ich nicht teilnehmen. Da in Twitter dazu aber noch anschließend diskutiert wurde, habe ich mir gedacht: Warum nicht die Aufgabe nachträglich angehen?

Meine Lösung liegt hier in meinem Mercurial Google Repository. Anders als im Coding Dojo bin ich jedoch nicht streng nach TDD vorgegangen. Deshalb ist die Struktur der Implementation anders als bei den Dojo-Teilnehmern und auch meine Tests sehen anders aus.

Zum Hintergrund meiner Lösung an dieser Stelle daher ein paar kurze Worte.

1. Der API

Bevor ich mit der Implementation losgelegt habe, habe ich über die Lösung nachgedacht. Wie könnte die aussehen? Nicht nur an der “Oberfläche”, Stichwort API, sondern auch darunter.

Der API – der immer explizit vor Codierungsstart festgelegt werden sollte, wie ich meine – sieht so aus:

var g = new Game();
g.AddScore(Players.Player1);
g.AddScore(Players.Player2);
g.AddScore(Players.Player1);

Console.WriteLine(g.Winner);

Bei mir geht es also nur darum festzustellen, ob durch einen Ballsieg ein Gewinn eingetreten ist und wer der Gewinner ist. Die Aufgabenstellung der Kata Tennis lässt diese Interpretation zu. Dort ist nämlich von gar keinem API die Rede; es sollen lediglich “irgendwie” die Regeln implementiert werden.

2. Das Modell

Ausgehen vom API habe ich mir Gedanken gemacht, wie denn so ein Tennisspiel intern überhaupt repräsentiert werden könnte. Sofort fiel mir da ein Zustandsautomat ein. Die Zustände sind die Spielstände eines Spielers, die Übergänge ergeben sich aus Gewinn eines Balls bzw. ob ein Ball verloren wurde (rot). Hier meine Skizze, die ich auf meinem iPad gemacht habe:

image

So ein Zustandsautomat ist natürlich eine sehr abstrakte Funktionseinheit. Wo und wie läuft der denn im Zusammenhang, so dass er vom API genutzt wird? Das habe ich mit einem kleinen EBC-Diagramm für den AddScore() API-Aufruf modelliert:

image

Der Spieler, der einen Ball gewonnen hat, fließt hinein und wird übersetzt in eine Position für jeden der beiden Spieler. Beide Spieler sind repräsentiert durch ein Objekt (Player), das ihren Zustand hält. Dort läuft der Zustandsautomat. Der liefert nach einer Transition den neuen Spieler-Spielstand an ein abschließendes Bauteil, das ermittelt, wer gewonnen hat.

Beide Skizzen zusammen haben mich, hm, 10-15 Minuten gekostet. Der Zustandsautomat hatte daran den Löwenanteil, würde ich sagen. Er diente ja aber nicht nur der Lösungsmodellierung, sondern auch noch dem Anforderungsverständnis.

Am Ende der Modellierung kannte ich dann alle relevanten Funktionseinheiten und konnte loslegen – und zwar wo ich wollte. Denn die Funktionseinheiten sind ja durch EBC wunderbar entkoppelt.

3. Implementation

Die Implementation habe ich mit den beiden kleinsten Funktionseinheiten begonnen: Ballgewinner in Position übersetzen und Gewinner aus den Spieler-Spielständen ermitteln. Die Herausforderung Zustandsautomat hab ich also an den Schluss gelegt.

Die ersten beiden Funktionseinheiten waren so klein, dass ein Test-first Ansatz nicht nötig war. Ich habe sie deshalb einfach runtergeschrieben und danach Tests geschrieben. Das war ohne Verlust an Korrektheit befriedigender.

Test-first/TDD sollte ja kein Dogma sein. Wenn die Zwecke von TDD anders/leichter erreicht werden können, dann sollte man den leichteren Weg gehen. Und was sind die Hauptzwecke von TDD? 1. Code in überschaubare, leicht testbare Einheiten strukturieren; 2. Eine gute Testabdeckung sicherstellen. Beides habe ich durch die Modellierung erreicht. Denn die hat zu kleinen Funktionseinheiten geführt – ohne später refaktorisieren zu müssen –, die testbar sind und die ich mit wenigen Tests gut abdecken kann – auch im Nachhinein.

Bei der Implementation des Players habe ich dann jedoch nach Test-first gearbeitet. Viel herausgekommen ist dadurch jedoch nicht ;-) Denn auch der Player ist am Ende so einfach mit dem Zustandsautomaten, dass sich eine weitere Zerlegung nicht lohnt:

internal class Player
{
    private readonly Dictionary<ScoringPositions,
                                Dictionary<Scores, Scores>>
                                _stateTransitions =
        new Dictionary<ScoringPositions, Dictionary<Scores, Scores>>
            {
                {ScoringPositions.YouWin,
                    new Dictionary<Scores, Scores>
                    {
                        {Scores.Love, Scores.Fifteen},
                        {Scores.Fifteen, Scores.Thirty},
                        {Scores.Thirty, Scores.Forty},
                        {Scores.Forty, Scores.Win},
                        {Scores.Deuce, Scores.Advantage},
                        {Scores.Advantage, Scores.Win},
                        {Scores.Win, Scores._Invalid},
                        {Scores._Advantage, Scores.Deuce},
                        {Scores._Win, Scores._Invalid},
                    }},
                {ScoringPositions.YouLoose,
                    new Dictionary<Scores, Scores>
                    {
                        {Scores.Love, Scores.Love},
                        {Scores.Fifteen, Scores.Fifteen},
                        {Scores.Thirty, Scores.Thirty},
                        {Scores.Forty, Scores.Forty},
                        {Scores.Deuce, Scores._Advantage},
                        {Scores.Advantage, Scores.Deuce},
                        {Scores.Win, Scores._Invalid},
                        {Scores._Advantage, Scores._Win},
                        {Scores._Win, Scores._Invalid},
                    }},    };
    internal Scores _currentState;
 
    public void Adjust_score(ScoringPositions position)
    {
        _currentState = _stateTransitions[position][_currentState];
        this.Out_CurrentScore(_currentState);
    }

 
    public void In_Deuce()
    {
        _currentState = Scores.Deuce;
    }
 
    public Action<Scores> Out_CurrentScore;
}

Etwas zusammenreißen musste ich mich vielmehr bei den Tests. Ich war schon dabei, möglichst viele Zustandsübergänge zu testen, als ich merkte, dass ich damit “Feedback” verzögerte. Also habe ich nur ein paar essenzielle Transitionen geprüft und mich dann an die Integration aller Bauteile gemacht.

Damit konnte ich dann schon “beweisen”, dass das Gesamtmodell grundsätzlich funktioniert. Zwei Szenarien zeigen Funktionsweise und Umgang mit dem API. Hier zum Beispiel ein Spiel mit Gewinn nach Tie Break:

[Test]
public void Game_with_a_tie_break()
{
    var sut = new Game();
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.Player1, sut.Winner);
}

Fazit

Ich bin zufrieden mit meinem Vorgehen. Erst modellieren – ja, auch in so kleinen Szenarien – und dann moderat testgetrieben implementieren hat mich schnell, flexibel und sicher gemacht. Alle Funktionseinheiten waren überschaubar und gut testbar. Ich konnte mich auf die Implementation konzentrieren, ohne immer wieder über Refaktorisierungen nachdenken zu müssen.

Es scheint mir ein Nachteil von TDD zu sein, die Modi Implementation und Refaktorisierung so zu verquicken. In der Schrittfolge red-green-refactor sind sie zwar getrennt, doch da diese Schleife immer wieder und schnell in kleinen Schritten durchlaufen werden soll, verschmelzen Implementation (red-green) und Refaktorisierung wie die Einzelbilder eines Films. Als Entwickler muss ich schwebende Aufmerksamkeit für beide haben.

Bei meinem Vorgehen hingegen sind beide Modi klar getrennt. Ergebnis der Modellierung sind Funkionseinheiten, die zunächst mal nicht mehr refaktorisiert werden müssen. Solange ich die implementiere, kann ich mich auf die Implementation konzentrieren. Das entlastet mich mental. Wenn ich damit dann fertig bin – z.B. nach einer Stunde wie in diesem Beispiel –, dann kann ich mich zurücklehnen und den Modus wechseln. Das empfinde ich als Entspannung und wertvolle Phase der Reflexion. Dann habe ich die “Ruhe des Erfolgs” in mir, weil ich ja schon eine Menge geschafft habe.

Ergo: Ich finde weiterhin, dass Implementierung ohne Modellierung harte Arbeit ist und nicht smarte. Man kommt damit auch zum Ziel – aber warum so anstrengen, wenn es auch einfacher geht? TDD ist ne gute Sache – in Maßen. Es ist keine Silberkugel, sondern nur eine Methode unter vielen, die im Zusammspiel zur Produktion von Code genutzt werden sollten. Und somit empfände ich es als künstliche oder gar unrealistische Reduktion, wenn ein Coding Dojo sich nur auf die Anwendung von TDD zur Lösungsfindung beschränken würde.

PS: Danke an Krzysztof Eraskov für seine Hinweise auf Fehler in der Implementierung und Inkonsistenzen im Modell.