Follow my new blog

Sonntag, 23. August 2009

Was ist mit Bob? – Verwirrung anlässlich einer Code Kata

image Wollte mich heute mal zur Entspannung mit einer Code Kata beschäftigen. Meine Wahl fiel auf die "Bowling Game Kata" von unser aller Onkel Bob. Das PPT dazu sieht hübsch strukturiert aus:

  • Am Anfang werden die Regeln für die Punktezählung bei einem Bowling Game erklärt. Da besteht ein Spiel z.B. aus 10 Sätzen (Frame), innerhalb derer mehrere Würfe (Roll) gemacht werden dürfen, um zu einem Punktestand (Score) für den Satz zu kommen. Die Gesamtpunktezahl für ein Spiel ist dann die Summe der Punkte der Sätze.
  • Dann wird eine Sollklasse als Anforderung formuliert. Die ist in der Kata per TDD zu implementieren. Ich formuliere sie mal als Interface für C#:

image interface IGame
{
    void Roll(int pins);
    int Score { get; }
}

 

Mit den Regeln und der formalen Anforderung in der Tasche kann es dann losgehen.

Oder man linst mal auf die weiteren Folien der Katabeschreibung - immerhin sind es mehr als 50. Da erklärt Meister Bob nämlich sein Vorgehen.  Aber, wer hätte das gedacht: Statt mit TDD loszulegen, macht Onkel Bob eine Design Session!

image

imageWas ist mit Bob? Da gibt es nicht nur eine Klasse Game, sondern auch eine Klasse Frame und Roll. Hört sich ja auch plausibel an, wenn man die Regeln liest. Darin tauchen diese Begriffe als Substantive auf. Aber warum müssen die denn in ein Design einfließen? Ist es ausgemacht, dass man sie für die Implementation wirklich braucht?

Oder besser: Ist es aus den Anforderungen ablesbar, dass diese Klassen gebraucht werden? Ich glaube, das kann man nicht. Denn ich habe meine Implementation der Anforderung “Implementiere eine Klasse Game wie folgt…” strickt mit TDD begonnen. Für so ein kleines Beispiel habe ich schlicht keine ausdrückliche Design Session für nötig gehalten. Und ich habe auch gedacht, genau das sei eben der Sinn solcher kleinen Katas: dass man eben vor allem den TDD-Prozess einübt mit seiner Schrittfolge red-green-refactor. Das Design soll iterativ und absolut bedarfsgetrieben evolvieren. Und dann sowas von Bob?!

Bei meinem TDD-Vorgehen konnte ich keine Notwendigkeit erkennen, Klassen jenseits von Game zu implementieren. Den Grund halte ich für ganz naheliegend: Die Anforderung (!) enthält aber auch gar keinen Bezug zu Sätzen und Würfen.

Die Regeln sagen zwar, dass man pro Satz nur soundsoviele Würfe machen darf und bestimmte Wurferfolge Zusatzwürfe (Bonus) gestatten. Doch die Punktezählung ist am Ende nur eine schlichte Addition aller Wurferfolge (Pins).

Darüber hinaus abstrahiert die Klasse Game von all diesen Details, indem auf ihr einfach immer nur wieder Roll() aufgerufen werden soll:

game.Roll(5);
game.Roll(3);
game.Roll(7);
game.Roll(9);

Console.WriteLine(game.Score);

image Was, bitte, hat das noch mit Sätzen zu tun? Ob ein Wurf ein Bonuswurf ist, entscheidet nicht die Klasse Game. Das steht jedenfalls nicht in den Anforderungen. Auch ist nicht erwähnt, ob irgendwie die Einhaltung der Regeln geprüft werden soll. Oder warum die Einschränkung, Score nur am Ende aufzurufen? Es macht keinen Unterschied, wann man Score befragt, da die Gesamtpunktezahl immer nur eine Addition aller per Roll() gemeldeten Wurfergebnisse ist.

Was ist also mit Bob? Wie kommt es, dass er eine so merkwürdige Aufgabe stellt und sich letztlich nicht an die eigenen Prinzipien hält? Explizites Design statt TDD – was hat ihn bei der Aufgabengröße denn da geritten?

Oder habe ich da etwas übersehen, falsch verstanden? Ist mir in der Spieldefinition etwas entgangen? Ist mir der tiefere Sinn der Anforderung an die Klasse Game entgangen? Ich bitte um Aufklärung.

Empfehlung für Katas

Egal, ob ich etwas nicht richtig verstanden habe oder in der Aufgabenstellung der Wurm ist, ich denke, eine Lehre lässt sich in jedem Fall ziehen: keine Code Kata ohne Akzeptanztests!

Alles wäre leichter, wenn Bob der Kata eine Datei in einem einfachen Format beigegeben hätte, die Sollergebnisse enthält. Schon folgendes hätte gereicht:

7,2,5,4,…,103
3,4,10,7,…134

Wobei jede Zahl einen Wurf repräsentiert und die letzte den Score.

Wer also Code Katas sucht, der sollte darauf achten, dass ihr “Abnahmetests” beiliegen. Und wer Code Katas beschreiben will, dem sei empfohlen, ein bisschen Mühe auf die Definition von “Abnahmetests” zu verwenden. Sie steigern den Wert der Code Kata – oder machen sie erst überhaupt durchführbar.

Kommentare:

Thomas Vidic hat gesagt…

Hallo Ralf,

soweit ich die Regeln auf der zweiten Folie verstehe gibt es Bonuspunkte für Spare und Strike. Daher ist die Gesamtpunktzahl für ein Spiel u.U. mehr als die Summe der einzelnen Würfe.

Übrigends kommt auch Martin in seiner Implementierung ohne Frame oder Roll Klasse aus, ich würde das UML-Diagram (obwohl die Folien das so nicht sagen) daher eher als "Analysemodell" statt als Modell für das Programmdesign sehen.

Grüße,
Thomas

Mario hat gesagt…

Hallo Ralf,

wie Thomas schon geschrieben ist die Gesamtpunktzahl nicht nur die Summe der Würfe, sondern etwas komplexer zusammengesetzt. Das wird aber auch so weit von der Spielbeschreibung auf Seite 2 dargestellt. Der von dir vermisste Akzeptanztest ist eigentlich auch vorhanden und zwar verbirgt er sich auch auf der zweiten Seite direkt unter der Überschrift. Dort ist ein komplettes Spiel mit den Würfen und den Punktzahlen abgebildet. Für jemanden, der noch nicht gebowlt hat, ist die Darstellung aber nicht unbedingt sofort und selbsterklärend als solche erkennbar.

Das ist wieder ein schönes Beispiel dafür, welch große Bedeutung Kontext allgemein und in diesem Fall kultureller Kontext haben. In den Staaten gibt es wahrscheinlich nicht sehr viele, die das nicht sofort als Bowlingspielblatt identifiziert hätten.

Ralf Westphal - One Man Think Tank hat gesagt…

@Thomas, Mario: Aus der Spielbeschreibung erkenne ich nicht, dass die Gesamtpunktzahl "komplexer zusammengesetzt" ist. Ja, es gibt Bonuswürfe nur in bestimmten Zusammenhängen, aber es sind eben auch nur Würfe, die mit Roll() gemeldet werden. An Roll() kann man nicht ablesen, dass ein Wurf ein Bonuswurf ist und auch nicht, dass der nächste einer sein sollte. Das verwirrt mich und ich denke, ich übersehe etwas.

Beispiel:

Roll(4), Roll(5), Roll(10), Roll(1), Roll(2) = 2 Sätze mit 22 Punkten.

Roll(4), Roll(5), Roll(3), Roll(6), Roll(4) = 3 Sätze mit 22 Punkten.

Es werden also einfach nur die Punktezahlen addiert. Oder?

Auf Seite 2 findet sich eine kryptische Grafik. Die wird nicht wirklich erklärt und ist nicht maschinenlesbar. Das ist leider kein wirklicher Akzeptanztest.

Tatsächlich ist das ein wunderbares Beispiel für Kontextwissen. Daraus ergeben sich zwei Ableitungen:

-Wir sehen, wie wichtig es ist, darüber zu reflektieren, was man selbst weiß und was die Leser eines Textes vermutlich wissen.
-Wir sehen, wie wichtig es ist, nachfragen zu können, wenn man Anforderungen liest. Wäre Bob als "ProductOwner" immer verfügbar, dann könnten wir bei Unverständnis gleich nachfragen oder er würde schon sehr bald bei Ablieferung einer der ersten Iterationen merken, dass ein Missverständnis vorliegt.

Dass Bob selbst keine Frame/Roll Klassen implementiert ist - wie man es auch wendet - ein schlechtes Beispiel:

-Entweder begeht er die BDUF-Sünde und nutzt dann noch nicht einmal seine Erkenntnisse aus dem Design. Das wäre Informationsverlust und Geldverschwendung in einem realen Projekt.

-Oder er lässt den Code leichtfertig schwerer als nötig verständlich. Sein Score() macht für mich die Bowlingregeln nicht sehr klar.

-Ralf

Anonym hat gesagt…

lustig, jetzt weiß ich wo die hier die Idee für den "einstellungstest" her haben...

Ralf Westphal - One Man Think Tank hat gesagt…

@anonym: Nicht nur die haben ihren Einstellungstest von Bob. Auch bei den Software Craftsmen berichten manche davon, dass sie damit bei Gesprächen konfrontiert wurden.

Die Beschreibung bei dem zitierten Website ist allerdings besser. Ich sehe jetzt mein Missverständnis. Die Gesamtpunktzahl ist nicht einfach nur eine Addition. Manchmal wird eben doppelt gezählt. Ich konnte mir eigentl auch nicht vorstellen, dass die Aufgabe sooo einfach ist. Aber bis jetzt hab ichs eben nicht gesehen.

Beispiel mit 3 Würfen: 10, 3, 4.

Frage: Was ist die Gesamtpunktzahl? 17 hatte ich gedacht weil: 1 Frame mit 1 Wurf (10) und zwei Bonuswürfe (3, 4) für den Strike. Ist aber falsch.

Es liegen 2 Frames vor? Frame 1=10, Frame 2=3 und 4. Nun der Trick: Zum Ergebnis von Frame 1 werden die nächsten beiden Würfe aus Frame 2 gerechnet. Das sind also keine zusätzlichen Würfe, wie mir der Begriff "Bonuswurf" suggeriert hat.

Wenn ich mit diesem Wissen nun nochmal in Bobs Code schaue, dann wird der aber leider auch nicht besser. Da wird z.B. das Feld rolls[] mit frameindex indiziert. Wie intuitiv ist das denn??? [Kopfschütteln]

Ich setze mich also mal hin und versuche mich an einer Alternative, jetzt da ich die Regeln verstehe.

-Ralf

Mahk hat gesagt…

Ralf, besorg Dir mal ne Wii mit Wii Sports. Da lernt man's recht schnell ;-)

Aber Du hast recht. Ich hab das Problem damals auch erst als recht einfach empfunden (die Wii haben wir erst seit 4 Wochen) und erst beim zweiten hinschauen das Problem erkannt. Trotzdem wollten die mich nicht - meine Lösung war anscheinend zu wenig "TestDriven", was allerdings nicht in der Anforderung steht. Auch waren "zu viele Strukturen enthalten, die nichts mit dem Problem zu tun haben" - war mir auch bewusst, aber wenn die schreiben, dass ich dabei ein wenig Spaß haben soll, heißt das für mich, dass ich mal was neues ausprobiere, z.B. die Business-Logik als WCF Service zu implementieren und einen Forms-Client Message-basiert drauf zugreifen zu lassen. Naja, wer nicht will der hat schon...