Von Code Katas, Root Causes und zu kurzen Blog Posts [endlich-clean.net]

Gestern habe ich über meine Erkenntnisse beim Bearbeiten zweier Code Katas berichtet. Ralf hat nun eine Root Cause Analysis angestellt und dabei einen wichtigen Aspekt herausgearbeitet:

TDD ist ein Werkzeug, das mir den Weg zu wartbarem Code für ein gegebenes Modell zeigt. TDD ersetzt die Modellierung aber nicht.

Mal davon abgesehen, dass er nicht “wartbar” meint sondern “evolvierbar”, stimme ich dieser Erkenntnis uneingeschränkt zu. Allerdings glaube ich, dass mein Blog Post eventuell etwas zu kurz geraten ist und daher eine andere Erkenntnis untergegangen ist. Denn ich hatte von Anfang an ein Modell der Lösung vor Augen. Dies soll keine Rechtfertigung sein, denn in der Tat passiert es mir auch manchmal, dass ich mit TDD loslege, ohne eine solches Modell.

Im konkreten Fall bei der KataPotter hatte ich zwei Probleme:

  • Umsetzung meiner algorithmischen Idee
  • Refaktorisieren der Lösung so dass sie “clean” wird

Ich habe die Kata Beschreibung komplett gelesen, bevor ich mit Tests oder gar Implementierung begonnen habe. Daher war mir von Anfang an bewusst, dass die Kata nicht gelöst werden kann, ohne eine algorithmische Idee, ein Modell, zur Lösung zu haben. Die Herausforderung dabei ist folgende:

Jeder Band kostet 8 EUR. Ein Set bestehend aus 5 unterschiedlichen Bänden ergibt einen Rabatt von 25%, Sets aus 4 Bänden 20%, Sets aus 3 Bänden 10% und Sets aus 2 Bänden 5%. Der Rabatt wird nur auf die Bände gewährt, die in einem Set enthalten sind, also nicht auf den gesamten Warenkorb! Das führt dazu, dass man nicht einfach die größtmöglichen Sets zur Berechnung heranziehen kann, sondern verschiedene Varianten durchprobieren muss und dann die Kombination mit dem niedrigsten Gesamtpreis verwendet.

Beispiel: der folgende Warenkorb kann zu unterschiedlichen Sets kombiniert werden.

  • 2 x Band 1
  • 2 x Band 2
  • 2 x Band 3
  • 1 x Band 4
  • 1 x Band 5

Daraus ergeben sich folgende Möglichkeiten:

  • 1 x 5er Set plus 1 x 3er Set
  • 2 x 4er Set

Die zweite Variante ist preiswerter.

So weit so gut. Meine algorithmische Idee sah von Anfang an folgendermaßen aus: in einer Hashmap werden die Bücher (Key) jeweils mit ihrer Menge (Value) abgelegt. Anschließend werden mögliche Sets gebildet und der Gesamtpreis des Warenkorbs gebildet. Dabei werden mehrere Durchläufe vorgenommen und die maximale Setgröße jeweils beschränkt. Im ersten Durchlauf sind 5er, 4er, 3er und 2er Sets zulässig. Im zweiten Durchlauf nur 4er, 3er, 2er, dann nur 3er und 2er, etc.

Beim jeweiligen Durchlauf zähle ich in der Hashmap also, wieviele Einträge vorhanden sind, deren Value größer 0 ist. Diese bilden ein Set. Dann wird deren Value um 1 reduziert und erneut gezählt. Dies so lange, bis alle Values 0 sind.

Das Modell sieht also bildlich so aus: nehme jeweils das größte Set aus dem Warenkorb und berechne seinen Preis. Addiere diesen Setpreis zum bisherigen Gesamtpreis. Wiederhole dies solange, bis der Warenkorb leer ist. Um das Minimum zu ermitteln, lasse erst Sets bis 5 zu, dann nur Sets bis zur Größe 4, etc.

Klingt alles ganz einfach (hoffe ich jedenfalls), erfordert in der Implementierung aber ein paar Schleifen und Hilfsvariablen. Und darin lag mein erstes Problem: in der konkreten Umsetzung der Idee. Zum Beispiel mussten die Values in der Hashmap jeweils um 1 reduziert werden. Dabei habe ich immer wieder eine Fehlermeldung erhalten, die (sinngemäß) besagte, dass die Hashmap innerhalb des foreach geändert worden wäre. Nach ein paar erfolglosen Versuchen ist mir gedämmert, dass ich garkeine Hashmap benötige, sondern ein Array mit den Mengen völlig ausreicht.

Doch dabei schlug dann mein zweites Problem zu: es tauchten Hilfsvariablen auf. Erst eine, dann noch eine. Der Code sah nicht “schön” aus. Also begann ich zu Refaktorisieren, obwohl noch nicht alle Tests liefen. Und das war meine eigentliche Erkenntnis, die ich gestern in meinem Blog Post zum Ausdruck bringen wollte: beginne nicht zu früh mit dem Rafaktorisieren.

Ich begann nämlich, die Implementierung zu verändern, kriegte die häßlichen Konstrukte aber nicht lesbarer hin. Also ließ ich es erstmal sein und stellte die Implementierung fertig. Und siehe da: plötzlich war es ganz einfach die Implementierung so zu refaktorisieren, dass sie leicht verständlich wurde und die Hilfskonstrukte verschwanden.

Fazit: TDD mit Modell im Kopf (oder auf Papier) funktioniert. Und ich bin sicher, dass meine Implementierung ohne die Tests noch zahlreiche Fälle falsch berechnen würde. Dadurch, dass die Tests vor der Implementierung da waren habe ich mir den Frust erspart, zu glauben ich wäre fertig, um dann festzustellen, dass es doch noch Fälle gibt, die von meiner Implementierung falsch berechnet werden.

Kick it on dotnet-kicks.de

Comments are closed.