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.
33 Kommentare:
Sicher ist eine elegante Lösung normalerweise einer hässlichen vorzuziehen, aber Eleganz darf nicht wichtiger sein als Korrektheit. Und deine Lösung ist offensichtlich nicht korrekt, denn
sut.AddScore(Players.Player1);
sut.AddScore(Players.Player1);
sut.AddScore(Players.Player1);
sut.AddScore(Players.Player2);
sut.AddScore(Players.Player1);
muss natürlich zum Gewinn von Spieler 1 führen, tut es aber bei deiner Lösung nicht.
Die Ursache ist ein Fehler im Zustandsautomat, der bei einem verlorenen Punkt im Zustand 40 immer in den Zustand deuce geht, egal wieviele Punkte der andere Spieler hat. Wenn man die Aufgabe mit einem Zustandsautomaten lösen will, müssen die Zustände die Punktzahlen beider Spieler berücksichtigen. Nur aus dem Zustand 40:30 würde es dann bei einem verloren Punkt in den Zustand deuce gehen (bzw. aus dem Zustand 30:40 bei einem gewonnenen Punkt).
Ralf, ich denke, dass du hier sehr verbissen argumentierst. Ein langer Blog-Kommentar sowie ein ganzer Blog-Eintrag sind sehr viel harter Stoff auf die wenigen Sätze vom Björn. Zudem stellst du mutmaßliche Argumente in den Raum. Bedenke, dass du als MVP, Fachautor und Initiator von CCD für den "normalen" Entwickler eine ziemlich einschüchternde Autorität darstellst.
Ist doch klar, dass du
a) als erfahrener Softwarearchitekt mit viel weniger Code-Zeilen auskommt und
b) alles mit EBC löst! ;-)
Gerade als "normaler" Entwickler nimmt man doch an einem Dojo teil, um von den anderen Schülern und vom Meister zu lernen! Hier hat ein Sensei aber fest zugeschlagen!
Würde man die gleiche Sprache sprechen (für Deutsche immer schwer), fiele auf, dass State Machine und State Pattern kein großes Meer trennt.
Der Unterschied liegt darin, dass die State Machine nur benutzt werden musste.
Wie der Vorredner gesagt hat, eine schöne Lektion für einen Schüler, nicht alles mit Patterns neu zu programmieren, wenn man es mit Frameworks auch so bekommen kann. Ist einfacher, lesbarer, macht Kollegen glücklicher.
Irgendwie fühl ich mich ja geehrt eine
so ausführliche Antwort zu bekommen.
Du hast also geschaft die wesentlichen Teile des Katas in 5 Zeilen zu quetschen. Sind wir tatsächlich wieder bei C angelangt, wo wir mit Längen und Mikro-Effizienz von Programmen argumentieren müssen?
Vielleicht waren "Abstrakt" und "Zeremonie" tatsächlich das falschen Worte. Sei es drum. Die gezeigte Implementierung ist mir im Vergleich zu den anderen Varianten einfach zu kompliziert. Ja, ich glaube kompliziert trifft es besser.
Zeig Deinen Zustandsautomaten doch mal jemand, der die Regel für Tennis nicht kennt und versuch sie ihn das anhand des Codes alleine erklären zu lassen. Ich bezweifle das dies ohne die Modellierungskizzen erfolgreich sein wird.
Ja, State Pattern und der Zustandsautomat sind ähnliche Ansätze. Wenn Du meinen Original Kommentar noch mal überfliegst, findest Du vielleicht die Stelle an der ich erwähne, das ich die explizite Modellierung von Zuständen ja positiv sehe.
Das State-Pattern scheint mir die geeignete Wahl als eine Integer-basierte Zustandsmaschine. Nicht weil es ein Pattern ist und man Pattern für die Lösung nutzen muss, sondern weil es in meinen Augen expliziter UND lesbarer ist als Deine Zustandsmaschine.
Thema Tests: Reichen dem Kunden die Tests nicht, um zu beweisen, dass die Anforderungen korrekt verstanden und umgesetzt worden sind? Ich bin da irgendwie ein wenig irritiert. Nicken und die Tests müssten doch reichen, oder?
Eins noch zum Thema Modellierung/Vorgehensweise: Ich bin mir nicht bewusst, dass ich ein Wort über Deiner Vorgehensweise fallen lassen hab. Die ist mir ehrlich gesagt auch ziemlich wurscht. Siehst Du doch ähnlich, oder? Ich habe lediglich meine Meinung zu dem Ergebnis kundgetan.
Wir können da gerne beim ArchOS noch mal drüber diskutieren ;-)
VG
Björn
So, in der Reihenfolge der Kommentareingänge. Schön, dass es soviel Feedback gab.
@Krzysztof: Danke für den Hinweis auf den Fehler. Natürlich darf Eleganz nicht über Korrektheit stehen. Und man kann sich elegant hinlegen ;-) Das habe ich getan, weil ich 40:40 falsch umgesetzt habe.
Die Lösung ist inzw angepasst. Für mich war dabei das schöne Gefühl, dass das ganz einfach war. Ich musste im Modell nur den Punkt lokalisieren, wo ich eingreifen muss und danach ergab sich eine einfache Umsetzung im Code.
Aus dem Fehler sollte also nicht geschlossen werden, dass Modellieren nix bringt, sonder das Gegenteil. Im Modell war sofort zu sehen, dass eine Verknüpfung zwischen den Spielern fehlte.
(Ich freue mich besonders über den Fehlerhinweis, weil der mir Anlass gab, meine Implementation auf Vordermann zu bringen. Bis dahin hatte ich nämlich EBC-Bauteile als Methoden implementiert, weil das zunächst einfacher schien. Der Fehler hat mir gezeigt, dass eine Änderung mit Klassen für Bauteile viel einfacher ist. Also habe ich refaktorisiert.)
@Anonym: Habe ich verbissen argumentiert? Hm... Ernst ja. Aber verbissen? Hab ich gar auf Björn zu fest eingeschlagen? Ich hoffe, nicht.
Björn weiß, dass ich ihn schätze. Habe ihn nicht umsonst zur prio als Sprecher eingeladen. Und er hat seine Sache dort sehr gut gemacht. Ich glaube daher, dass er meinen "Widerstand" richtig einzuordnen weiß.
Ganz bewusst habe ich natürlich einen sehr direkten Ton gewählt. "Rumeiern" mag ich nicht. Immer wieder relativieren, bringt uns nicht voran. Hart, aber herzlich find ich passend. Und vor allem habe ich mir sehr bemüht, zu argumentieren und nicht nur eine Befindlichkeit gegen eine andere zu setzen.
Habe ich eine besondere Verantwortung als MVP etc.? Hm... Ich weiß nicht. Muss ich länger als andere über meine Aussagen nachdenken? Ne, ich glaub, nicht. Ich habe höchstens eine Verantwortung authentisch zu sein und offen für Kritik an mir und sachlich. Aber auch da unterscheide ich mich eigentlich nicht von anderen, würd ich sagen.
Ich habe Björn auch nicht per se kritisiert für seine Zahl an Codezeilen. Darauf schaue ich gar nicht so sehr. Doch er hat recht pauschal mit dem Patternpfahl gewunken, da wollt ich es wissen, ob es ihm denn etwas gebracht hat. Und siehe da, eine simple Metrik scheint mir das Gegenteil zu beweisen.
Dass ich nun EBC benutzt habe, ist halt mein Steckenpferd derzeit ;-) Darum ging es mir nicht primär. Eine andere Art von Modell wäre mir auch recht gewesen bei anderen zu sehen. Doch Modelle fehlen soweit das Kata-Auge reicht. Schade.
Und wenn du sagst, andere wollen beim Dojo was lernen, dann hab ich für die auch geschrieben. Denn ich möchte ihnen zeigen, dass TDD allein es nicht reißt.
@Björn: "Sind wir tatsächlich wieder bei C angelangt, wo wir mit Längen und Mikro-Effizienz von Programmen argumentieren müssen?":
Ne, sicher nicht. Es geht auch nicht per se um Kürze. Bei einer knappen Zehnerpotenz Unterschied jedoch, da würd ich doch mal hinschauen.
"im Vergleich zu den anderen Varianten einfach zu kompliziert":
Hm... definiere mal "kompliziert". Ist ein Zustandsautomat komplizierter als ein State-Pattern? Ne, das glaub ich nicht. Vor allem, weil du ja auch letztlich einen Zustandsautomaten implementiert hast. Der sieht nur anders aus.
"Zeig Deinen Zustandsautomaten doch mal jemand, der die Regel für Tennis nicht kennt und versuch sie ihn das anhand des Codes alleine erklären zu lassen. Ich bezweifle das dies ohne die Modellierungskizzen erfolgreich sein wird.":
Ah, hier sind wir beim Knackpunkt für dich, denke ich. Ich gebe dir recht, dass die Integers als Zustandsbezeichnungen nicht super zu lesen sind - allemal, da im Zustandsdiagramm die Zustände nicht nummeriert sind.
Aber deshalb den Zustandsautomaten auf 5 Zeilen rauskippen und stattdessen 10 mal mehr Zeilen schreiben? Ne. Sorry. Das sehe ich anders. Denn die Lösung ist viel einfacher: Die Modellskizze ist Teil des Codes. Ich muss niemandem Code zeigen, der einfach nur verstehen will, wie das Programm grundsätzlich funktioniert. Ich zeige dem nur das Modell.
Diese Wahl hast du nicht mal. Du musst Code zeigen. Das halte ich für falsch.
Nur, weil irgendwann mal jmd gemerkt hat, dass Klassendiagramme das Papier kaum Wert sind, auf dem sie gedruckt wurden, weil der Code schon wieder woanders ist, soll nun der Code es allein richten? Ne, ne. Das ist für mich der falsche Ansatz.
Ich will nicht per se Papier produzieren. Aber etwas Modell darf es schon sein.
Reichen dem Kunden tests? Mag sein. Akzeptanztests. Die laufen auf der Oberfläche dessen, was er bestellt hat. Hier: ein Tennis-API.
Aber was dem Kunden reicht, reicht mir als Entwickler doch noch lange nicht.
Den Fehler hab ich zunächst im API als Integrationstest nachgestellt. Klar. Aber danach wollte ich selbstverständlich ganz gezielt die Funktionseinheiten testen, die den Fehler verursachen. Und die sind nicht im API sichtbar.
Auf dem ArchOS können wir natürlich nochmal drüber sprechen. Und nen Bierchen dabei schlürpfen :-)
Es mag für eine Kata als Qualitätsanspruch ausreichen, dass der Code die Aufgabe erfüllt. Vermutlich auch einfach deswegen weil, wenn die Kata erledigt ist, niemand mit der Tennis Kata Teil 2,3 und 4 auftauchen wird. Genau darum geht es aber an dieser Stelle in der professionellen Softwareentwicklung: Evolvierbarkeit. Daher ist für mich ein weiterer Anspruch an die Codequalität selbstverständlich auch wieviel Zeit es einen Entwickler kostet, bestehenden Code zu verstehen, um damit arbeiten zu können. Ein mehrdimensionales Array mit ein paar Zahlen darin lässt sich ausschließlich dann verstehen, wenn man dazu Dokumentation hat. Die muss geschrieben, verwaltet, gelesen und aktuell gehalten werden. Daher scheidet deine Lösung, Ralf, als Mittel zum Qualitätszweck für mich leider aus.
Zu dem Unterschied zwischen "State-Pattern" und "Zustandsmaschine" sollte man vergleichen:
'this'-Pointer auf das State-Objekt <-> _currentState Index in die Score Enumeration
Methodenaufruf zur Transition in das State-Objekt <-> Lookup des nächsten States anhand von transition und _currentState im Array _stateTransitions
Deutlich wird, dass die vorgestellte Implementierung nichts anderes als das State-Pattern eben mit Mitteln von imperativen Programmiersprachen wie C ist. ('this' vs. 'int', 'vtab' vs. 'array')
Neben der erwähnten Dokumentationspflicht des Arrays, ist es auch noch nötig, das Array _stateTransitions und die Enumeration Scores abgestimmt zu halten; und das obwohl die Enumeration nicht einmal in der Klasse Player zu finden ist. CleanCode ich hör' dir trapsen ... :)
@Thomas: Danke für deinen Einwand. Ich glaube, damit sind wir bei einem Kernmissverständnis der heutigen Softwareentwicklung.
"Ein mehrdimensionales Array mit ein paar Zahlen darin lässt sich ausschließlich dann verstehen, wenn man dazu Dokumentation hat. Die muss geschrieben, verwaltet, gelesen und aktuell gehalten werden."
Ich denke, wir sind uns einig, dass ein Kontext immer gegeben sein muss, um Code zu verstehen, oder? Wenn ich Björns Code irgendwem ohne weitere Angaben zum Problem gebe, wird ihn auch niemand verstehen.
Den Kontext klar zu machen und dann Code vorzulegen, kann aber auch nicht reichen. Irgendwie und irgendwann mag man dann zwar verstehen, wie der Code funktioniert - doch das kann dauern. So passiert es jeden Tag in unzähligen Brownfieldprojekten. Alle wissen, worum es geht, es existieren Berge an Code - doch die Entwicklung hat Mühe mit dem Verständnis. Aber nicht, weil der Code zu knapp ist. Das passiert auch den größten Freunden von Patterns. Soviel kann ich dir versichern. Warum? Weil Patterns erstens falsch eingesetzt werden und zweitens, wenn der Projektdruck steigt, solche Feinheiten schnell unter den Tisch fallen. Es gibt keinen Zwang zum Pattern, man kann jederzeit ohne. Und dann ist ihr Einsatz vom Individuum abhängig, weil es allermeistens keinen systematischen Codereview gibt.
Natürlich bin ich gegen beliebige Verkürzungen von Code. Es gibt auch eine Eleganzmanie, die alles auf möglichst wenige Konstrukte zusammenpressen will.
Ich mache aber etwas anderes. Ich produziere auch keine Dokumentation. Ich modelliere! Das ist etwas anderes. Das Modell übersetze ich dann in Code - und schmeiße es dann natürlich nicht weg. Das machst du mit deinem Quellcode auch nicht nach der Compilation. Und du forderst auch nicht, dass man Funktionalität aus IL-Code ablesen kann.
Programmierung ohne Modell macht es sich immer schwer. Deshalb sage ich mal provokant: Code ohne Modell ist nicht zu verstehen. Auch der wunderbarste Patterncode nicht. Das weiß jeder, der schonmal versucht hat, an Open Source etwas zu verändern. Warum ist das so mühsam? Weil Open Source gewöhnlich nur aus Code besteht. Da fehlt jede Abstraktion darüber. Die Wahrheit, die dort im Code steckt, ist daher kryptisch. Ich kann sie kaum deuten. Deshalb nützt sich nichts und die Realität ist, dass die allermeisten OOS Projekte keine Contributors haben.
Modellierung ist daher der Ausgangspunkt für Clean Code für mich.
Dass mein Zustandsautomat ein kleinwenig besser verständlich sein könnte, geb ich zu. Die Zustände hätten mit dem Enum Scores deutlicher in Verbindung gebracht werden können. Aber wenn wir uns auf die Detailebene begeben, dann kann ich dir an allen Kata-Lösungen etwas bemängeln.
Ich stelle mal die Behauptung zur Diskussion in den Raum: Entwickler lernen nicht zu modellieren. Auf Befragen - und das tue ich - wissen sie ja nicht mal, was ein Modell ist (im Gegensatz zum Code). Weil sie es nicht lernen und wissen, finden sie das unnötig.
"Was der Bauer nicht kennt, das frisst er nicht."
Weiter bei Teil 2...
Teil 2:
Dass Gurus wie Robert Martin oder Kent Beck dann auch nichts zum Thema sagen, sondern mit ihren Kommentaren zu Kommentaren weiter auf "die einzige Quelle der Wahrheit, den Code" fokussieren, macht die Sache da nicht besser.
Andere sagen etwas zur Modellierung - z.B. Eric Evans -, aber werden dann natürlich, weil sie nur wenige sind, weithin missverstanden. Dazu habe ich ja auch schon etwas unter dem Stichwort Domänenobjektmodell gesagt.
Bottom line: Code ist und bleibt ein Implementierungsdetail. Da wir ihn noch schreiben müssen, sollte er eine gewisse Qualität aufweisen. Absolut. CCD forever :)
Aber Code ist nur ein Mittel. Das weiß jeder, der früher Assembler programmiert hat und dann C und dann C++. So wie ich zum Beispiel ;-)
Code auf einem Abstraktionslevel wurde ersetzt durch Code auf einem höheren Abstraktionslevel. Das hat einen Moment verwirrt, man hat sich etwas geziert und versucht dagegen zu argumentieren. Doch am Ende war der Vorteil klar und niemand trauert den Assemblerzeiten nach.
Dasselbe passiert hier gerade. Die bisherige Abstraktionsebene ist nicht abstrakt genug. Mit ihr kann man Lösungen entwickeln. Klar. Wie mit Assembler auch. Aber es ist nervig. Den Code sauberer zu machen ist nur eine vorübergehende Lösung. Notwendig, aber keine Wurzelproblembehandlung. Das Wurzelproblem liegt im Programmiermodell und in der niedrigen Abstraktionsebene.
Bei so winzigen Beispielen wie Kata Tennis fällt das nicht auf. Klar. Die kann ich auch in Assembler komfortabel und lesbar lösen. Aber in richtigen Anwendungen, da fällt es auf. Wie gesagt: Man wähle ein beliebiges OSS Projekt und versuche darin Änderungen vorzunehmen. Ohne hohen Zeitaufwand für das Studium der Sourcen, d.h. Reengineering (!), ist das nicht machbar.
Ohne Modelle geht es nicht (mehr).
Auch mir kam deine Argumentation etwas verbissen vor. Das lag vor allem der Darreichungsform (extra Blog-Artikel, da dir dein einfacher Kommentar nicht mehr gereicht hat) in Kombination mit einer aus meiner Sicht ziemlich wackligen, um nicht zu sagen unhaltbaren Position, die du da vertrittst, nochmals verstärkt durch die im Code enthaltenen Fehler.
Dass ich einen Fehler gefunden habe, kannst du natürlich als Indiz dafür sehen, dass deine Vorgehensweise die von dir beschriebenen Vorteile bezüglich der Feststellbarkeit der Korrektheit hat. Ich sehe das nicht so. Deine Skizze des Zustandsautomaten weicht nämlich von deiner Implementierung ab (in der Skizze gibt es zwei rote Pfeile, die aus dem Zustand 40 ausgehen, und die man durchaus so interpretieren kann, dass es von der Anzahl der Punkte des Gegners abhängt, ob der Automat im Zustand 40 bleibt oder in den Zustand deuce geht). Die Korrektheit des Programms lässt sich also offensichtlich nicht aus der (unterstellten) Korrektheit der Skizze ableiten.
Insofern muss man deine Frage "Wie hätte ich nach Darlegung der Anforderungen in Björn Vertrauen haben können, dass er korrekten Code schreibt?" in Bezug auf die von dir unterstellten Vorteile so formulieren: "Wie hätte ich nach Darlegung der Skizze Vertrauen haben können, dass du korrekten Code schreibst?". Sicher kann so eine Skizze helfen, Fehler zu finden, die schon in einem Missverständnis der Anforderungen liegen, aber garantiert ist das offensichtlich nicht.
Ich stimme dir darin zu, dass man Pattern nicht unreflektiert anwenden sollte. Für das Tennis-Problem wäre der State-Pattern sicher nicht die optimale Lösung. Das liegt wohl auch daran, dass es außer den den Zustandsübergängen keine Aktionen gibt, die durchgeführt werden müssen, und der State-Pattern somit seine Vorteile nicht ausspielen kann.
Dein Ansatz ist aber auch keine gute Lösung, denn sie hat ein echtes Dokumentationsproblem. Dass die Zustände in der Skizze nicht nummeriert sind, ist nur die Spitze des Eisbergs. Selbst wenn sie das wären, gäbe es einen unnötigen Medienbruch, weil die Skizze physisch vom Code getrennt vorliegt, mit allen bekannten Problemen, insbesondere dass Codeänderung meist nicht in einer Änderung der Skizze resultieren.
So wie ich dich an anderer Stelle verstanden habe, bist du der Meinung, dass Kommentare, die nur beschreiben, was der Code tut, vermieden werden sollten, dass nur kommentiert werden sollte, warum man etwas macht, nicht was der Code macht und dass insbesondere Code immer so geschrieben werden sollte, dass er aus sich selbst heraus verständlich ist. Diesem Anspruch wirst du hier nicht gerecht.
Dabei wäre es so einfach gewesen: Man braucht nur ein Enum mit symbolischen Namen für die Zustände und ein ("doppeltes") Dictionary, das die Abbildung SxI->S repräsentiert (wobei S die Menge der Zustände ist und I die Menge der Inputs, also ob Spieler 1 den Punkt gewonnen hat). Dadurch würde aus jeder Zahl in deinem Array eine eigene Zeile, etwa in der Art
_stateTransitions [Result.Win] [State.Points15] = State.Points30;
Das ist natürlich deutlich länger (aber so lang nun auch wieder nicht; die Zahl der Zeilen der Klasse Player würde sich - den Enum schon eingerechnet - allenfalls verdoppeln), vor allem aber deutlich lesbarer und ohne weitere Kommentare oder gar externe Dokumentation (also insbesondere ohne externes Modell) verständlich. Das ist für mich mehr als eine Detail-Verbesserung.
Ich vermute, dass dich gerade Existenz der Skizze dazu verleitet hat, so kompakten Code zu schreiben, dass dich die Existenz der Skizze mindestens hat übersehen lassen, wie schlecht die Qualität des resultierenden Code ist. Insofern lässt sich schon die Frage anknüpfen, ob nun durch die Skizze - also durch die Vorgehensweise, zuerst das Modell in grafischer Form darzulegen - etwas verbessert oder verschlechtert wurde.
Auch mir kam deine Argumentation etwas verbissen vor. Das lag vor allem an der Darreichungsform (extra Blog-Artikel, da dir ein einfacher Kommentar nicht mehr gereicht hat) in Kombination mit einer aus meiner Sicht ziemlich wackligen Position, die du da vertrittst, nochmals verstärkt durch den im Code enthaltenen Fehler.
Dass ich einen Fehler gefunden habe, kannst du natürlich als Indiz dafür sehen, dass deine Vorgehensweise die von dir beschriebenen Vorteile bezüglich der Feststellbarkeit der Korrektheit hat. Ich sehe das nicht so. Deine Skizze des Zustandsautomaten weicht nämlich von deiner Implementierung ab (in der Skizze gibt es zwei rote Pfeile, die aus dem Zustand 40 ausgehen, und die man schon so interpretieren kann, dass es von der Anzahl der Punkte des Gegners abhängt, ob der Automat im Zustand 40 bleibt oder in den Zustand deuce geht). Die Korrektheit des Programms lässt sich also nicht aus der (unterstellten) Korrektheit der Skizze ableiten.
Insofern muss man die Frage "Wie hätte ich nach Darlegung der Anforderungen in Björn Vertrauen haben können, dass er korrekten Code schreibt?" in Bezug auf die von dir unterstellten Vorteile so formulieren: "Wie hätte ich nach Darlegung der Skizze Vertrauen haben können, dass du korrekten Code schreibst?". Sicher kann so eine Skizze helfen, Fehler zu finden, die schon in einem Missverständnis der Anforderungen liegen, aber garantiert ist das eben nicht.
Ich stimme dir darin zu, dass man Pattern nicht unreflektiert anwenden sollte. Für das Tennis-Problem wäre der State-Pattern sicher nicht die optimale Lösung. Das liegt wohl auch daran, dass es außer den den Zustandsübergängen keine weiteren Aktionen gibt und der State-Pattern somit seine Vorteile nicht ausspielen kann.
Dein Ansatz ist aber auch keine gute Lösung, denn sie hat ein echtes Dokumentationsproblem. Dass die Zustände in der Skizze nicht nummeriert sind, ist nur die Spitze des Eisbergs. Selbst wenn sie das wären, gäbe es einen unnötigen Medienbruch, weil die Skizze physisch vom Code getrennt vorliegt, mit allen bekannten Problemen, insbesondere dass Codeänderung meist nicht in einer Änderung der Skizze resultieren.
So wie ich dich an anderer Stelle verstanden habe, bist du der Meinung, dass Kommentare, die nur beschreiben, was der Code tut, vermieden werden sollten, dass nur kommentiert werden sollte, warum man etwas macht, nicht was der Code macht und dass insbesondere Code immer so geschrieben werden sollte, dass er aus sich selbst heraus verständlich ist. Diesem Anspruch wirst du hier nicht gerecht.
Dabei wäre es so einfach gewesen: Man braucht nur ein Enum mit symbolischen Namen für die Zustände und ein doppeltes Dictionary, das die Abbildung SxI->S repräsentiert (wobei S die Menge der Zustände ist und I die Menge der Inputs, also ob Spieler 1 den Punkt gewonnen hat). Dadurch würde aus jeder Zahl in deinem Array eine eigene Zeile:
_stateTransitions [Result.Win] [State.Points15] = State.Points30;
Das ist natürlich deutlich länger (aber so lang nun auch wieder nicht; die Zahl der Zeilen der Klasse Player würde sich - den Enum schon eingerechnet - allenfalls verdoppeln), vor allem aber deutlich lesbarer und ohne weitere Kommentare oder gar externe Dokumentation (also insbesondere ohne externes Modell) verständlich. Das ist für mich mehr als eine Detail-Verbesserung.
Ich vermute, dass dich gerade die Existenz der Skizze dazu verleitet hat, so kompakten Code zu schreiben, dich aber mindestens hat übersehen lassen, wie schlecht die Qualität des resultierenden Code ist. Insofern lässt sich schon die Frage anknüpfen, ob nun durch die Skizze - also durch die Vorgehensweise, zuerst das Modell in grafischer Form darzulegen - etwas verbessert oder verschlechtert wurde.
@Krzysztof: Also, dass man mir einen Blogartikel als Verbissenheit auslegt... Das find ich schon etwas verbissen. Denn ich hab ja klar gemacht, warum ich ihn geschrieben habe: weil der Platz in einem Kommentar für die Wichtigkeit des Themas zu klein ist. Ist das verbissen? Ne. Nur weil mir etwas wichtig ist und ich dafür eine besondere Form wähle, bin ich doch nicht verbissen.
Und dass ich eine unhaltbare (!) Position vertrete... Boah, da muss ich mich erstmal setzen. Tu mir doch bitte diesen Gefallen:
1. Formuliere meine Position in deinen Worten, damit ich sehe kann, ob ich sie überhaupt rüberbringen konnte.
2. Erkläre mir dann, warum die Position unhaltbar ist. (Vor allem im Vergleich zu was.)
Du hast völlig recht, dass meine Skizze des Zustandsautomaten fehlerhaft ist. Zwei rote Pfeile von der 40 weg, ist inkorrekt. Genau dort lag dann ja aber auch das Problem.
Warum habe ich das Problem nicht selbst erkannt? Warum habe ich den Zustandsautomaten falsch übersetzt? Keine Ahnung. Passiert halt. Wofür ist das ein Beweis?
Sollte ich mit der Modellierung aufhören, weil mir ein Übersetzungsfehler unterlaufen ist? Sorry, das ist doch Quatsch. Da hören wir besser alle gleich mit der Programmierung auf, weil wir immer wieder Fehler machen.
Ich lese vielmehr zweierlei heraus:
a.) Programmierung profitiert von vielen Augen. Es ist eine kollaborative Aufgabe. Und das geht leichter, wenn diese Augen nicht auf Text starren, sondern auf ein Modell.
b.) Am Modell kann ich Inkonsistenzen leichter ablesen. Ich war zwar wohl systemblind, aber dir ist der doppelte Verlierer-Pfeil ins Auge gesprungen. Wunderbar.
Korrektheit lässt sich überhaupt nicht beweisen/ablesen. Auch aus Code nicht. Ja, ich weiß: "Bubbles don´t crash." Aber das ist so ein Totschlagargument, dass ich nur gähnen kann. Denn erstens vernachlässigt es "lauffähige Bubbles" wie DSLs. Zweitens kippt es mit dem Blasenbad das Kind Abstraktion aus. Sehr schade.
Was an meinem Ansatz ist denn nicht gut? Bring es bitte differenziert auf den Punkt.
Ist es der Zustandsautomat? Nein.
Ist es die Art der Implementierung des Zustandsautomaten? Ja.
Aha, dann sind wir beieinander. Ich finde deinen Vorschlag mit den Dictionaries wunderbar. Wieder ein Beweis dafür, dass Programmierung davon profitiert, mehrere Köpfe zusammenzustecken.
Meinen Job hab ich nicht darin gesehen, den besten Zustandsautomaten zu liefern, sondern die Modellierungsfahne zu hissen. Dabei ist was auf der Strecke geblieben. Passiert halt. Und andere kümmern sich dann darum. Arbeitsteilung. Wunderbar.
Deinen Vorschlag hab ich aufgegriffen und meine Implementation auf Dict<> umgestellt. Siehe den ursprünglichen Blogartikel.
Jetzt ist der Code natürlich länger. Aber ich halte ihn immer noch für besser les- und wartbar als ein State-Pattern. Und er ist immer noch modellbasiert.
Hat mich das Modell zu irgendwas verleitet? Sollte Modellierung gar deshalb verworfen werden? Natürlich nicht.
Das Modell hat keine Aussage über den Code gemacht. Das ist ja der Trick am Modell. Wenn ich besonders knapp übersetzt habe, dann nicht wegen dem Modell, sondern aus andern Gründen. Vielleicht, weil ich früher schonmal Zustandsautomaten so gemacht hatte? Könnte sein. Auf keinen Fall halte ich das Modell für überflüssig. Es hat mich geleitet und ist die Ebene, auf der ich weiterhin über die Lösung nachdenke. Warum sollte ich mich auch ständig auf Codeebene bewegen? Das muss mir mal einer erklären.
Wie gesagt: Ich glaube inzw, dass Entwickler nicht modellieren, weil sie es schlicht nie gelernt haben. So rationalisieren sie sich zurecht, dass Modelle scheiße sein, weil schwer zu pflegen und redundant oder sonstwas.
Und das mag für Nicht-Modell-Doku auch stimmen. Ein echtes Modell jedoch... das ist Gold wert.
Hallo Ralf,
bzgl. ->
"Ich glaube inzw, dass Entwickler nicht modellieren, weil sie es schlicht nie gelernt haben."
Wen oder was Du als "Entwickler" bezeichnest?
Gruß JJR
Richtig, gegen Zustandsübergangsdiagramme und Zustandsautomaten habe ich nichts. Zustandsübergangsdiagramme sind nicht zu abstrakt, sondern im Gegenteil leicht zu verstehen, sofern die Zustände sinnvolle Namen haben und es weder zu viele verschiedene Zustände noch zu viele verschiedene Inputs gibt (oder wenn doch, zumindest die Zustandsübergangsfunktion "sparse" genug ist). Selbst wenn man für die Implementierung den State-Pattern gewählt hätte, wäre ein Zustandsübergangsdiagramm die richtige grafische Darstellungsform dafür gewesen. Und ein Zustandsübergangsdiagramm durch einen Zustandsautomaten statt durch einen State-Pattern zu implementieren, geht auch in Ordnung.
Streiten kann man sich noch darüber, ob ein Zustandsübergangsdiagramm so günstig ist, wenn man von der konkreten Aufgabe weg auf die Klasse ähnlich gelagerter Probleme blickt. Wenn man alleine die Tischtenniszählung nimmt ...
"Ein Satz endet, wenn ein Spieler elf Gewinnpunkte erreicht hat und dabei mindestens zwei Punkte Vorsprung hat, zum Beispiel 11:9, 12:10, 13:11. Beim Stand von 10:10 geht der Satz in die Verlängerung. Dabei wechselt das Aufschlagsrecht nach jedem Punkt. Die Verlängerung endet dann, wenn sich ein Spieler zwei Punkte Vorsprung erkämpft hat." Quelle: Wikipedia
... sieht man, dass nicht nur für einen "normalen" Spielverlauf bis 11 Punkte die Zahl der nötigen Zustände (quadratisch) explodiert, sondern dass die Zahl der Zustände gar nicht endlich ist. Die Evolvierbarkeit stößt also bei diesem Modell schnell an ihre Grenzen.
Das alles ändert ohnehin nichts daran, dass mindestens im konkreten Fall die von dir behaupten Vorteile nicht eingetreten sind. Weder hat das Modell das Vertrauen des Auftraggebers in eine korrekte Implementierung gerechtfertigt noch hat das Modell zu einer - schon im ersten Anlauf - guten Implementierung geführt. Ich würde im Gegenteil behaupten, dass die Existenz des Modells psychologisch eine im Sinne der genannten Kriterien gute Implementierung vereitelt hat.
Das heißt natürlich noch lange nicht, dass Modelle grundsätzlich schädlich oder gar verwerflich sind. Es heißt nur, dass Modelle nicht automatisch Gold wert sind.
@JakeJBlues: Entwickler sind die, die zu einer Veranstaltung wie der BASTA! oder VSone gehen, um dort was über VS oder WCF oder WF oder NHibernate oder EF zu lernen. Leute, die dann im Projekt Quellcode schreiben in C# oder Java oder C++ usw.
Woher die das können, die Ausbildung... die ist egal. Also subsummiere ich auch mal die Informatiker darunter. Selbst wenn die eine Vorlesung hatten "Modellierung von Softwaresystemen", nehmen sie davon nicht unbedingt viel mit in die Praxis. Dasselbe gilt für Vorlesungen, die sich mit autom. Tests beschäftigen.
Damit spreche ich selbstverständlich nicht über alle/100% Entwickler. Klar. Aber über die Mehrheit. Gern bin ich bereit, den Beweis bei einer der nächsten Entwicklerveranstaltungen mit einer Umfrage anzutreten.
Hallo Ralf,
"Entwickler sind die, die zu einer Veranstaltung wie der BASTA!"
Alle anderen dann wohl, nicht :-D
Wenn dann noch die Ausbildung egal ist ->
"Woher die das können, die Ausbildung... die ist egal."
Gebe ich Dir wohl zu 100% recht, Entwickler nach Deiner Definition können nicht modellieren?!
Gruß JJR
@Krzysztof: "Streiten kann man sich noch darüber, ob ein Zustandsübergangsdiagramm so günstig ist, wenn man von der konkreten Aufgabe weg auf die Klasse ähnlich gelagerter Probleme blickt."
Warum sollte ich mich darüber streiten. Ich habe keine allgemeine Lösung für irgendwas liefern wollen, sondern nur eine konkrete für die Zählung beim Tennis.
Neues Spiel, neues Modell. Wenn ich Reversi oder Tischtennis oder Bowling implementieren soll, sieht mein Modell anders aus. Womöglicht ist ein Zustandsautomat dann nicht mit von der Partie.
"Die Evolvierbarkeit stößt also bei diesem Modell schnell an ihre Grenzen.": Die Evolvierbarkeit ist eben nicht schnell an ihre Grenzen gestoßen, weil ich 1. die Kopplung der Spieler sehr einfach nachrüsten konnte, um den Fehler zu beheben, und 2. der Umstieg von meinem Zustandsautomaten auf deinen möglich war, ohne auch nur einen Test ändern zu müssen.
Das Modell - übrigens bestehend aus Zustandsautomat und (!) EBC-Diagramm - hat sich als evolvierbar erwiesen.
Dass es von Tennis nach Tischtennis oder gar Fußball evolvieren können soll, hat niemand behauptet. Das kannst du auch nicht ernsthaft meinen.
"Weder hat das Modell das Vertrauen des Auftraggebers in eine korrekte Implementierung gerechtfertigt noch hat das Modell zu einer - schon im ersten Anlauf - guten Implementierung geführt."
Das ursprüngliche Modell war inkorrekt. Wie sollte da eine korrekte Implementierung rauskommen? Sollte ich aber, weil ich inkorrekt modellieren kann, die Modellierung sein lassen? Das ist doch Quatsch. Dann sollten wir - ich habs schon mal geschrieben - die Programmierung sein lassen. Die ist ständig voll von Fehlern.
Der Trick ist vielmehr, dass am Modell das Problem am Ende sehr, sehr einfach zu sehen war. Der Übergang von 40:* zu 40:40=Deuce war schlicht nicht da. Das (!) ist der Vorteil des Modells.
Man möge mir ein solches Missverständnis aufzeigen, wenn kein Modell vorhanden ist. Fällt das da auch ins Auge?
Was meinst du mit "guter Implementierung"? Definiere "gut". Führt TDD zu einer guten Implementierung? Immer?
"Ich würde im Gegenteil behaupten, dass die Existenz des Modells psychologisch eine im Sinne der genannten Kriterien gute Implementierung vereitelt hat."
Das Modell hat keine gute Implementierung vereitelt. Wenn mein Verständnis der Anforderung inkorrekt ist, dann kann weder mein Modell korrekt sein, noch Code, den ich gleich ohne Modell schreibe.
Es ist eine Illusion anzunehmen, dass "gleich coden" zu besseren Ergebnissen führt. Dann müssten wir ja nur extrapolieren und sagen: am besten gleich mit Assembler loslegen, also auf der niedrigsten Abstraktionsstufe. Dann wird alles noch korrekter.
Weiter mit Teil 2...
Teil 2:
Modelle sind Lösungen auf höherer Abstraktionsstufe als heutiger Quellcode. Nichts weiter. Keine Wundermittel, keine Silberkugeln, sondern konkrete Denk- und Kommunikationshilfen.
Wer die ausschlägt, der bindet sich einfach ein Bein hoch. Warum sollte ich das tun? Gibt es einen Preis für einbeinige Sprinter? Ist Askese in der Werkzeugnutzung eine Tugend? Sind wir im Mittelalter bei Mönchen und Geißlern?
Ich nutze jedes Tool, dass mir was bringt. Modelle bringen mir Überblick und einfachere "Knetmasse" für die Lösungsformung.
Und nochmal: Bis mir einer zeigt, dass er modellieren kann und es trotzdem lässt, bleibe ich dabei, dass Modellierung hilft.
Ob die Modellierung dann im Kopf stattfindet, weil der ein Meister ist, oder nur auf dem Papier... das ist mir egal. Auf Befragen kann mir der Meister dann auch sein Modell "dumpen".
Wer allerdings kein Modell hat, der kann es nicht aufmalen. Und der soll bitte nicht über die Nachteile des Modellierens sprechen. Er hat es ja noch nicht versucht.
(Dass du nicht modellieren kannst, behaupte ich übrigens nicht. Ich stelle diese These allgemein in den Raum. Wer nicht schwimmen kann, soll mir nichts über Delphin-Stil oder Schmetterling oder sonstwas erzählen.)
Zum Versuchen des Modellierens möchte ich aber anregen.
Wenn ich dabei Fehler mache... so what. Kann passieren. Warum soll ich die nicht öffentlich machen? Ein Fehler in der Anwendung spricht nicht gegen das Werkzeug. Vor allem nicht, wenn er nicht eklatant ist. Denn das war er hier nicht. Ich habe die Modellierung nicht ad absurdum geführt.
@JakeJBlues: Natürlich geht die Majorität der Entwickler nicht zur BASTA! & Co - und ist trotzdem Entwickler. Ich wollte damit nur Entwickler "anfassbar" machen. Da kannst du sie exemplarisch treffen.
Die Ausbildung ist tatsächlich egal. "Entwickler" ist kein geschützter Begriff. Man erkennt ihn an dem, was er tut: Code in einer formalen Sprache schreiben. Und im engeren Sinn: Einer, der seine Brötchen damit verdient, C# (oder VB oder Java) Code zu schreiben, um seine Brötchen zu verdienen.
Das tun Autodidakten und Dipl. Infs. gleichermaßen.
Haben die gelernt zu modellieren? Ne. Ich war lange genug in der Uni, um das aus eigener Erfahrung beurteilen zu können. Und auch UML hat da nicht viel dran geändert.
Modellierung ist kein Thema in den weitesten Teilen der Entwicklergemeinde - von Ruby über C# bis Go.
Das heißt nicht, dass es keiner tut. Immerhin gibt es ja auch sowas wie MDSD. Aber es ist kein Massenphänomen. Es ist nicht der Default. Aber nicht, weil es schlecht wäre, sondern weil es schlicht nicht konsequent gelehrt wird. (Nein, UML ist keine Lehre der Modellierung. Eher schon DDD.)
Hallo Ralf,
verstehst Du unter "modellieren":
eine Abstraktion des Problems auf Papier zu bringen?
Gruß JJR
Ob die Lösung zu abstrakt und zu zeremonisch ist oder zu wenig Pattern hat, ist mir grundsätzlich auch egal - da bin ich bei Dir. Was ich aber für wichtigen Metriken halte, sind Komplexität und Umfang des Codes.
Deinen Zustandsautomaten in Form eines Integer Arrays finde ich unnötig komplex:
{1,2,3,6,5,6,-1,4,-1},
{0,1,2,4,7,4,-1,8,-1}
Warum an der siebten Stelle -1 und kein 7 steht, kann ich spontan nicht sagen. Da finde ich die Lösung von Björn mit benannten Zuständen viel selbsterklärender.
Den Umfang des Codes Deiner Lösung finde ich für die einfache Kata etwas übertrieben. Ich bin ein Minimalist mit dem Motto "less is more". Besonders im Code halte ich das für sehr wichtig. Weniger Code bedeutet weinger Wartungsaufwand, weniger Einarbeitungszeit, weniger Platz für Defekte. Aus diesem Grund habe ich mit meiner ersten C# Version nicht aufgehört und habe noch eine PowerShell Version entwickelt, weil ich glaubte, dass meine Tests noch einfacher und expressiver sein könnten.
Was ich in Deiner Lösung aber sehr gut finde, sind die Skizzen. Das soll ich demnächst auch mal probieren. Allerdings, würde ich die Skizzen in der produktiven Anwendung sofort wegwerfen, da ich glaube, dass die Dokumentation nie veraltet werden darf. D.h. soll die Dokumentation ausführbar sein - Tests zum Beispiel.
Ich hoffe, dass wir auf dem kommenden ArchOS auch ein CodingDojo durchführen können um wieder von einander was zu lernen.
@JakeJBlues: Wikipedia sagt zu Modell dies: http://de.wikipedia.org/wiki/Modell
Da bin ich grundsätzlich dabei.
Ein Beispiel für Modell ist auch Landkarte. Die Landkarte ist nicht die Realität - das ist ihr Zweck. Bewegen tut man sich am Ende im Gelände, aber den Überblick gewinnt man auf der Karte. Wer ohne Karte ins Unbekannte fährt, der kann sicherlich Abenteuer erleben. Wunderbar, wenn man Urlaub machen möchte. Aber z.B. für eine militärische Aktion ungeeignet. Da werden Karten studiert - oder extra aufgeklärt.
Und beim Programmieren ist ein Modell die Beschreibung der Lösung mit anderen Mitteln als der Programmiersprache - und auch einem höheren Abstraktionsniveau.
Ein Zustandsautomateb, EBC-Diagramme, ER-Diagramme... das sind Modelle (bzw. eigentlich erstmal Metamodelle). Wie die am Ende implementiert werden? Egal. Direkt in Assembler oder mit C#, objektorientiert oder mit Funktionaler Programmierung... alles ist möglich.
@Sergey: "Deinen Zustandsautomaten in Form eines Integer Arrays finde ich unnötig komplex":
Hm... was ist an den ursprünglichen 5 Zeilen komplex? Komplex im Sinne von "durch Nachdenken wird man nicht schlauer".
Oder meinst du kompliziert (im Sinne von "durch Nachdenken wird man schlauer")?
Sind die 5 Zeilen kompliziert? Zu kompliziert? Hm... Sie sind zugegebenermaßen nicht superleicht zu lesen. Allemal, da ich das Mapping der Zustände im Modell auf die Zustandsindizes nicht deutlich gemacht hatte im ersten Anlauf.
Aber: Hey, es ist KISS. In jedem Fall ist eine 5 Zeilen-Lösung (auch wenn sie unwissentlich knapp daneben lag) weniger kompliziert als eine 50+ Zeilen-Lösung. Denn 50+ Zeilen wollen geschrieben, getestet und verstanden werden. Man sage mir nicht, dass das schneller ging als meine 5 Zeilen.
"Den Umfang des Codes Deiner Lösung finde ich für die einfache Kata etwas übertrieben.": Warum ist meine Katalösung zu umfangreich? Weil ich zwei Projekte gemacht habe?
Dagegen halte ich, dass für einfache Katas womöglich gar keine Tests nötig sind. Warum also welche schreiben? Bläht den Umfang nur auf.
Zwei Projekte, ein Modell mit zwei Diagrammen: Das ist nicht zu umfangreich, sondern das ist der Zweck. Darin liegt die Übung.
Katas sind nicht dazu da, einen "Ich habs irgendwie geschafft"-Kick zu bekommen, sondern eine "Programmierdisziplin" zu üben. Die meisten beschränken sich da auf TDD. Auch schön. Ich seh es breiter. Und das ohne Einbuße an Geschwindigkeit, denke ich. Aber selbst wenn... Mach du die Kata in 20 Minuten ohne Modell und mit der Hälfte der Codezeilen. Wenn das dein Übungszweck ist - schnell sein und Codezeilen sparen -, dann gut. Meiner ist es nicht. Das muss ich nicht üben, weil es für realistische Programmierung keinen Sinn hat.
Auf dem ArchOS können wir weiter drüber plaudern.
Hallo Ralf,
OK, dann haben wir einen gemeinsame Grundlage. Die Frage ist nun, ob ein Entwickler grundsätzlich abstrahieren muß oder nicht. M.E. gibt es nun einmal sehr viele Entwickler, die einfach nur froh sind, wenn Sie das Problem in einer Programmiersprache irgendwie umsetzen können. Hier ist es ein wenig wie in der Mathematik ;-) Nehmen wir mal den Satz von Pythagoras. Ich fürchte die Diskussion ist hier, soll ich als Beweis den geometrischen oder den "schönen abstrakten" im Vektorraum benutzen. Der Vorteil des geometrischen ist es, dass ein nicht so hohes "Abstraktionsvermögen" vorrausgesetzt wird.
Hoffe Du stimmst mir zu, wenn ich zusammenfassend behaupten würde, wenn ich "das modellieren" bei den Entwicklern bemängle, dann sollte ich Ihnen zuerst Lösungen vorstellen, welche vielleicht nicht die elegantesten sind, aber (einfach) nachvollziehbar.
Denke einfach, demnächst an den Pythagoras und den "Vektorraum" ;-)
Gruß JJR
Hallo zusammen,
ich hätte da noch zwei Anmerkungen zu diesem Thema.
1. Modellierung
Es gab die Kritik, dass sich trotz vorheriger Modellierung seitens Ralf ein Fehler eingeschlichen hat. Hm... Voreiliger Schluss Einiger: Modellierung schützt mich (anscheinend) nicht vor Fehlern.
Meiner Meinung nach ein Fehlschluss. Wäre dieser Fehler wirklich bis in die Implementierung gekommen, wenn Ralf sein Modell mit dem Kunden durchgesprochen hätte? Glaube ich nicht. Ganz im Gegenteil. Ginge es hier nicht um eine Kata, sondern ein reales Projekt, wäre der Fehler wahrscheinlich schon durch eine Besprechung des Modells mit dem Kunden gefunden worden.
Punkt geht also an die Modellierung.
2. Evolvierbarkeit
Das geht jetzt direkt @ralf:
Finde ich gut, dass Du ganz einfach eine Lösung zur Behebung des Fehlers gefunden hast. Das hat gezeigt, dass Deine Lösung auf jeden Fall evolvierbar war. ABER: Das was da jetzt nach Deiner Änderung an Code liegt ist nicht mehr evolvierbar!
a.) Es wird mir suggeriert, dass die komplette Business-Logik in der Klasse Player liegt. Einfach, weil dort offensichtlich eine State-Machine "lebt". Automaten sollten meiner Meinung nach immer in sich geschlossene Einheiten sein. Jetzt "lebt" aber auf einmal ein Zustandsübergang in der Klasse Determine_Winner.
b.) Von einer Funktionseinheit mit dem Namen Determine_Winner erwarte ich nicht, dass sie auch nur ein einzelnes Bit in meinem Objektbaum verändert. Hm... bei näherem Nachdenken doch. Sie könnte vielleicht den Gewinner des Spiels setzen. Aber ich erwarte nicht, dass sie den Spielstand von 40-40 auf Deuce setzt.
Meine Erkenntnis in Bezug auf Evolvierbarkeit wäre in diesem speziellen Fall also: Evolvierbarkeit bedeutet nicht nur Fehler einfach beseitigen zu können, sondern auch, Fehler einfach lokalisieren zu können. Letzteres finde ich leider in dem aktuellen Code nicht mehr so einfach.
MfG,
Nils
Der Schluss war keineswegs voreilig sondern wohldurchdacht. Der Zustandsübergangsgraph war im ersten Anlauf so, dass man ihn für korrekt hätte halten können. Wie ich oben schrieb:
"in der Skizze gibt es zwei rote Pfeile, die aus dem Zustand 40 ausgehen, und die man durchaus so interpretieren kann, dass es von der Anzahl der Punkte des Gegners abhängt, ob der Automat im Zustand 40 bleibt oder in den Zustand deuce geht"
und weiter
"Die Korrektheit des Programms lässt sich also offensichtlich nicht aus der (unterstellten) Korrektheit der Skizze ableiten."
Mit der "unterstellten Korrektheit" war gemeint, dass der Auftraggeber die Skizze begutachtet und sie für korrekt hält. Es ist menschlich, nicht auf dem Schirm zu haben, dass eine Spezifikation so krass missverstanden wurde.
Zugegeben das war ein spezieller Fall. Aber in diesem speziellen Fall halte ich es für durchaus wahrscheinlich, dass der Fehler nicht rechtzeitig aufgefallen wäre.
@Krzysztof: Anforderungen können immer missverstanden werden. Und die Beschreibung einer Lösung kann immer korrekt aussehen, aber falsch sein. Natürlich zählt daher am Ende nur der Code und erfolgreiche Akzeptanztests.
Auf welcher Ebene ist es aber leichter, Verständnisfehler zu erkennen? Ich behaupte weiterhin: auf der Modellebene.
Noch ein Beispiel aus der Realität: Morgen mache ich einen Review. Ich kenne die Anwendung noch nicht. Was habe ich davon, wenn mir die Entwickler mit Klassendiagrammen kommen? Was habe ich davon, wenn sie mir mit Sourcecode kommen?
Nichts.
Ich verstehe dann nicht oder nur sehr schwer, was das Ganze soll. Auch ein normales Blockdiagramm hilft da wenig.
Was ich brauche, um in eine Anwendung einzusteigen, sind Modelle. Und zwar mehrere, auf unterschiedlichen Abstraktionsebenen und unterschiedlicher Art (Daten, Funktionalität).
Wer die erst am Ende produziert, benimmt sich der Vorteile, die sie für die Lösungsfindung bieten.
Und ich bezweifle, dass du ohne Modelle auskommst. Die Frage ist vielmehr: Warum explizieren die Entwickler ihre Modelle so selten und glauben stattdessen, dass sie sie am besten gleich in Code gießen sollten?
@nsteenbock: Liegt der Zustandsautomat nun in zwei Funktionseinheiten? Hm... nein, ich sehe das nicht so.
Der Zustandsautomat in Player wird getriggert. Es kommen Ereignisse von außen. Ein Ereignis ist, dass ein Spieler einen Ball gewinnt. Ein anderes ist, dass beide Spieler Gleichstand bei 40 haben.
Der Automat reagiert auf beides mit einem entsprechenden Zustandswechsel.
Die Funktionseinheit Determine_winner verändert keinen Zustand. Es fließt aus ihr die Information über den Gewinner hinaus. Schau dir das EBC-Diagramm nochmal genau an.
@Krzysztof
Mit voreilig meinte ich nicht, dass da einfach ohne Nachdenken eine Meinung kundgetan wurde. Aber egal - streichen wir einfach das Wort [i]voreilig[/i].
Ich bin immer noch der Meinung, dass ein Modell hilft, solche Fehler zu vermeiden:
[i]"in der Skizze gibt es zwei rote Pfeile, die aus dem Zustand 40 ausgehen, und die man durchaus so interpretieren kann, dass es von der Anzahl der Punkte des Gegners abhängt, ob der Automat im Zustand 40 bleibt oder in den Zustand deuce geht"[/i]
Exakt. Das ist doch ein Punkt. Das Modell war nicht gut genug, weil es Interpretationsspielraum gelassen hat. Quasi ein Bug in der Modellierung. Behebt man jetzt diesen Bug oder sagt sofort "Modelle sind fehleranfällig, also lieber sofort implementieren"? Nur mal überspitzt formuliert.
@ralf
Ich hasse es, wenn Du Recht hast! ;-)
Ja, wenn man konsequent in EBCs denkt, dann ist das vollkommen korrekt. Ich tue mich damit aber trotzdem noch schwer. Der Score eines Spielers ist per Definition abhängig vom Score eines anderen Spielers. Bei "traditioneller" Implementierung würde das für mich bedeuten, dass ein entsprechender Zustandsautomat nicht innerhalb des Spieler-Objekts leben kann.
Bei EBCs geht es ja genau darum: Abhängigkeiten. Du hast gezeigt und jetzt auch mich überzeugt, dass man genau solche Probleme auflösen kann. Aber geht es noch ein wenig expliziter? So, dass ich das auf Anhieb sehe, dass hier nicht der vollständige Automat definiert ist?
Grüsse,
Nils
@nsteenbock: Ich finde das Modell aus zwei Gründen ausreichend:
1. Es spiegelt die Realität recht gut wider. Da gibt es Spieler und einen Schiedsrichter. Und darauf bin ich ohne Krampf gekommen.
2. Das Modell war flexibel genug, einen Fehler sehr einfach zu beheben. Ich sehe auch nicht, warum diese Eigenschaft nun verschwunden sein soll.
Mehr als gut genug im Verhältnis auch zu anderen Werten wie Entwicklungsgeschwindigkeit brauche ich auch nicht. Nur weil ich modelliere muss ich nicht perfekt sein. Auch Modelle dürfen sich weiterentwickeln und refaktorisiert werden.
@ralf
Ja, das Modell ist sicherlich in Ordnung. Was ich nicht gesehen habe war, dass ich mein Problem schneller finde, wenn ich das Modell anschaue. Im Code war es für mich schwerer auf den ersten Blick zu sehen. Im Zusammenhang mit dem Modell jedoch ganz klar.
Das liegt natürlich daran, dass ich noch nicht so gefestigt in der EBC Denkweise bin(nehme ich jedenfalls an). Allein die Tatsache, dass es hier zu einem Missverständnis gekommen ist, muss aber meiner Meinung nach mindestens mal hinterfragt werden.
Entwicklungsgeschwindigkeit bringt mir nichts, wenn ich bei der Fehlersuche Zeit zusätzlich investieren muss. Ich habe gefragt, wie man dieses Modell "expliziter" in den Code bringen kann. Einfach, dass ich merke, dass diese Dictionarys nicht die komplette Wahrheit sind bzw. nicht der vollständige Automat.
Hm... nach einigem Nachdenken: Vielleicht sind es nur minimale Details, die mich vom Weg abgebracht haben. Eigentlich sind es 2 Sachen.
1. In_Deuce ... Ja, das ist meinem "noch nicht vollständig in EBC denken" geschuldet. Man kann es auch Schludrigkeit nennen. Ah, InDeuce ist bestimmt eine Methode, die einen Boolean zurückliefert(Spieler befinden/befindet sich im Zustand Deuce).
2. Erst kommt die Definition des Zustandsautomaten, dann kommt mehr oder weniger Boilerplate-Code, wie dieser Zustandsübergang vollzogen wird. Dann kommt wieder ein Zustandsübergang, der von aussen als Event getriggert wird.
Für mich persönlich wäre alles alleine durch eine Änderung der Reihenfolge von AdjustScore und In_Deuce klarer geworden. Wenn wir nach Uncle Bob gehen, dann würde ich sagen Adjust_Score sollte so nah wie möglich an den Zustandstabellen sein. Vielleicht verlangen EBCs aber auch nach einer anderen Strukturierung?
MfG,
Nils
Kommentar veröffentlichen