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.