Softwareentwicklung mit Energie – die war wieder mal zu sehen beim Coding Dojo in München. Knapp 30 Entwickler waren zusammengekommen, um sich in geselliger Runde in der .NET Programmierung anhand einer Aufgabe zu üben.
Das Coding Dojo Muc hat sein Format und seinen Rhythmus. Im Fokus steht gewöhnlich die testgetriebene Entwicklung (TDD) einer Funktionalität, die sich mit einer Funktion als API ausdrücken lässt (Beispiele: FizzBuzz, LOC Counter). Das Motto: Alles denkt, einer tippt.
Doch dieses Mal war vieles anders…
Der Ablauf
Dieses Mal hatte Ilker mich eingeladen, um in das Dojo ein wenig architektonisches Denken hineinzutragen. Das Motto: Alles denkt, keiner tippt ;-) Zumindest nicht die ersten zwei Stunden.
Die Frage nun: hat das geklappt?
Die Aufgabe
Um architektonisches Denken zu üben, musste die Aufgabe natürlich etwas größer ausfallen als Dojo-gewöhnlich. Auf der anderen Seite durfte sie nicht zu groß sein, denn ich wollte ja auch mit Ergebnis mit den Teilnehmern erzielen.
Angemessen erschien mir schließlich eine Spellchecker-Anwendung: Das Programm sollte Texte in verschiedenen Sprachen auf Rechtschreibfehler überprüfen. Wenn man so ein Programm aufsetzen will, sind mehr als eine architektonisch relevante Funktionseinheit nötig; Architektur lohnt sich also.
Der Umfang dieser Aufgabe führte dann gleich zu einer ersten Neuerung bei Dojo: der Anforderungsanalyse. Nicht, dass bisher die Teilnehmer nicht versucht hätten, die Aufgaben zu verstehen. Aber so ein Programm machte eine systematischere Herangehensweise nötig. Die bestand aus zwei Schritten:
1. Featureliste
Im ersten Schritt haben wir die Anforderungen mündlich ventiliert. Da kamen einige Fragen an den “Kunden” zusammen. Und der Kunde blieb keine Antwort, keinen Wunsch schuldig – allerdings konnte nicht alles wirklich in das Backlog übernommen werden. Wir hatten ja nur einen Abend für die Realisierung zur Verfügung. Nichtsdestotrotz war die Diskussion wichtig, um die Gruppe auf das Thema einzuschwingen.
Was am Ende dann realisierbar erschien, haben wir in eine Featureliste übertragen. Wichtig dabei: Nur aus Kundensicht Relevantes ging darin ein.
2. Ubiquitous Language
Die Featureliste hat den generellen Scope der Entwicklung abgesteckt. Sie hat eine Grenze gezogen. Wie sah aber das Terrain innerhalb dieser Grenze aus? Wie sollten Problem und Lösung zusammenfinden?
Um einen Übergang in die Lösungswelt zu finden, haben wir im zweiten Schritt für die Features eine Ubiquitous Language mittels einer Concept Map erarbeitet.
Darin wurden die zentralen Begriffe der Domäne nicht nur gelistet, sondern auch qualifiziert in Beziehung gesetzt. Interessante Erkenntnisse wurden dadurch herausgearbeitet:
1. Um sich unzweideutig über die Domäne unterhalten zu können, ist zwischen Prüfworten und Fehlerworten zu unterscheiden. Prüfworte sind die, die auf Korrektheit geprüft werden müssen; Fehlerworte sind die, die als Inkorrekt erkannt wurden.
Fehlt die Unterscheidung, wird die Rede über die Domäne im besten Fall mühsam, im schlimmsten Fall jedoch Missverständlich.
2. Nachdem das Flipchart schon fast voll war, stellte sich die Frage, wer denn überhaupt die Rechtschreibprüfung vornehmen würde. Ihre Elemente (z.B. Prüfworte, Wörterbuch) waren vorhanden, es fehlte aber eine Instanz, die damit umging.
Diese Instanz war in der Diskussion immer implizit vorhanden. Das Team hatte sich oder das ganze Programm dafür in der Verantwortung gesehen. Das ist aber natürlich ungenügend, weil trivial bzw. impraktikabel. Es gilt vielmehr: Zentrale Domänenaufgaben müssen als “Bubbles” in einer Concept Map der Ubiquitous Language auftauchen.
Und so wurde spät aber doch der Prüfer als Vokabel in die Ubiquitous Language aufgenommen. Er prüft, ob ein Wort im Wörterbuch und damit korrekt oder als Fehlerwort zu melden ist.
Die Architektur
Nach soviel Vorgeplänkel waren die Teilnehmer natürlich gespannt darauf, wie denn aus den Anforderungen eine Architektur entstehen würde.
Einige wollten mit Klassendiagrammen anfangen. Andere wollten Schichten übereinander malen. Doch ich hatte mich entschieden, eine Architektur “nach der neuesten Mode” zu versuchen. Event-Based Components (EBC) sollten es sein.
Da keine Zeit für lange Erklärungen des Konzepts sein würde, fand ich mich mit dem EBC-Ansatz recht mutig. Aber es half nichts: Ich wollte einfach nicht mehr überholte Ansätze wie IBC (Injection-Based Components) widerkäuen. Viel simpler wäre es auch nicht nicht geworden. Womöglich sogar schwieriger, weil sich Abstraktionsniveaus mit IBCs schlechter modellieren lassen.
Also bin ich mit einem System-Umwelt-Diagramm eingestiegen und habe dessen System dann als “Hauptplatine” einer EBC-Architektur verfeinert und bin dann nochmal eine Ebene tiefer in den SpellChecker (SC) hinunter gestiegen:
In drei Abstraktionsschritten von den Anforderungen zur Architektur. Das war sicherlich überraschend für einige Teilnehmer.
Interessanterweise gab es aber weder dazu noch zu den “komischen Pfeilen”, die ja nicht wie üblich Abhängigkeiten, sondern Nachrichtenflüsse darstellen, grundsätzlich kritische Kommentare. Puh. Das hat mich erleichtert.
Die Kontrakte
Nach soviel Gerede und soviel Malerei stand dann zum Glück aber etwas Code auf dem Programm. Es galt, die die Kontrakte der ermittelten Komponenten festzuzurren. Trotz EBC bleibt Contract-First Design zentral für eine Architektur, die in der Implementierung nicht erodiert.
Für die Kontrakte (und den weiteren Code) hatte ich schon ein Google Project aufgesetzt und mit einer Visual Studio Solution ausgestattet. In die haben wir die Servicekontrakte für die Funktionalität und die Nachrichtenkontrakte “hineingegossen”:
Je Komponente (Bauteile und Platinen) ein Interface. Interface-Abhängigkeiten gibt es nicht – das ist ja der Trick bei EBCs.
Hier ein Beispiel für einen Servicekontrakt:
Und hier ein Teil des Nachrichtenkontrakts:
Die Vokabeln der Ubiquitous Language tauchen konsequenterweise darin wieder auf.
Die Implementation
Nach mehr als 2,5 Stunden war es dann soweit. Die Teilnehmer konnten in die Tasten greifen. Vier kleine Teams gingen daran, die Bauteil-Komponenten zu implementieren. Auch Gastgeber Ilker ließ es sich nicht nehmen, mitzumachen.
Anders als üblich arbeiteten die Teams allerdings still. “Entertainment” hatte es genug gegeben. Jetzt sollte nicht mehr lange (öffentlich) diskutiert werden. Gummi musste auf die Straße.
Um aber nicht nur Gummi bei der Implementation, sondern auch bei der Integration auf die Straße zu bekommen, schlug ich vor, zunächst nur Minimalfunktionalität ohne Tests zu implementieren. Das versetzte mich in die Lage, die Platinen zu prüfen.
Also machte der USB-Stick ein erstes Mal die Runde, um den Teams die Kontrakte zuzuspielen. Und dann ein zweites Mal die Runde zurück zu mir, um ihre rudimentären Implementationen abzuliefern. Darauf aufbauend war meine Aufgabe, die Hauptplatine und die SpellChecker-Platine zu implementieren und alles mit einer kleiner Host-EXE zu starten.
Anschließend gaben die Teilnehmer alles, um ihre Implementationen auszufleischen.
Um 22:17h war es dann soweit: Die Anwendung lief. Texte konnten gegen ein englisches und ein deutsches Wörterbuch geprüft werden. Dabei holperte es noch etwas im UI und die Wörterbücher waren mager – aber “der Kunde” konnte sehen, dass seine Anwendung lief.
Reflexion
Unterm Strich war das Coding Dojo ein Erfolg. Ich bin grundsätzlich zufrieden mit dem Ergebnis. Auch wenn es gedauert hat und wir über Stolpersteine gelaufen sind, gab es am Ende eine lauffähige Anwendung, die von einem verteilten Team realisiert worden ist.
Was ist gut gelaufen?
Ich würde sagen, dass das grundsätzliche Vorgehen gepasst hat. Anforderungen verstehen, Features aufschreiben, Context Map, Kontrakte implementieren: der Fluss war gut. Es gab allerdings Kritik, dass ich dabei zuviel angeleitet hätte. Diese Kritik kann ich gut nachvollziehen, doch ich sehe nicht, wie es hätte anders laufen können.
Wenn bei sonstigen Dojos nach 3 Stunden eine Funktion realisiert ist, dann wäre bei einer ganzen Architektur am Ende kaum etwas Lauffähiges herausgekommen. So schön eine gemeinsame Entwicklung durch die Teilnehmer gewesen wäre – sie hätte nicht mehr als ein gutes Erlebnis produziert. Da bin ich mir sicher. Der Grund: Wenn schon bei der Implementation einer Funktion die Meinung weit auseinandergehen, dann ist das noch mehr der Fall beim Thema Entwurf. Die unterschiedlichen Ansätze, ein leeres Flipchart-Blatt zu füllen, sind dafür der Beweis.
Ziel war nicht, die Gruppe “irgendwie” zu einem Ergebnis kommen zu lassen, sondern einen Weg systematisch zu beschreiten. Das bedarf der Anleitung.
Ich behaupte nicht, dass der Weg, den ich durch den Architekturdschungel “vorgetrampelt” habe, der beste ist. Aber ich behaupte, dass er lohnenswert war, einmal erfahren zu werden. Ich bezweifle, dass viele von den Teilnehmern in ihren Projekten je erlebt haben, wie nach einer sauberen Planung echt parallel und ohne Codekollisionen entwickelt werden kann. Das zu demonstrieren, war mein Anliegen.
Für eine “Diskussionskuschelrunde” mit etwas Architektur hätte Ilker mich auch nicht einladen müssen.
Bei einem nächsten Übungsprojekt wäre es möglich, mehr Arbeit ins Team zu verlagern. Dann würde sich zeigen, ob es etwas vom Vorgehen mitgenommen hat. Bei diesem ersten Mal war jedoch Anleitung in Form eines “fragend entwickelnden Unterrichtsgesprächs” und einem Architekturparadigma nötig.
Gefreut hat mich auch, dass die Concept Map so gut aufgenommen wurde und die Ubiquitous Language sich selbst bei diesem kleinen Projekt als hilfreich herausgestellt hat. Ich hatte mich zunächst gefragt, ob der Aufwand lohnt – doch jetzt bin ich gewiss. Die Concept Map sollte nie fehlen.
Basierend auf der Concept Map habe ich dann auch die Nachrichtenkontrakte gestaltet. Darin kamen im Grunde keine einfachen Typen mehr vor. Statt eines string hat das Frontend ein PrüfTextPaket geschickt. Das hat niemand als Overkill bemängelt. Sehr schön. Ich fühle mich in dem Ansatz bestätigt, zumindest auf Komponentenebene im Grunde keine primitiven Typen mehr einzusetzen. Man vergibt sich mit primitiven Typen nämlich ein Mittel, den Code verständlich zu machen. Aber dazu mehr in einem anderen Blogartikel.
Cool war es zu sehen, wie motiviert alle mitgedacht haben. Soviel Offenheit und Durchhaltevermögen bei Architekturdiskussion! Toll! Und dann auch zu später Stunde noch viel Ehrgeiz bei der Implementation. Wahnsinn!
Was ist weniger gut gelaufen?
Bis zur Implementation ist alles nach Plan gelaufen. Doch dann… Zwar hatte ich den richtigen Gedanken, Integrationsproblemen mit einer “Tracer Bullet” “Kurzimplementation” vorzubeugen. Aber die Ausführung habe ich schlecht vorbereitet.
Das Problem war nicht so sehr, dass Code nur per USB-Stick die Runde machen konnte. Schöner wäre zwar gemeinsamer Zugang mit Mercurial Repository gewesen… doch es ging auch so. Nur langsamer. Viel schlimmer war, dass ich zuwenig über Konventionen gesprochen habe. Denn wo Tools fehlen, müssen Konventionen her.
Die binären Kontrakte haben natürlich die grundsätzliche “Zusammensteckbarkeit” der getrennt entwickelten Komponenten garantiert. Das hat funktioniert. Wir hatten kein wirkliches Integrationsproblem der Assemblies.
Wie der Code aber an mich ausgeliefert wurde, das war ein Problem. Visual Studio kann ja in so unterschiedlicher Weise benutzt werden. Da habe zuwenig erklärt bzw. vorgegeben. Besser wäre es gewesen, ich hätte für jedes Komponenten kurz eine Rumpf-Projektmappe aufgesetzt. Das hätte zwar etwas Zeit gekostet – am Ende wäre dadurch aber so mancher Reibungsverlust vermieden worden.
Lehre #1: Solange Konventionen für die Codeorganisation unbekannt sind oder noch nicht verlässlich eingehalten werden, sollten Komponentenimplementationen als Rahmen vorgegeben werden. Dann gibt es keine Frage, wohin der Output-Path zeigen soll, woher NUnit kommt, wo die Tests zu hinterlegen sind, wie die Bennungen sind, so dass der autom. Buildprozess schlank bleibt.
Kreativität ist ja eine gute Sache – aber sie sollte einen Rahmen haben. Dass z.B. Entwickler die Freiheit haben, Komponenten nicht gem. der Ubiquitous Language zu benennen, ist falsch verstandene Kreativität.
Lehre #2: Die Ubiquitous Language ist heilig. Spaß mit Benennungen mögen Entwickler vielleicht hier und da in ihren Komponenten haben. An den Schnittstellen jedoch und auf Architekturebene ist dafür kein Raum. Verständlichkeit entsteht nur, wenn die gemeinsame Sprache auch wirklich gesprochen wird.
Die Zeit war am Ende knapp. So haben wir alle auf eine erfolgreiche Integration gestarrt. Darunter hat die Codequalität gelitten. TDD im Speziellen war dieses Mal nicht wirklich ein Thema. Und Clean Code Developer Bausteine im Allgemeinen (jenseits der Architektur) auch nicht. So haben sich – wie ein Blick in den Code zeigt – Unschönheiten eingeschlichen. Manche davon waren sogar auf der Projektion zu sehen, weil sie mit dem GUI zu tun hatten.
Lehre #3: Keine Integration ohne Code-Review. Soviel Zeit muss sein. Für den Event war es ok, mal ohne Review zu leben. Es ging ja vordringlich um den Architekturentwurf. Die Implementierung am Ende war Kür und Spaß. Wenn es jedoch etwas ernster wird, dann gibt es kein “Done!” ohne Review.
TDD erleben, ist eine gute Sache. Architekturen mit “parallel rauchenden Köpfen” umsetzen, ist aber auch eine gute Sache. Beides sollten Entwickler öfter tun. Für TDD mag dabei eine Projektion reichen. Beim parallelen Entwickeln ist jedoch erstens ein Coderepository und zweitens ein Zugriff für alle darauf unverzichtbar.
Lehre #4: Das Google Projects Repo war eine gute Sache - allerdings nur für mich. Das sollte beim nächsten Mal anders sein. Wie könnte das gehen? Mehr Teilnehmer mit einem UMTS-Stick. Oder ein WLAN mit einem Rechner, der ein Repo hostet. Oder ich muss einfach auf dem Zettel haben, dass ich ja einen WLAN UMTS Stick habe, über den ich auch anderen Internetzugang geben kann. Die 4 Rechner hätte der wohl stemmen können.
Schwieriger wird es bei der Versionskontrollsoftware. Viele sind mit SVN oder TFS vertraut. Mit denen würde ich soetwas aber nicht machen wollen. Ein verteiltes Versionskontrollsystem wie Mercurial (oder auch Git) ist viel besser geeignet für verteilte Teams. Da kann jedes Team z.B. in mehreren Commits bis zur Tracer Bullet Implementation kommen, die mit einem Push auf den Server schieben und dann lokal weiterarbeiten, während die Integration sich den Tracer Bullet Stand von allen mit Pull herunterlädt.
Beim nächsten Mal versuche ich es mit TortoiseHg für alle. Das ist einfach zu bedienen. Damit bekommt jeder ein Clone, ein Commit und ein Push hin. Insbesondere ich Mercurial auch sehr tolerant gegenüber Löschungen und Umbenennungen.
Fazit
Trotz einiger Stolpersteine ist das Coding Dojo aus meiner Sicht schön gelaufen. Mein Eindruck war, die Teilnehmer haben etwas mitgenommen. Und sie haben auch Spaß gehabt. Sonst wären sie nicht bis zum bitteren Ende nach knapp 4 Stunden geblieben. Super! Danke an alle fürs Kommen und Mitdenken und Mitmachen.
Und Danke an Ilker für die Einladung. Und Danke an Microsoft für Raum und Annehmlichkeiten. Von mir aus können wir das gern wieder mal machen ;-)
Wem es Spaß gemacht hat und wer tiefer eintauchen möchte in systematischen Softwareentwurf und komponentenorientierte Entwicklung, der kann ja mal überlegen, ob er/sie beim Clean Code Developer Praktikum im Juli mitmachen will. Das bietet 5 Tage Praxis pur – und ist auch noch kostenlos.
3 Kommentare:
ECB funktioniert in der Auspregung aber nur wenn alle Komponenten im selben Prozess leben, oder? Die Kontrakte mit Action und Func sind nicht (XML) serializierbar -- kann so eine Architektur auch dann funktionieren, wenn eine der Komponenten hinter einem Web Service lebt? In anderen Worten, kann man damit ein Request/Response Pattern bei auch bei Web Services ersetzen?
@Robert: EBCs funktionieren wunderbar in asynchronen und verteilten Szenarien. Zur Asynchronizität habe ich in diesem Blog schon etwas geschrieben. Zur Verteilung werde ich das nachholen.
-Ralf
PS: Die Pins müssen ja nicht serialisierbar sein, sondern nur die Nachrichten.
Ralf,
kannst du die Bilder in größerer Version verlinken? Dann könnte ich diese lesen ;-)
Gruß,
Sebastian
Kommentar veröffentlichen