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.
Nebenbei: 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.