Follow my new blog

Sonntag, 18. Juli 2010

Die Rückkehr der Täter – EBC-Prozesse übersetzen

Einen Moment das objektorientierte Denken auszusetzen, darum hatte ich Sie in ???meinem Blogartikel über die nächste Entwicklungsstufe von EBCs gebeten. Ich weiß, das ist eine große Bitte gewesen. Deshalb möchte ich Ihr Wohlwollen auch nicht länger strapazieren. Kommen Sie zurück in die Welt der Objekte bzw. Klassen und der Komponenten.

In diesem Artikel möchte ich Ihnen zeigen, wo Klassen ihren Platz auch in einer Welt der Featureprozesse haben, d.h. der Prozesse, die ein Anwendungsfeature realisieren.

Übersetzung in Klassen

Bisher habe ich vorgeschlagen, Anforderungen in Features zu zerlegen, von denen jedes durch einen Prozess “hergestellt” wird. Hier nochmal das Feature “Funktion plotten” als EBC-Prozess:

image

Initiator des Prozesses ist das GUI, Empfänger für das Resultat ebenfalls. Ich denke, soweit bereitet Ihnen das Verständnis keine Probleme.

Allerdings werden Sie sich fragen, wie das Prozessdiagramm in Code übersetzt werden kann. Abgesehen vom GUI, d.h. einer Formularklasse, ist darin ja noch keine Klasse zu sehen. Beschrieben sind lediglich Taten, aber keine Täter. Das ist natürlich gewollt, denn sonst wäre das Diagramm kein Modell. Modelle sollten nämlich nicht mit dem Vokabular der Implementation beschrieben werden.

Also: Wo sind die Klassen?

Aus meiner Sicht ist der Default, jede Tat, jede Aktivität, jeden Prozesschritt in eine Klasse zu übersetzen. Das sähe dann so aus:

image

Damit machen Sie nichts falsch. Das entspräche den bisherigen Übersetzungsregeln für EBC-Bauteile und –Platinen. Bitte beachten Sie jedoch: Auch wenn Sie Aktionen in Klassen übersetzen, werden daraus nicht gleich Täter. Sie würden den Prozessschritt “In Symbole zerlegen” nicht als Klasse Scanner implementieren. Wenn Klassen Aktivitäten repräsentieren, dann sollten sie auch so benannt sein:

class Funktion_in_Symbole_zerlegen
{
    public void In_Process(string funktion)
    {…}

    public event Action<Symbol[]> Out_Symbole;
}

Da nun jedoch die “Kästchen” in den EBC-Diagrammen nicht mehr durch Substantive beschrieben werden, da sie keine Täter, sondenr Taten sind, liegt es für mich sehr viel näher als bisher, allemal die atomaren Aktionen nicht in Klassen, sondern in Methoden zu übersetzen. Klassen weisen dann mehrere solcher Methoden-Taten zu einem Täter zusammen:

image

Eine Klasse könnte dann so aussehen:

class Funktionsanalyse
{
    public Symbol[] In_Symbole_zerlegen(string funktion)
    {…}

    public AST Auf_syntaktische_Korrektheit_prüfen(Symbol[] symbole)
    {…}
}

Wann sollten Prozessschritte Methoden werden, wann Klassen? Hier sind verschiedene Prinzipien in Anschlag zu bringen. Wenn die Methoden sehr überschaubar sind und nicht in viele weitere zerfallen, um dem SLA-Prinzip zu dienen, dann würde ich dazu raten. (Im vorliegenden Beispiel des Funktionsplotters ist das eher nicht der Fall. Gerade ein Scanner kann schon sehr umfangreich werden.) Auch könnte für Methoden sprechen, dass sie gemeinsamen Zustand haben – wobei anzumerken ist, dass der eine Abhängigkeit darstellen würde, die man aus einem Prozessdiagramm nicht herauslesen kann. Das wäre der Evolvierbarkeit abträglich.

Oder lassen Sie es mich anders sagen: Methoden sind absolut ok, um Ihnen die Arbeit etwas leichter zu machen, wenn Ihnen kein Prinzip entgegensteht. Vor allem könnten das SRP und SoC sein.

Täter durch Abstraktion

Vergleichen Sie nun die Klassennamen der Übersetzung in Klassen und der Übersetzung in Methoden. Sehen Sie den Unterschied? Bei einer Übersetzung in Klassen beschreibt der Klassenname die Aktivität, die Klasse ist eine Tat. Anders ist es bei der Klassen, die Prozessschritte als Methoden implementiert. Ihr Name beschreibt einen Verantwortlichen, sie ist ein Täter.

Dieser Wechsel in der Semantik ist nötig, da ein Menge von Methoden, d.h. Taten nicht ohne weiteres zusammen eine “Großtat” ergeben. Wer sie zusammenfasst, ist also keine Tat, sondern tut viele Taten und ist somit ein Täter. Ein Substantiv als Name ist angezeigt.

Mit der reinen Objektorientierung können Sie quasi nicht anders: Sie suchen sofort nach Tätern. Sie hätten sich gefragt, welche Verantwortlichen an einem Funktionsplot beteiligt sein könnten. Und da wären Ihnen sofort Scanner, Parser, Codegenerator, Plotter usw. Dann hätten Sie die in irgendeiner Form in Abhängigkeiten zueinander gesetzt (Klassendiagramm, Schichtendiagramm o.ä.).

Mit EBC-Prozessdiagrammen jedoch müssen Sie sich nicht so anstrengen. Sie listen einfach die Tätigkeiten, die ein Feature ausmachen. Das ist ein viel kleinerer Schritt, wenn Sie von den Anforderungen ausgehen.

Und erst danach schauen Sie, ob Sie mehrere Prozessschritte demselben Täter zuordnen können. EBC-Prozessdiagramme sind also ein Synthesewerkzeug. Statt top-down vorzugehen und mit Abstraktionen zu beginnen, die Sie nicht kennen, starten Sie bottom-up. Sie beginnen bei relativ leicht identifizierbaren Aktivitäten und abstrahieren dann von ihnen. So kommen Sie zu Klassen, wenn Ihre Prozessschritte Methoden sind. Aber was, wenn sich für eine Übersetzung von Aktivitäten in Klassen entschieden haben?

Komponenten als Großtäter

Klassen enthalten Methoden. Assemblies enthalten Klassen. Für alle Klassen – egal, ob die Taten oder Täter darstellen – müssen Sie sich entscheiden, wie Sie sie zu Assemblies zusammenfassen.

Bei EBC-Schaltplänen habe ich noch die Auffassung vertreten, jede EBC-Komponente (allemal Bauteile) wirklich als Komponente zu implementieren. Für EBC-Prozessdiagramme sehe ich das nun anders.

Wie Sie Taten als Klassen oder Taten als Methoden in Täter-Klassen zu Komponenten zusammenfassen, ist völlig getrennt vom Prozessdiagramm zu entscheiden. Eine automatische Übersetzung von Diagrammelementen in Komponenten (d.h. Assemblies mit separatem Kontrakt) gibt es für mich nicht mehr.

Komponenten sind insofern darauf reduziert, Einheiten für die starke Entkopplung und eine isolierte bzw. parallele Entwicklung zu sein. Fragen Sie sich bei der Entscheidung für die Zusammenfassung z.B.

  • …ob Klassen eine genügend hohe Kohäsion haben, um im selben VS-Projekt realisiert zu werden? Das könnte z.B. der Fall sein, wenn sie in Bezug auf die Problemdomäne eng beieinander liegen oder auf derselben Technologie basieren.
  • …ob die Implementierung einer Klasse risikoreich ist und daher parallel zur Implementation anderer stattfinden sollte, um deren Umsetzung nicht zu behindern?
  • …ob für die Implementation von Klassen besondere Kompetenz nötig ist und sie daher parallel zu anderen stattfinden sollte?

Hohe Kohäsion sollten Sie durch Zusammenfassung in einem VS-Projekt, d.h. in einer Komponente ausdrücken. Hohes Risiko, spezielle Kompetenz oder auch lange Entwicklungsdauer sollten hingegen eher dazu führen, dass Klassen getrennt von anderen in Komponenten realisiert werden.

Dasselbe gilt auch für die Art ihrer Herstellung. So kann es sein, dass zusammengesetzte Aktivitäten (die früheren Platinen) generiert werden. Damit kommen sie ohnehin in anderen Assemblies zu liegen als manuell zu entwickelnde atomare Aktivitäten. Ich fasse deshalb alle zusammengesetzten Aktivitäten (z.B. “Funktion berechnen”) in einem von allen anderen VS-Projekten getrennten zusammen.

Unterm Strich sind Komponenten heute für mich “nur noch” Container, die Sie nach sehr pragmatischen Gesichtspunkten füllen. Sie sollen ihre Inhalte von anderen physisch und inhaltlich isolieren. Nur weil Sie in einem Diagramm aber irgendwelche Kästchen zeichnen, entstehen noch keine Komponenten. Ihre Bildung ist orthogonal zur Formulierung eines Lösungsmodells. Machen Sie Komponenten in EBC-Prozessdiagrammen ruhig kenntlich. Aber sehen Sie sie als das, was sie sind: als Einheiten der Codeorganisation und nicht als Bestandteile des Lösungsraumes.

image

Diese Auffassung unterscheidet sich deutlich von meiner bisherigen. Und sie widerspricht eigentlich auch der Bezeichnung Event-Based Components, denn die primären Elemente von Prozessdiagrammen sind keine Komponenten mehr. Es tut mir leid, wenn ich Sie damit verwirren sollte. Aber es hilft nichts: Es fühlt sich besser an, Komponenten in dieser Weise zu benutzen. Und ich glaube auch, dass es leichter zu erklären ist.

Komponenten sind Strukturierungsmittel für Code zur Entwicklungszeit. Sie sind damit genausowenig funktional lösungsregelavant wie VS Solutions oder Klassendateien. Fassen Sie in ihnen Klassen für Täter und Taten schlicht zu “Großtätern” zusammen, wie es für die Evolvierbarkeit und Produktionseffizienz günstig ist.

Komponenten entstehen mithin wie Täter-Klassen durch Abstraktion. Versuchen Sie nicht, sie durch Analyse der Anforderungen zu entdecken. Lassen Sie sie entstehen durch Zusammenlegung von Klassen, die nach einem Gesichtspunkt wie Risiko oder Entwicklungsdauer oder Technologie zusammen gehören.

Komponenten elastisch

Das bedeutet aus meiner Sicht sogar im Umkehrschluss, dass Komponenten in ihrem Inhalt variabel sein können. Was heute in bestimmter Hinsicht günstig zu einer Komponente zusammengefasst werden sollte, kann morgen anders zusammengefasst werden. Hier ist die Softwareentwicklung im Vorteil gegenüber der Elektrotechnik oder dem Maschinenbau. Deren Komponenten sind fix. Wir können Komponenten jedoch “atmen” lassen. Wir können sie elastisch gestalten.

Einzig wichtig für eine Software sind die Aktivitäten. Weder ist wichtig, ob sie als Klassen oder Methoden realisiert werden. Noch ist wichtig, welche Methoden-Prozessschritte in welchen Klassen zusammengefasst sind, noch ist es interessant, in welchen Komponenten Klassen gemeinsam liegen.

Nun mag es schwierig sein, Aktivitäten von Klasse auf Methode umzustellen oder Methoden-Prozessschritte aus Klassen herauszutrennen. Täter- bzw. Tat-Klassen (mit ihren “Anhängseln”) jedoch aus der einen in eine andere Komponente zu verschieben, sie heute so und morgen anders zu gruppieren, scheint mir relativ leicht. Das sollten wir als Chance begreifen und ausnutzen.

Denken Sie also in elastischen Komponenten. Fühlen Sie sich recht frei, sie mit Aktivitäten zu füllen, um ihren Produktionsprozess zu optimieren. Komponenten mit ihren Komponentenwerkbänken schaffen beste Voraussetzungen für zügige Parallelentwicklung am Code. Im CCD Praktikum sind wir durch sie in fünf Tagen über nicht einen Mergekonflikt gestolpert.

Namensräume

Klassen und Komponenten sind Mittel zur physischen Strukturierung. Die Beweggründe zu Trennung oder Zusammenfassung von Code sind vielfältig. Sie können mit der Arbeitsorganisation zu tun haben, mit technologischen Abhängigkeiten oder einem Implementationsdetail geschuldet sein.

EBC-Prozesshierarchien stellen demgegenüber eine funktionale Strukturierung dar. Aktivitäten sind geschachtelt oder nachgeschaltet, um eine Anforderung zu erfüllen.

Physische Nähe und funktionale Nähe drücken aber noch nicht unbedingt inhaltliche Nähe aus. Von der physischen Nähe ist das zwar eher anzunehmen, aber es ist auch nicht zangsläufig. Vor allem kann sich die physische Struktur ändern, ohne dass deshalb die inhaltliche, logische Struktur auch ändern sollte. Wie drücken Sie im Code also inhaltliche, logische, konzeptionelle Nähe aus? Wie fassen Sie etwas logisch zusammen, auch wenn es physisch getrennt z.B. in verschiedenen Komponenten liegt?

Das Mittel für die logische Zusammenfassung von Klassen sind Namensräume:

image

Ohne Ansehen des Definitionsortes (physische Struktur) und des Einsatzortes (funktionale Struktur) verbinden Namensräume Klassen. Sie schaffen damit eine weitere “Großtäterdimension”.

image

Zwischenfazit

Für mich fühlen sich EBC jetzt stimmiger an. Klare Regeln für das, wie ein EBC-Diagramm aufgebaut ist, leiten meine Gedanken bei der Ableitung des Entwurfs aus den Anforderungen:

  1. Finde Quelle und Senken für den Prozess. Diese Verantwortlichen sind vergleichsweise einfach zu bestimmen.
  2. Zerlege den Prozess in Aktivitäten; zerlege die Aktivitäten falls nötig in weitere Aktivitäten. Diese Ableitung von Schritten aus einer Anforderung ist vergleichsweise naheliegend; man beginnt mit dem Konkreten, statt mit Abstraktem/Unbekanntem.

Und klare Übersetzungsoptionen helfen mir beim Codieren des entworfenen Modells. Die Möglichkeiten moderner Sprachen werden ausgenutzt.

  • Übersetze Aktivtitäten in Klassen (oder auch Methoden); finde die Täter hinter den Taten; abstrahiere vom Bekannten, suche Muster
  • Finde Namensräume, um Klassen logisch/inhaltlich in die Nähe zu rücken
  • Organisiere Klassen in Komponenten, um ihre Implementation effizient betreiben zu können – aber auch, um “Übergriffe” zu vermeiden. Komponenten dienen der Stabilisierung der funktionalen Strukturen; es sind Bollwerke gegen die Tendenz zu steigender Code-Entropie

Unterschiedliche Plattformfeatures dienen unterschiedlichen Zwecken:

  • Klassen, Methoden: funktionale Strukturierung
  • Namensräume: logische Strukturierung
  • Assemblies: Strukturierung der Code-Produktion

Da schaut konzeptionell sauber aus, oder? Alles hat seinen Zweck.

Fragts sich abschließend nur, was mit den “normalen” Klassen/Objekten ist? Gibt´s die noch? Ja, die gibt´s noch. Sozusagen in den Zwischenräumen ;-) Davon mehr beim nächsten Mal.

Freitag, 16. Juli 2010

Prozesse klammern

Prozesse, Tätigkeitsfolgen, Herstellungsschritte stehen für mich derzeit am Anfang des Softwareentwurfs, wie ich hier beschrieben habe. Aus Anforderungen lassen sich nur schwer Klassen als Verantwortliche einer Lösung ableiten. Viel einfacher ist es, die Tätigkeiten zu ermitteln, die in Summe den Wunsch eines Anwenders erfüllen.

Wenn Sie ins Restaurant gehen, dann wollen Sie ein leckeres Essen. Dafür ist es wichtig, dass ein Prozess reibungslos abläuft:

  1. Sie wollen einen Tisch zugewiesen bekommen
  2. Sie möchten die Karte gereicht bekommen
  3. Sie möchten eine Bestellung aufgeben
  4. Sie möchten, dass das Gericht schmackhaft und zügig zubereitet wird
  5. Sie möchten, dass das Gericht serviert wird
  6. Sie möchten am Ende reibungslos bezahlen

Aus der Anforderung “Lecker essen” lassen sich ganz simpel “Herstellungsschritte” ableiten. Es ist klar, was (!) getan werden muss, um Ihren Wunsch zu erfüllen. Wer das tut, ist hingegen nicht herauszulesen. Ihnen als Gast ist es letztlich egal, ob die Tischzuweisung durch Sie selbst geschieht oder Restaurantpersonal. Ihnen ist auch egal, ob ein Kellner Ihnen die Karte bringt oder die schon auf dem Tisch liegt. Und wer das Gericht zubereitet, ist auch einerlei, solange es Ihren Qualitätsanforderungen entspricht. Am Ende müssen Sie auch nicht bei einem Kellner bezahlen, sondern wären womöglich bereit, automatisch bei Verlassen des Restaurants zu bezahlen.

Wenn Sie ein Programm für die Anforderung hätten entwerfen sollen, dann wären Sie jedoch nach OOP-Manier sehr schnell darauf gekommen, einen Kellern, einen Koch, einen Restaurantchef zu modellieren. Denen hätten Sie Aufgaben/Dienstleistungen zugeordnet.

Objektorientierung legt es mir Klassen/Objekten einfach nahe zu überlegen, wer (!) an einer Lösung beteiligt sein kann. Ein Prozessdenken, wie ich es hier beschreibe und derzeit übe, stellt hingegen das Was (!) an den Anfang. Was tun? Nicht: Wer tut?

image

Spüren Sie den Vorteil der Prozessdenke schon bei diesem Beispiel? Wenn Sie in Objekten denken, also in Verantwortlichen, dann beschränken Sie den Lösungsraum. Wenn Sie Kellern, Koch, Restaurantchef an den Anfang stellen, kommen Sie nicht auf ein Konzept wie es z.B. hinter der Restaurantkette Vapiano steht. Dort gibt es nämlich keinen Kellner. Sie interagieren direkt mit dem Koch. Und sie bezahlen beim Rausgehen das, was auf einer “Kreditkarte” registriert wurde. Dass dort noch jemand steht und kassiert, hat keinen Nutzen in Bezug auf die Bezahlung, sondern ist nur noch eine Freundlichkeitsgeste.

In einer reinen Prozessbeschreibung zur Erfüllung der Anforderung “Lecker essen” ist das Vapiano-Konzept erhalten. Es ist eine funktionierende Ausprägung. Prozessschritte sind invariant. Sie müssen sich immer setzen, bestellen, Essen muss zubereitet werden, für Bestelltes muss man bezahlen. Sitzen, bestellen, zubereiten, bezahlen. Ohne diese Aktivitäten geht es nicht.

image
Wer dafür jedoch verantwortlich ist… das ist eine ganz andere Frage. Es geht ohne Kellner, es geht ohne Koch, es geht ohne Restaurantchef, es geht ohne Kassierer. Die Hotelkette MotelOne macht es auch vor: Sie durchlaufen beim MotelOne einen Prozesse wie in jedem anderen Hotel – aber die Verantwortlichen sind anders verteilt. Alle “Herstellungsschritte” für das Produkt “Angenehme Übernachtung” werden erbracht, nur anders, von anderen als üblich.

Quellen und Senken für Prozesse

Am Anfang des Softwareentwurfs sehe ich derzeit also ganz klar die Aktivitäten. Was muss für jedes Feature geleistet werden? Das beginnt beim allerkleinsten Feature und hat nach oben keine Grenze. Ob die Farbe einer Zahl verändert werden soll, jenachdem ob ihr Wert positiv oder negativ ist, oder eine Funktion zu plotten ist… immer steht dahinter ein Prozesse bestehend aus Aktivitäten. Eine Tat nach der anderen muss vollbracht werden. Von wem? Erstmal egal.

image

Im Entwicklungsprozess steht also zunächst die Identifikation eines Features und die Bestimmung von Input/Trigger sowie Output/Zustandsänderung.

image

Diese Features leiten Sie aus den Anforderungen ab. Das ist natürlich eine Kunst für sich. Sie sollen so groß sein, dass sie einen Anwendernutzen darstellen. Andererseits sollen sie so klein sein, dass sie sich schnell umsetzen lassen. Nur so können Sie in einen kontinuierlichen Feedbackprozess mit dem Anwender treten.

Diese Feature sind kleine System-Umwelt-Diagramme. Sie benennen einen Prozess, der durch die Umwelt gestartet wird und auf die Umwelt wirkt. Deshalb sind noch Quellen und Senken zu bestimmen. Wer initiiert einen Prozess? Wer nimmt die Produkte des Prozesses entgegen? Quellen und Senken sind Verantwortliche. Bei ihnen geht es um das Wer.

image

Typischer Initiator, typische Quelle für den Prozess eines Feature ist natürlich das UI. Ebenso ist es typische Senke, typischer Empfänger. Aber auch ein Webservice-Stub kann Prozessinitiator sein oder ein Timer. Und Ressourcen wie eine Datenbank können Senke sein.

Im Augenblick sehe ich also die Verantwortlichen Quelle/Initiator und Senke/Empfänger als Klammern um featureproduzierenden Prozesse. Ganz einfach, ganz systematisch.

Dieses Denken hat uns jedenfalls im CCD Praktikum sehr geholfen. Hier ein Tafelbild von unserer Planungsarbeit:

image

Die Prozesse für zwei Features des Kinokassenprogramms, an dem wir zwei Tage gearbeitet haben, sind grün hervorgehoben. Die Verantwortlichen für ihre Initiierung bzw. Ergebnisverarbeitung sind rot markiert. Für das obere Feature ist sogar das untere eine Senke; der obere Prozess liefert Daten an den unteren.

Initiatoren und Senken als Klassen umzusetzen, liegt natürlich nah. Aus meiner Sicht sind sie EBC-Komponenten, wie ich sie bisher gedacht hatte. Denn bisher ging es in EBC-Schaltplänen eigentlich immer um “Wer tut etwas?”

Diese Frage lässt sich natürlich mir einigem Aufwand beantworten – doch aus heutiger Sicht ist die Diskussion um Request/Response-Drähte ein Symptom dafür, dass das Denken in Verantwortlichen, die per Events kommunizieren, falsch ist.

Wenn ein Verantwortlicher eine Query absetzt, dann will er zurecht eine Antwort. Wenn aus einer Aktivität jedoch eine Query hinausfließt, dann will die keine Antwort. Die Antwort fließt vielmehr in eine nachgeschaltete weitere Aktivität. Mit Prozessdenken verliert also das Request/Response-Denken an Dringlichkeit. Das fühlt sich für mich gut an.

Zwischenstand

Das Vorgehen bei der Modellierung sieht bisher für mich also wie folgt aus:

  1. Aus den Anforderungen Features ableiten; Features haben immer Wert für den Anwender
  2. Für die Features Input/Trigger und Output/Zustandsänderungen bestimmen
  3. Bestimmen, wer triggert und wer am Ende Ergebnisse entgegennimmt
  4. Ausgehend vom Trigger den Prozess formulieren, der seinen Input in Output bzw. Zustandsänderungen transformiert. Der Prozess besteht aus Aktivitäten, d.h. er dreht sich um das, was zu tun ist, nicht darum, wer es tut.

Donnerstag, 15. Juli 2010

Code als Fabrik – Event-Based Components weitergedacht

Ein Gedanke, der mich gerade nicht loslassen will ist, dass Code eigentlich eine Fabrik ist. Seit EVA wissen wir es eigentlich: “Eingaben verarbeiten zu Ausgaben” ist die Aufgabe von Code. Im Laufe der Objektorientierung scheint mir das jedoch in Vergessenheit geraten zu sein.

Statt um einen Fluss von Datenquelle zu Datensenke geht es der OOP um Zustand. Kochtopf statt Durchlauferhitzer. Und das hörte sich irgendwie auch plausibel an, da die Welt doch aus Objekte zu bestehen scheint. Vom Atom bis zum Galaxienhaufen alles Objekte mit einem Zustand. Oder?

image Eine Zeit lang und für einige Anwendungsszenarien hat dieses Denken funktioniert. Ziel dieser Programmierung ist es, gesunde Klassen mit roten Bäckchen zu entwerfen. Klassen mit wenig Funktionalität auf ihrem Zustand gelten als kränklich, als anämisch.

Doch mir kommen immer mehr Zweifel, ob das der beste Modellierungsansatz für Problemlösungen mit Software ist.

Nicht, dass ich OOP verwerfen wollte. Nein, nein. Ich mag OOP-Sprachen wir C# sehr. Keine Frage. Alles wunderbar. Ihre Möglichkeiten möchte ich nicht missen. Selbst Vererbung kann nützlich sein. Zurück zu prozeduralen Sprachen möchte ich nicht.

Doch ich möchte von diesen Möglichkeiten nicht auch noch eine bestimmte Modellierung aufgezwungen bekommen. In gewisser Weise ist das natürlich unvermeidbar. Ob man mit Stahl, Stein oder Holz eine Brücke baut, macht auch einen Unterschied für die grundsätzliche Bauweise. “Form follows Material”, würde ich mal sagen. Bei Software möchte ich mich jedoch weniger eingeschränkt fühlen.

Und so mache ich mich mal frei von der Objektorientierung und der von-Neumann-Maschine. Das ist nicht leicht, auch mir Sitzt die prozedurale und objektorientierte Prägung im Stammhirn. Aber wenn ich mich recke, meine ich ein Bild von jenseits des Tellerrandes erhaschen zu können.

Nach meinen bisherigen Experimenten mit Event-Based Components (EBC) habe ich mich deshalb in der letzten Woche nochmal auf die Zehenspitzen gestellt. Im CCD Praktikum habe ich EBC bewusst in neuer Weise eingesetzt. Ich wollte ausprobieren, wie es sich anfühlt, ganz bewusst “unobjektorientiert” zu entwerfen. Selbst die bisherige Sichtweise auf EBC-Komponenten als “Dinger, die etwas tun” haben wir daher aufgegeben. Stattdessen haben wir Software als Fabriken angesehen. Fabriken, deren Produkte Daten sind. Fabriken, die Feature-Wünsche des Kunden erfüllen.

Verantwortlichkeiten als Phantasieprodukte

Lassen Sie sich doch einmal darauf ein. Vergessen Sie die Objektorientierung für einen Moment. Wenn ich Ihnen nun sage, dass wir einen Taschenrechner programmieren wollen, woran denken Sie bei einem Lösungsansatz als erstes?

Sie denken an eine Funktionseinheit, die etwas tut. Nennen Sie sie Rechenwerk. Der Anwender gibt zwei Zahlen ein, wählt eine Rechenoperation und das Rechenwerk stellt aus einem Tripel wie (2, 3, +) das Ergebnis 5 her.

image

Jetzt erweitern wir die Aufgaben des Taschenrechners. Er soll Ergebnisse zwischenspeichern können. Welche Funktionseinheit ist dafür verantwortlich? Vielleicht ein Zwischenspeicher? Dann brauchen wir zwei Funktionen darauf: Wir wollen Ergebnisse speichern und auch wieder auslesen können.

 image

Vielleicht wollen wir sie sogar beim Speichern hinzuaddieren (akkumulieren)?

image

Fühlt sich das für Sie ok an? Wir haben zwar noch nicht von Klassen gesprochen, aber Sie denken natürlich reflexartig, dass Rechenwerk und Zwischenspeicher als Klassen implementiert werden können.

Dass dies schon kleine EBC-Schaltpläne waren, ist Ihnen nicht so sehr ins Auge gestochen. Aber woran liegt das? Ich glaube, das liegt daran, dass die Kästchen mit Substantiven bezeichnet sind. Sie heißen Rechenwerk und Zwischenspeicher. Es sind Verantwortliche, es sind Arbeiter, nun, es sind Objekte.

Jetzt festhalten:

Mein Gefühl ist, dass wir damit schon zu kompliziert denken.

Wenn die Aufgabe lautet: “Entwerfe ein Programm, dass addiert und Zwischenergebnisse zwischenspeichern und akkumulieren kann”, dann ist es ein recht großer mentaler Aufwand, aus der Anforderungen Verantwortliche, Arbeiter, Objekte abzuleiten. In der Anforderung steckt kein Rechenwerk drin. Auch kein Zwischenspeicher ist darin beschrieben. Wir phantasieren sie nur reflexartig hinein. Weil wir so OOP-trainiert sind. Denn einer muss die Aufgaben ja am Ende übernehmen, oder? Einer muss addieren, einer muss zwischenspeichern. Na, dann nennen wir den doch Addierer oder allgemeiner Rechenwerk und Zwischenspeicher. Liegt das nicht auf der Hand?

In diesem Fall ist das sehr naheliegend, weil die Anforderungen trivial sind. Wenn es aber komplizierter wird… was dann? Sind unsere Reflexe dann auch verlässlich im Sinne evolvierbarer Strukturen?

Ich glaube, unsere Reflexe sind umso kontraproduktiver, je unbekannter die Problemdomäne ist. Verantwortliche lassen sich schlecht durch Analyse finden. Stattdessen sollten Objekte/Klassen als Abstraktionen durch Synthese entstehen. Klassen und Komponenten sind Orte der Verantwortlichkeit, die etwas zusammenfassen. Was das jedoch ist, das (!) muss erstmal ermittelt werden. Damit sollte der Entwurf beginnen.

Solange wir also durch die Objektorientierung getrieben sind, Verantwortliche zu finden, weil wir nur mit Klassen Aufhänger für Methoden haben, solange produzieren wir leichtfertig Phantasiegebilde. Wir wollen dann den zweiten Schritt vor dem ersten tun.

Am Anfang steht der Prozess

Bisher haben noch keine Klassen gesehen, sondern nur erahnt. Jetzt nehme ich Ihnen aber auch noch die Verantwortlichen weg. Denken Sie nicht mehr in Substantiven. Fragen Sie nicht mehr “Wer soll das tun?”

Machen Sie sich leer von jeglicher OOP und sehen Sie nur die Anforderungen. Anforderungen können sich nur um die Problemdomäne drehen. In ihnen kann per definitionem nichts über die Lösungsdomäne, d.h. den Programmcode stehen. Darüber kann und soll sich ein Kunde keine Gedanken machen.

Ich halte es daher für einen ungeheuren Aufwand, aus Anforderungen irgendeinen im Code Verantwortlichen ableiten zu wollen. Anforderungen können nur über Daten und Prozesse sprechen. Es geht nicht anders. Selbst wenn in den Anforderungen ein Verantwortlicher (in der realen Welt) vorkommt, kann das ja nicht automatisch bedeuten, dass der auch im Code 1:1 so repräsentiert werden sollte. Das (!) ist vielmehr die große, kontraproduktive Suggestion der Objektorientierung.

Versuchen wir es daher einmal anders. Nehmen wir die Anforderungen genau so, wie sie formuliert sind und übersetzen sie in ein Modell. Es ergibt sich:

image

Es setzt die Anforderungen sehr viel einfacher, direkter um. Wir haben nichts hinzugedichtet. Kein Rechenwerk mussten wir ersinnen, keinen Zwischenspeicher. Wir haben lediglich die Prozesse in den Anforderungen aufgedeckt und modelliert.

Ja, ich nenne das Addieren und zwischenspeichern mal Prozess. Denn es geht immer um eine Tätigkeit. Es wird etwas verarbeitet. Mal sind es Daten, mal nur ein Signal wie beim Auslesen. Mal ist das Ergebnis ein Output, mal eine Zustandsänderung. Wessen Zustand geändert wird? Keine Ahnung. Das ist erstmal nicht wichtig. Der Zustandsbehaftete steht in den Anforerungen nicht drin. Und selbst wenn… ob wir gut daran täten, die Vorstellung des Kunden zu übernehmen, steht auf einem ganz anderen Blatt.

Ich behaupte nicht, dass wir Hinweise auf Verantwortliche im Code ausschlagen oder bewusst ignoieren sollten. Ich möchte nur zur Vorsicht mahnen, sie gutgläubig zu übernehmen. Wenn wir bei unserer Modellierung selbst auf sie kommen, dann ist es gut. Ansonsten auch.

Und diese Modellierung beginnt eben nicht mit der Suche nach Verantwortlichen, sondern mit einer Modellierung von Produktions- und Transformationsprozessen. Immer wenn der Anwender einen Knopf drückt, einen Menüpunkt aufruft, ein Zeichen in ein Feld eingibt, kurz: Immer wenn der Anwender einen Event auslöst, kann potenziell ein kurzer oder langer, ein kleiner oder großer Prozess ablaufen.

Mit Prozess meine ich hier schlicht eine Reihe von Verarbeitungsschritten. Erst wird das eine getan, dann das andere, dann noch etwas usw.

Aus dieser Perspektive besteht Software zunächst einmal aus einer großen Anzahl von Prozessen. Die werden getriggert durch Events im Frontend – sei das eine Benutzerinteraktion oder eine Nachricht von einer anderen Software auf einem Kommunikationskanal.

Software ist also eine Fabrik mit vielen Prozessen, die jeder für ein anderes Produkt stehen. Ein Tastendruck stößt die Produktion einer Summe an, ein Mausklick stößt die Transformation einer Zahl in einen Speicherzustand an usw. Nennen wir das einmal Feature-Prozesse, weil sie Feature der Anforderungen realisieren:

image

Auf die Struktur der Prozesse möchte ich hier noch nicht näher eingehen. Aber soviel sei gesagt: Natürlich lassen sie sich schachteln. Eine Tätigkeit wie Akkumulieren ist nur auf einer gewissen Abstraktionsebene eine einzige Tätigkeit. Wir können in sie hineinzoomen. Sie könnte z.B. so aussehen:

image

Ha! Was erkennen wir? Akkumulieren ist eine Tätigkeit, die sich durch andere Tätigkeiten, die wir schon modelliert haben, ausdrücken lässt. Wiederverwendung winkt! Und das, obwohl wir immer noch nicht an Objekte oder Verantwortliche gedacht haben. Wir haben nur Tätigkeiten, Verben, Prozessschritte im Blick. Wir kümmern uns nur um Verarbeitung und Transformation, d.h. um das Was, nicht um das Wo oder Wer.

Dafür ist ein Umdenken nötig. Wir müssen aufhören, reflexhaft über rotbäckige Verantwortliche zu phantasieren. Doch mir scheint, das lohnt sich. Wenn wir an den Anfang unserer Modellierung eine ganz schlichte featureorientierte Modellierung von Prozessen setzen, die selbstverständlich Durchstiche im Sinne eines Schichtenmodells darstellen, dann müssen wir weniger Energie aufwenden.

Damit meine ich nicht, dass wir in Flowcharts programmieren sollen. Die obigen Diagramme sehen auch nicht so aus, würde ich sagen. Auch Windows Workflows (WF) sehe ich noch nicht als Realisierungstechnologie. BPMN-Diagramme oder UML Aktivitätsdiagramme liegen da schon näher. Aber so ganz sicher bin ich mir noch nicht, wohin es führt, Software ganz konsequent als Fabrik bestehend aus vielen Prozesse anzusehen.

Aber vielleicht fällt uns zusammen ja etwas ein. Lassen Sie diese Gedanken erstmal auf sich wirken: Tätigkeiten statt Täter, Prozessschritte statt Verantwortliche. Der Unterschied mag ihnen akademisch erscheinen – ich glaube aber, dass er fundamental ist.

Beim nächsten Mal holen ich Sie dann aus der “objektfreien” Welt zurück. Denn es hilft ja nichts, die Verben der Prozesse müssen ja mit Mitteln von C# umgesetzt werden.

Dienstag, 13. Juli 2010

Testnähe aus der Distanz betrachtet

Die Diskussion um den besten Platz für Tests reißt nicht ab. Nach meiner Gegenüberstellung zweier Testplatzierungsstile hat Stefan nachgelegt und nun Ilker gekontert. Die WM ist vorbei, der Testball aber noch im Spiel… :-)

Was für ein Spiel ist das aber? Ist es ein Nullsummenspiel? Und worum dreht es sich? Die erste Frage ist, was “nah am zu testenden Code” bedeutet. die zweite Frage, ob Nähe das wichtigste oder gar einzige Kriterium für die Platzierung von Testcode ist.

Nah und fern

Wie nah ist nah? Ist “nah am Code” nur dies? Test und System-under-Test (SUT) liegen im selben Projekt.

image

Oder wäre es sogar so noch besser? Der Test ist dem SUT visuell untergeordnet, um den Blick auf die Hauptfunktionalität nicht zu verstellen:

image

Oder ist Nähe noch gegeben, wenn Tests in eigenen Unterverzeichnissen stehen?

image

Oder darf ich mit Recht behaupten, dass Tests platziert in einem eigenen Projekt noch nah sein können?

image

Hm… verwirrend diese Optionen. Oder sind sie gar nicht so verschieden, denn wie sieht es während der Arbeit an SUT und Tests in Visual Studio aus? Ist aus diesen Bildern die Platzierung der Tests abzulesen?

image 

image

Ich sehe da keinen Unterschied, obwohl die Tests sehr unterschiedlich platziert sind. Für die Arbeit am Code, also den ständigen Wechsel zwischen Test und SUT und Test und SUT, scheint es mir unbedeutend zu sein, ob die Tests als “near spec” im Projekt des SUT liegen oder in einem anderen Projekt. Solange sie in derselben Solution zu finden sind, können Tests und SUT ganz einfach so dargestellt werden.

Wer es noch verwegener mag und einen großen Bildschirm hat, der legt sich Tests und SUT sogar nebeneinander. Solche Darstellung überwindet jede Distanz. Programmieren wird da zur Kuschelstunde ;-)

image

Angesichts dessen würde ich sagen, dass es während der Arbeit an Tests und SUT einerlei ist, ob die Tests im SUT-Projekt liegen oder in verschiedenen. Solange sie sich in derselben Projektmappe befinden, ist der Wechsel zwischen beiden einfach möglich. Im Editor kann man sie immer in die Nähe rücken. Dasselbe gilt für Testdaten.

Kohäsion

Für die Arbeitsgeschwindigkeit an Tests und SUT sollte es keinen Unterschied machen, in welchem Projekt Tests platziert sind. Wie sieht es aber mit der Verständlichkeit aus? Ist Code besser zu verstehen, wenn Tests inkl. Testdaten und SUT in einem Projekt liegen? Ist nicht die Kohäsion zwischen Tests und SUT höher als die zwischen zwei unterschiedlichen SUTs? Müssten daher nicht Tests sogar eher in einem Projekt zusammen mit ihrem SUT liegen als zwei verschiedene SUTs?

Hier ein Blick in die Sourcen von RavenDB, der dokumentenorientierten NoSql-Datenbank von Ayende Rahien:

 image

Die Tests für den Client liegen in einem eigenen Projekt. Ob sie sich nur auf das markierte SUT-Projekt beziehen oder auch die anderen Client-Projekte, ist nicht ersichtlich. Das widerspricht der hohen Kohäsion von Test und SUT, würde ich sagen. Die Zusammenhänge zwischen beiden zu erkennen, ist schwierig.

Wer den Code grundsätzlich so strukturiert, d.h. viele Projekte in einer Projektmappe führt, der beklagt zurecht, dass die natürliche Kohäsion durch getrennte Projekte für Tests und SUT schwer auszudrücken ist.

Wie gesagt, dem Wechsel zwischen SUT und Tests tut das keinen Abbruch. Doch die Übersichtlichkeit leidet zunächst.

Was aber, wenn Software besser strukturiert ist? Wenn ihr ein Plan zugrunde liegt, der auch noch umgesetzt wird? Was wenn Plan und Code gar übereinstimmen? Hier ein Beispiel aus dem CCD Praktikum:

image

Das ist der Plan für ein Feature eines Softwaresystems für Kinos, das wir im Praktikum begonnen haben. Der Plan zeigt die Prozessschritte für ein Feature. Er ist als EBC-Schaltplan ausgelegt. Die Kästen werden in Klassen übersetzt. Die Pfeile in Event-Eventhandler-Verbindungen. Darüber hinaus gibt es aber auch noch Zusammenfassungen zu Komponenten. Die habe ich farblich hervorgehoben. Sie werden in Projekte übersetzt.

Im Repository sieht das dann so aus:

image

Die Komponenten finden sich dort als Projektmappen in eigenen Verzeichnissen wieder. (Wer mag, kann die natürlich auch wieder in Unterverzeichnissen gruppieren.)

Und innerhalb der Komponenten sind jeweils mindestens zwei Projekte zu finden, eines für das SUT sowie eines für dessen Tests:

image

Die hohe Kohäsion zwischen Tests und SUT findet dadurch klaren Ausdruck, würde ich sagen. Wer Tests zu einem SUT sucht, weiß genau, wo er sie findet.

Voraussetzung dafür ist natürlich der explizite Wille zur Architektur. Wo es den nicht gibt oder wo der nicht walten kann, weil der Brownfield-Matsch so tief ist… da mag es tatsächlich angezeigt sein, Tests und SUT im selben Projekt zu pflegen. Doch man sei sich bewusst, was dafür der Grund ist. Tests und SUT im selben Projekt ist dann ein Symptom der Grundproblems “Brownfield”. Tests und SUT im selben Projekt sind aus meiner Sicht daher kein Ziel, sondern nur eine Übergangslösung.

Trennung

Worin sich alle Diskutanten auch einig sind, das ist die Notwendigkeit der Trennung von Tests und SUT bei der Auslieferung. Tests und ihre Artefakte sollen nicht zum Kunden.

Wenn Tests und SUT in verschiedenen Projekten liegen, ist diese Trennung trivial. Testsprojekte haben ein anderes Output-Verzeichnis als SUT-Projekte, so dass Visual Studio beim Bau einer Solution automatisch die Trennung vornimmt. Im SUT-Output-Verzeichnis kommt immer nur das an, was auslieferungsrelevant ist.

Anders wenn Tests und SUT zusammen in einem Projekt stehen. Dann muss der Buildprozess explizit angewiesen werden, Tests und Testartefakte auszufiltern. Das mag eine Zeile Scriptcode sein oder viele. Egal. Wer den Buildprozess aufsetzt, muss daran denken. Und Entwickler müssen womöglich auch daran denken und während der Entwicklung Konventionen einhalten, die das ermöglichen.

So hoch die Kohäsion zwischen Tests und SUT prinzipiell sein mag – bei der Auslieferung hört sich dann doch auf.

Wenn es aufgrund von monolithischem Code nicht anders geht, die Kohäsion während der Entwicklung auszudrücken als durch Zusammenlegung von Tests und SUT in einem Projekt, dann sei es so und man filtere am Ende beim Build die Tests heraus. Aber, bitte, bitte, man verkaufe mir das nicht als Tugend. Zusätzlicher Aufwand ist zusätzlich fehleranfällig. Wenn ich ihn nicht treiben muss, dann möchte ich das auch nicht.

Eine saubere, angemessene Planung mit klarer Übersetzungsregel schenkt mir also nicht nur Evolvierbarkeit, sondern auch übersichtliche Codeverhältnisse und weniger Aufwand für den Buildprozess. Dann bin ich für saubere, angemesse Planung, würd ich sagen. (Die nicht zu verwechseln ist mit BDUF.)

Fazit

Tests im selben Projekt wie ihr SUT? Ist einzig das zeitgemäß? Ich würde sagen, wir sollten nicht nach Geschmack und Mode und Zeitgeist entscheiden. Professionelle Arbeit braucht Werte und Prinzipien. Um welche geht es hier?

Produktionseffizienz: Tests im selben Projekt sollen die Arbeit beschleunigen. Der Wechsel zwischen beiden soll leichter fallen. Wie oben gezeigt, glaube ich daran jedoch nicht. Visual Studio macht es leicht, für die Arbeit an Tests und SUT Nähe im Editor herzustellen. Und mit einem Tool wie dotCover wird es noch einfacher.

image

Als Beispiel die Open Source Software Lounge Repository. Sie ist komponentenorientiert entwickelt, d.h. für jede Komponente gibt es auch eine Werkbank bestehend aus Tests und SUT in je verschiedenen Projekten.

image

Allerdings bin ich bei der Codeorganisation einen Kompromiss eingegangen und habe alle Projekte in einer Projektmappe versammelt. Damit wollte ich es dem unbedarften Open Source Freund ein wenig einfacher machen, in die Quellen einzusteigen.

Ich finde das übersichtlich; viel übersichtlicher als die RavenDB Sourcen trotz Multi-Projekt Solution. Die Tests liegen nahe dem SUT, sie sind damit “near specs”. Aber gerade geht es ja um Produktionseffizienz. Die ist nicht nur hoch, weil ich Tests und SUT side-by-side im Editor liegen können (s.o.), sondern auch, weil dotCover mir erlaubt, aus dem SUT zum Test zu springen:

image

Die Liste auf der rechten Seite zeigt die Tests, die eine bestimmte SUT-Zeile abdecken. Damit muss ich gar nicht mehr wissen, wo in einer Projektmappe Tests überhaupt stehen. Ich lasse schlicht alle Tests mit dotCover ausführen, bekomme einen Eindruck vom Nutzen der Tests aufgrund ihrer Codeabdeckung – und kann dann aus den SUT-Zeilen, an denen ich gerade arbeite, zu relevanten Tests springen. Im selben Projekt oder in anderen. Wenn das nicht effizient ist.

Ebenfalls effizient ist, dass bei Trennung von Tests und SUT kein weiterer Aufwand für die Trennung beider Anteile während der Produktion zu treiben ist. Nicht anfallender Aufwand ist ganz eindeutig der kleinste mögliche Aufwand.

Übersichtlichkeit: Der nächste für die Kolokation von Tests und SUT reklamierte Wert ist die Übersichtlichkeit. Es sei übersichtlicher, wenn beide in einem Projekt lägen. Wie gesagt, das kann ich glauben, wenn es denn nicht anders geht. Wenn die Verteilung auf zwei Projekte ihrer natürlichen Kohäsion zuwiderläuft, weil sie dann sehr weit auseinander stünden in einer Projektmappe, dann mag die Kolokation einen Vorteil haben.

Der Umkehrschluss ist jedoch aus meiner Sicht falsch: Weil Zusammenlegung im Matschfeld einen gewissen Vorteil haben kann, weil damit ein grundlegendes Problem pragmatisch kurzfristig kaschiert wird, deshalb sollten Tests und SUT möglichst immer zusammengelegt werden. Nein, so würde der Bock zum Gärtner.

Darüber hinaus sollte Übersichtlichkeit aber noch weiter gefasst werden. Die zu einem SUT gehörenden Tests leicht finden können, ist eine Sache. Wie stehts aber mit der Identifikation von SUTs? Wie steht es mit Verantwortlichkeiten der Software im Allgemeinen auf unterschiedlichen Abstraktionsebenen?

Übersichtlichkeit entsteht nicht nur durch Zusammenlegung, sondern auch durch Trennung. Mir scheint, dass dieses Prinzip schnell übergangen wird, wenn man im Kolokationsflow ist. Denn wo Tests und SUT zusammengeschnürt werden sollen, dann halte ich die Tendenz für hoch, auch anderes zusammen zu schnüren. Und am Ende entsteht bzw. wird erhalten der Monolith, das Softwaresystem mit hoher Entropie, das Brownfield.

Statt Tests als Anlass zu begreifen, zumindest mal einen Concern aus dem großen Brownfield herausziehen zu können, trägt die Kolokation vielmehr zum Erhalt des status-quo bei. Schade.

Mein persönliches Fazit der Diskussion: Zeitgeist und Geschmack bringen uns nicht weiter. Meinungen sollten auf Prinzipien verweisen. Sie sollten zeigen, wie und warum und wann diese oder jene durch eine Praktik besser oder schlechter umgesetzt werden.

Bei trivialem Code mag es daher ok sein, Tests und SUT zusammenzufassen. Eine Code Kata könnte dazu gehören – wenn man nicht gerade üben möchte, eine andere Codeorganisation einzuhalten.

Jenseits des trivialen Codes bezweifle ich jedoch, dass Tests und SUT im selben Projekt der Produktionseffizienz und Übersichtlichkeit wirklich dienen. Sie sind mir eher Symptom für ein Wurzelproblem und Bequemlichkeitsmaßnahme. Mehr als eine Übergangslösung aus der Not geboren sehe ich in ihnen nicht. (Mann, oh, mann, wie störrisch und uneinsichtig ist das denn eigentlich ;-) Aber es hilft nix: “Hier stehe ich, ich kann nicht anders.” Für die Zukunft gelobe ich jedoch, den Kontext besser zu berücksichtigen. Bei einer Code Kata könnte ich mal von dieser “Prinzipienreiterei” abesehen ;-)

Sonntag, 11. Juli 2010

Mit vielen Gehirnen entwirft man besser [OOP 2010]

image Was macht einen erfahrenen Entwickler aus? Dazu habe ich grad hier etwas gelesen und bin beim “Mid-Level Developer” (beim “Mittelmäßigen” möcht ich da mal nich rauslesen ;-) auf diesen Satz gestoßen:

“Can work from user stories”

Was bedeutet das? Ich habe herausgelesen: “Der mid-level Developer kann das, was er am Quellcode zu tun hat, selbstständig aus User Stories ableiten.”

Stimmen Sie mit mir in dieser Interpretation überein? Wenn ja, dann hier zur Begründung, warum ich darüber den Kopf geschüttelt habe.

Welcher Ansatz von Softwareentwicklung steht dahinter, wenn Entwickler ihre Arbeit am Quellcode selbst aus User Stories ableiten? Ich stelle mir das so vor:

Es gibt eine Besprechung, in der User Stories vorgestellt werden. Das Team klärt sein Verständnis der Anforderungen, die darin stecken. Dann wird vielleicht der Aufwand geschätzt. Dann wird geklärt, welche User Stories in Angriff genommen werden sollen. Und dann melden sich Entwickler, die einzelne User Stories umsetzen wollen. Sie arbeiten im Sinne eines Durchstichs, also einer allein (oder vielleicht zwei im Pair) realisieren die User Story.

Ich behaupte nicht, dass der Autor des Blogpostings so arbeitet. Ich behaupte auch nicht, dass er meint, ein mid-level Developer solle so arbeiten. Die obige Formulierung jedoch suggeriert mir so ein Vorgehen. Und das ist so weit weg von dem, wie ich meine, dass Software entwickelt werden sollte, dass ich einfach mal einhake.

image In der letzten Woche habe ich mit Stefan Lieser das Clean Code Developer Praktikum durchgeführt. Fünf Tage lang haben wir darin mit fünf Entwicklern Projekte angepackt. Die kleinsten waren Code Katas und in 2-3 Stunden zu realisieren; das größte dauerte jedoch 2 Tage und ist noch nicht abgeschlossen.

Und wenn mir in den Tagen eines klar geworden ist, dann dies:

Kein Entwickler sollte allein von User Stories ausgehend arbeiten.

Wir haben jedes Projekt mit der Erarbeitung eines gemeinsamen Verständnisses der Anforderungen begonnen. Anschließend haben wir gemeinsam den Lösungsansatz erarbeitet. Viele Gehirne haben also die Anfordeurngen hinterfragt, viele Gehirne haben sich kreativ in den Entwurf eingebracht. Das war für die Projekte unheimlich hilfreich.

Nicht nur wurde dadurch eine gemeinsame Sprache als Basis für den vertrauensvollen Umgang mit der Problemdomäne im Team herausgearbeitet. Nein, alle haben auch dazu beigetragen, dass der Entwurf unter keinen blinden Flecken leidet. Und alle haben einen Überblick über die Gesamtanwendung bekommen.

image Gerade die konzentrierte gemeinsame Entwurfsphase war für mich immer tief befriedigend. Sie hat mir das Vertrauen gegeben, dass wir alle an einem Strang ziehen. Jeder hat sein Bestes gegeben, um einen angemessenen und realistischen Lösungsansatz zu entwickeln.

Das war Team Buildung und Software Design in einem. Ein effizienter und effektiver Prozess – der darüber hinaus auch noch Spaß gemacht hat. Keine Prinzipienreiterrei, sondern kreativer Fluss der Ideen im Rahmen des Clean Code Developer Wertesystems.

Aus meiner Sicht brauchen User Stories daher immer viele Entwickler und nicht einen. Ob der eine allein mit User Stories umgehen kann, ist mir recht egal. Wichtiger ist, ob ein Entwickler sich im Team in den Umgang mit User Stories einbringen kann, ob er einen Beitrag zum Verständnis und zur Planung der Umsetzung leisten kann.

Das hat denn für mich auch weniger mit Domänenwissen oder technischer Erfahrung zu tun, als vielmehr mit Softskills. Wie neugierig ist ein Entwickler? Wie “schnell im Kopf” ist sie? Ist sein Denken flexibel? Ist sie offen für Neues? Kann er querdenken? Wie geht sie mit anderen Teammitgliedern im Verständnis- und Entwurfsprozess um? Welche Erfahrung hat er mit dem Prinzipien- und Praktikenrahmen von Clean Code Developer?

Was das Erfahrungsniveau von Entwicklern definiert, weiß ich nicht so genau. Wie lange ist einer Junior Developer, ab wann ist einer Senior Developer? Mit einem bin ich mir aber sicher: Dass Softwareentwicklung jenseits des Trivialen ein Prozess ist, der entscheidend durch Phasen der Gemeinsamkeit geprägt ist.

Das reine Codieren darf gern im stillen Kämmerlein allein oder zu zweit geschehen. Es ist eine eher umsetzende, ausführende Tätigkeit. Klar, dabei ist auch Kreativität gefragt, aber die kann keinen großen Schaden mehr anrichten ;-) Denn sie findet in einem Rahmen und auf einem Fundament statt.

Und dieses Fundament, dieser Rahmen, die sind das Ergebnis gemeinschaftlichen Denkens. Da sind viele Gehirne gefragt.

Dienstag, 29. Juni 2010

Zeig mir deinen Code und ich nenn dir deinen Dojo-Stil

image Die Coding-Dojo-Welt hat sich geteilt. Es gibt nun den münchener Latifa-Stil von Ilker. Und es gibt den Schwarm-Stil von Stefan Lieser und mir. Bei der Teilung hat es ein wenig gerumpelt in der Community – aber nun sollten wir in friedlicher Koexistenz coden können. Die Welt ist halt bunt.

Den einen wahren, kanonischen Coding-Dojo-Stil gibt es nicht. Es gab und gibt nur Interpretationen dessen, was Leute wie z.B. Robert C. Martin mal gezeigt haben. Einzig in der Praktik des TDD sind sich die Dojo einig – und natürlich darin, dass Code geschrieben werden soll.

Über den Prozess, wie zu diesem Code im Dojo gekommen werden sollte, ist schon geschrieben worden. Das ist es, worin sich Latifa- und Schwarm-Stil unterscheiden. Und das macht natürlich einen Unterschied im Lernen der Teilnehmer.

Aber welchen Einfluss hat das auf das Produkt eines Dojos? Merkt man es dem Code an, ob er nach diesem oder jenem Stil entwickelt wurde? Ich habe mal versucht, das herauszufinden.

Für die Kata FizzBuzz habe ich versucht, mich in den Latifa-Stil hinein zu versetzen. Ich hoffe, das Ergebnis ist nicht zu sehr gefärbt von irgendwelchen Missverständnissen oder Voreingenommenheiten.

Anschließend habe ich den Code nach Schwarm-Stil Manier nochmal entwickelt.

FizzBuzz à la Latifa

Gerade nach der Erfahrung des letzten Coding Dojo zum dotnetpro.powerday bin ich mir sicher, dass ein Latifa-Dojo die grundsätzliche Codestruktur so aufgesetzt hätte:

image

Implementation und Test sind in einem Projekt zusammengefasst. Begründung: Mehr braucht es nicht. Das Beispiel ist so klein, dass alles andere Overkill wäre.

Von der Implementation nehme ich an, dass sie im Wesentlichen so aussehen würde:

public class FizzBuzzer
{
    public List<string> Generate()
    {
        var numbers = new List<string>();
        for (int i = 1; i <= 100; i++)
        {
            if (IsFizz(i) && IsBuzz(i))
                numbers.Add("fizzbuzz");
            else if (IsFizz(i))
                numbers.Add("fizz");
            else if (IsBuzz(i))
                numbers.Add("buzz");
            else
                numbers.Add(i.ToString());
        }
        return numbers;
    }

    private bool IsBuzz(int i)
    {
        return i % 5 == 0;
    }

    private bool IsFizz(int i)
    {
        return i % 3 == 0;
    }
}

Die Aufgabe ist klein. Hexenwerk ist nicht nötig. Alles geradlinig implementiert. Die Funktionen IsFizz() und IsBuzz() sind bei einer Refaktorisierung herausgezogen worden.

Exemplarisch hier auch noch ein Ausschnitt aus den Tests:

[TestFixture]
public class Test_FizzBuzzer
{
    [Test]
    public void Check_numbers()
    {
        var sut = new FizzBuzzer();

        var numbers = sut.Generate();

        Assert.AreEqual("1", numbers[0]);
        Assert.AreEqual("91", numbers[90]);
    }

    [Test]
    public void Check_fizz()
    {
        var sut = new FizzBuzzer();

        var numbers = sut.Generate();

        Assert.AreEqual("fizz", numbers[2]);
        Assert.AreEqual("fizz", numbers[98]);
    }
   …

Sie setzen alle beim FizzBuzz-API an – öffentliche Methode Generate() der FizzBuzzer-Klasse. Alle Tests sind Black Box Tests.

So, wie ich bisher die Latifa-Dojos erfahren habe, glaube ich nicht, dass weitere Refaktorisierungen vorgenommen worden wären. Ich glaube auch nicht, dass andere Datenstrukturen gewählt worden wären.

Und warum auch? Läuft doch.

FizzBuzz à la Schwarm

Nach solcher (visionierter) Vorlage eines Latifa-Dojos stellt sich die Frage, inwiefern eine Lösung überhaupt anders aussehen kann. Das Problem ist ja trivial. Kann es da überhaupt zwei Meinungen geben?

Ja, es gibt Alternativen. Der Schwarm-Stil unterscheidet sich also nicht nur im Prozess vom Latifa-Stil, sondern auch in seinem Anspruch an die Lösungsformulierung. Die beginnt bei der Codeorganisation:

image

Zwei Projekte, statt einem. Denn beim Schwarm legen wir Wert nicht nur auf TDD und Funktionalität, sondern auch auf Klarheit in der Form. Verständlichkeit ist uns wichtig. Separation of Concerns (SoC) gilt nicht nur innerhalb von Code, sondern auch für seine Form. Deshalb sind für uns Tests und Implementation immer getrennt. Der Schwarm bemüht sich, möglichst viele Clean Code Developer Bausteine zu berücksichtigen.

An der groben Codeorganisation ist aber noch mehr als eine SoC abzulesen. Das Testprojekt besteht aus drei Klassen. Die spiegeln wider, dass vor dem Coden über den Lösungsansatz nachgedacht wurde. Es hat eine kurze Kreativphase gegeben, in der erkannt wurde, dass die Lösung ein Prozess ist, der aus zwei Phasen besteht:

  1. Zahlengenerierung
  2. Zahlenübersetzung (Mapping).

Um diese Erkenntnis im Code festzuhalten, habe von vornherein für beide Phasen Methoden im Code vorgesehen. Und deshalb konnte ich mich auch entscheiden, welche ich davon zuerst nach TDD implementiere. Ich habe mich für die Zahlenübersetzung entschieden. Sie ist ja auch der Kern des FizzBuzz-Problems.

public class FizzBuzzer
{
    internal static string MapNumber(int number)
    {
        if (IsFizz(number) && IsBuzz(number)) return "fizzbuzz";
        if (IsFizz(number)) return "fizz";
        if (IsBuzz(number)) return "buzz";

        return number.ToString();
    }

    private static bool IsBuzz(int number)
    {
        return number % 5 == 0;
    }

    private static bool IsFizz(int number)
    {
        return number % 3 == 0;
    }

Natürlich habe ich die Tests jeweils vorher geschrieben:

[TestFixture]
public class Test_MapNumber
{
    [Test]
    public void Map_straight_number()
    {
        Assert.AreEqual("1", FizzBuzzer.MapNumber(1));
        Assert.AreEqual("2", FizzBuzzer.MapNumber(2));
        Assert.AreEqual("4", FizzBuzzer.MapNumber(4));
        Assert.AreEqual("98", FizzBuzzer.MapNumber(98));
    }

    [Test]
    public void Map_fizz_number()
    {
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(3));
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(6));
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(99));
    }

Von den Testfällen her unterscheiden die sich eigentlich nicht vom Latifa-Stil. Aber betonenswert anders ist, dass ich sofort mit dem Kern des Problems anfangen konnte, weil ich wusste, dass es dafür eine Funktionseinheit gibt. Und die Tests sind fokussierter, weil sie nur die Übersetzung testen und nicht immer auch noch die Zahlengenerierung berücksichtigen müssen. Schließlich ist der Code in der MapNumber()-Methode auch noch simpler als der Übersetzungscode beim Latifa-Stil, weil er weniger tief schachtelt (keine else-Zweige).

image Hätte der Latifa-Stil mit konsequentem TDD auch dahin kommen können? Klar. Aber die Latifa-Dojo-Erfahrung zeigt, dass das nicht passiert. Da es im Latifa-Stil kein “offizielles” Nachdenken über den Lösungsansatz gibt, gibt es auch keine Gewissheit, dass das demokratische Voranschreiten so tief ins Refactoring einsteigt, wo es doch viel spannender ist, überhaupt eine lauffähige Lösung zu produzieren.

Nach Implementation des Mappings habe ich die Zahlenerzeugung implementiert. Die ist natürlich trivial. Dem Schwarm-Stil ist das jedoch egal. Wenn in der Kreativphase ein Lösungsansatz erarbeitet wurde, der die Zahlengenerierung ausweist, dann wird sie in einer expliziten Funktionseinheit implementiert. Das entspricht dem Single Responsibility Principle (SRP).

internal static IEnumerable<int> NumberGenerator(int from, int to)
{
    for (var i = from; i <= to; i++)
        yield return i;
}

Die Zahlenerzeugung ist kein Bestandteil des API. Deshalb erfolgt sie in einer internen Methode.

Beim Testen ist zu sehen, dass auch der Schwarm-Stil pragmatisch vorgeht. Ich habe nur einen Test geschrieben:

[TestFixture]
public class Test_NumberGenerator
{
    [Test]
    public void Numbers_1_to_3()
    {
        var numbers = FizzBuzzer.NumberGenerator(1, 3);
        Assert.AreEqual(new[]{1,2,3}, numbers.ToArray());
    }
}

Im Sinne eines universellen Zahlengenerators ist das natürlich zuwenig. Wie würde der z.B. auf Zahlengrenzen < 0 reagieren? Wie würde er reagieren, wenn die erste Zahl größer als die zweite ist? Eine Beschränkung auf den problemrelevanten Happy Day Fall ist auch für den Schwarm-Stil ok.

Die Schritte des Lösungsansatzes müssen am Ende natürlich noch zusammengezogen werden. Das ist die Aufgabe der API-Methode:

public IEnumerable<string> Generate()
{
    return NumberGenerator(1, 100).Select(MapNumber);
}

Im Vergleich zur Latifa-Lösung folgt sie sofort dem Single Level of Abstraction (SLA) Prinzip. Die Lösung wird auf hohem, einheitlichem Abstraktionsniveau beschrieben. Und die Phasenfolge ist klar zu erkennen. Schwer zu verstehende Schachtelung ist nicht nötig.

Da die Einzelfunktionalitäten schon getestet sind, ist für die Integration nur noch ein Integrationstest nötig:

[TestFixture]
public class Integrationtest
{
    [Test]
    public void Check_number_generation_and_mapping()
    {
        var sut = new FizzBuzzer();
        Assert.AreEqual(
            new[] { "1", "2", "fizz" },
            sut.Generate().ToArray().Take(15));
    }
}

Der Test kann so kurz ausfallen, weil lediglich geprüft werden muss, ob die Funktionseinheiten überhaupt korrekt zusammenspielen.

Fazit

Wenn ich die typischen Vorgehensweisen von Latifa- und Schwarm-Stil hier halbwegs wirklichkeitstreu wiedergegeben habe, dann führen sie zu unterschiedlichem Code. Ob das eine Ergebnis besser oder schlechter ist, will ich nicht beurteilen. Mir war nur die Beantwortung der Frage wichtig, ob es überhaupt einen Unterschied gibt. Und wenn ja, was der ausdrückt.

Unterm Strich scheint mir hier Conway´s Law bestätigt: Der Code spiegelt Prozess und Organisation des Teams wider. Latifa setzt auf Demokratie, d.h. Gleichberechtigung und Gleichstellung, und hat darüber hinaus keinen Prozess jenseits TDD.

image Schwarm setzt auf einen klaren Prozess (Kreativphase gefolgt von Umsetzungsphase) und die Einhaltung von Prinzipien jenseits von TDD bei der Umsetzung.

Nun mag jeder (potenzielle) Teilnehmer an einem Dojo entscheiden, welchen Stil er/sie bevorzugt.

Samstag, 26. Juni 2010

Kollektiv intelligent codieren

Wenn ein einzelner Entwickler etwas codiert, dann hat das Ergebnis eine gewisse Qualität. Und wenn mehrere Entwickler zusammen etwas codieren, dann sollte das eine höhere Qualität haben. Oder?

Wie die sprießenden Coding Dojos zeigen, ist das nicht der Fall. Da mag der Spaß noch so groß sein, da mag die Motivation hoch fliegen – die Ergebnisse sind meist schlechter, als wenn ein einzelner Entwickler dasselbe probiert. Wenn nach 2 Stunden zwei 5zeilige Tests stehen und noch vielleicht 10 Zeilen Domänenlogik, ohne dass abzusehen wäre, wann die Aufgabe ganz gelöst sein wird, dann ist das ziemlich wenig.

Wie kann das aber sein, was geht schief beim Coding Dojo? Warum bleibt das Kollektiv der Dojo-Teilnehmer hinter Einzelleistungen verlässlich zurück? Oder sehe ich das ganz falsch und nichts geht schief? Ist das einzig schiefe der Sitz meiner Brille, durch die ich das Coding Dojo sehe?

Die anhaltende Diskussion (s. z.B. hier oder hier oder hier) über Sinn und Zweck von Coding Dojos lässt mich nun vermuten, dass die Diskutanten (mich eingeschlossen) aneinander vorbei reden.

Also setze ich heute mal meine übliche Brille erstmal ab und stecke mir eine Lupe aufs Auge.

Struktur von Dojo-Problemlösungen

Worum geht´s denn beim Dojo überhaupt? Es wird ein Szenario präsentiert, zu dem die Teilnehmer zusammen eine Lösung in Form von Code entwickeln sollen. Ob der Code durch einen Stellvertreter (Code Monkey) eingegeben wird, den die Gruppe “fernsteuert” oder ob Gruppenmitglieder reium den Code eingeben, ist an dieser Stelle nicht so wichtig. Ebenfalls noch nicht so wichtig ist, dass Dojos einen bestimmten Weg der Implementation favorisieren: das TDD.

Entscheidend ist die Art der Aufgabe: Übersetzung von Anforderungen in Code.

Diese Art der Aufgabe hat es nämlich in sich. Sie besteht aus zwei Phasen, die auch mehrfach durchlaufen werden können:

  • Phase 1: Kreativphase
  • Phase 2: Umsetzungsphase

Zwischen Anforderungen und “Produkt” steht mithin eine Barriere. Das ist der Entwurf des Produktes. Wie ein Produkt aussieht, ergibt sich nämlich nicht einfach so. Bevor man ein Produkt bauen kann, muss man eine Vorstellung davon haben, wie es aussehen soll.

Ein Bild vom Produkt, von der Lösung zu entwickeln, das ist nun ein kreativer Prozess. Aus den scheinbar oder tatsächlich möglichen Lösungen ist eine auszuwählen.

Ist die Auswahl getroffen, kann sie umgesetzt werden. Umsetzung erfordert also Klarheit in der Vorstellung, was umgesetzt werden soll.

Lösungsfindung und Umsetzung sind ganz verschiedene Tätigkeiten. Sie sind so verschieden, dass die Psychologie dafür zwei Arten des Denkens unterscheidet: divergentes Denken (oder auch laterales Denken) und konvergentes Denken.

image

Divergentes Denken ist in der Kreativphase nötig, konvergentes Denken in der Umsetzungsphase. Die Umsetzung ist also kein “no brainer”, sondern erfordert auch Denken. Denn selbst wenn die Lösungsstruktur grundsätzlich klar ist, bedarf die Umsetzung durchaus noch weiterer Entscheidungen. Umsetzung kann weit entfernt von “Malen nach Zahlen” sein. Umsetzung braucht Erfahrung und Kompetenz.

image

Sind Erfahrung und Kompetenz nicht aber auch für die Lösungsfindung (Kreativphase) wichtig? Interessanterweise sind Erfahrung und Kompetenz, also Expertentum, überbewertet, wenn es um kreative Lösungen geht. Lösungssuchende Gruppen sollten sich daher um Heterogenität bemühen; sonst schmoren Experten zu sehr in ihrem eigenen “Denksaft”. Der Horizont von homogenen Gruppen ist schlicht enger als der von heterogenen.

Fazit bis hierher: Wenn ein Coding Dojo daran interessiert ist, Anforderungen mit Code umzusetzen, dann ist es gut beraten, die zwei grundsätzlichen Phasen auf dem Weg dahin zu beachten. Ein Dojo muss sich also überlegen, wie es divergentes und konvergentes Denken herstellt.

Divergentes Denken in der Gruppe

Kollektive können gut divergent denken. Kollektive Intelligenz kann bei Problemen, die divergentes Denken erfordern, individueller Intelligenz relativ leicht überlegen sein. Ein Coding Dojo ist also gut aufgestellt, schöne Lösungsansätze für die gegebenen Anforderungen zu finden, da die Teilnehmergruppe gewöhnlich sehr heterogen ist.

Allerdings braucht es dafür eine Voraussetzung: das Dojo muss überhaupt eine Kreativphase durchlaufen. Nach Verständnis der Anforderungen muss es eine Phase geben, in der die kollektive Intelligenz divergent denkt. Ob diese Phase 3 Minuten oder 30 Minuten dauert, sei dahin gestellt. Das hängt ja auch vom Problem ab. Aber ohne eine solche Phase, d.h. einen Zeitraum im “Divergenzdenkmodus”, gibt es keinen Lösungsansatz.

Und wie sieht eine solche Kreativitätsphase aus? Was tun die Gruppenmitglieder da? Meine Vorstellung davon sieht so aus:

  • image Am Anfang der Kreativphase steht eine Stillarbeitsperiode. Jeder überlegt für sich, wie er/sie die Lösung gestalten würde. Warum Stillarbeit, wenn doch alle in der Gruppe sitzen? Weil sonst bei der folgenden Diskussion leicht die “Lauten”, die “Redseligen”, die “Schnelldenker” sich durchsetzen. Wird ohne einen Moment der stillen Besinnung gleich das kreative Getümmel eröffnet, haben “Leise” und “Langsame” wenig Chance, ihre womöglich besseren Ansätze zu Gehör zu bringen. Ohne Stillarbeit reduziert das Kollektiv also sein Potenzial – und nicht unbedingt auf die Kompetentesten. Hierzu mehr in “Group Genius”.
  • Auf die Stillarbeit folgt die Diskussion der verschiedenen Lösungsansätze. Die Gruppe trägt zusammen, was die Heterogenität ausgebrütet hat. Dabei sollten am besten alle (!) Teilnehmer gehört werden. Auch das wieder eine Maßnahme, um kompetente Zurückhaltende nicht zu verlieren.
  • Die Sammlung der Lösungsansätze erfolgt am besten am Flipchart/Whiteboard. Visual Studio oder eine Diagrammsoftware haben hier keinen Platz. Sie sind schlicht zu beschränkend. Die Kreativitätsphase braucht alle Freiheit und Flexibilität, die sie bekommen kann. Mit einem Stift in der Hand auf einer großzügigen Fläche ist sie am größten.
  • image Lösungsansätze sind keine Programme. In der Kreativphase wird nicht codiert. Es geht um ein Denken auf hohem Abstraktionsniveau. Es geht ums Modellieren. Stefan Lieser hat dafür ein Beispiel in seinem Blogartikel gebracht: eine Sequenz von Kullern (Funktionseinheiten mit klarer Verantwortlichkeit) war sein Modell für die Kata BankOCR. In seiner Lösungsskizze ist keine Klasse zu sehen. Und er hat sich auch nicht durch VS oder TDD oder C# behindern lassen. Wo ein konkreter C# Typname auftaucht, ist der auch nicht ganz scharf als Programmiersprachenkonstrukt gemeint. Er ist für Stefan eher nur ein Platzhalter oder ein Ausrutscher in eine andere Sprache. Ein Idiom, dass knackig eine Intention ausdrückt. Mehr nicht.
  • Damit die Kreativphase auf eine Lösung hin konvergiert, muss am Ende irgendwie gefiltert werden. Entscheidungen sind zu treffen. Wie Stefan bin ich der Meinung, dass dafür ein Konsens (Abstimmung) der denkbare falsche Ansatz ist. Im Konsens steckt nämlich kein Bezug zur Sache. Warum jemand die Hand bei einer Abstimmung hebt oder nicht, ist nicht erkennbar. Vielleicht ist sie von einer Lösung überzeugt, vielleicht genervt (und will deshalb schnell einen Mehrheitsbeschluss, damit es weitergeht), vielleicht unentschieden, aber von einem anderen Teilnehmer persönlich beeindruckt. Eine Abstimmungsmehrheit sagt nur aus, dass für einen Lösungsansatz aus irgendwelchen Gründen mehr Leute die Hand gehoben haben als für einen anderen. Das war´s. Alles andere ist eine hübsche Illusion. Konsens/Demokratie gehört in den Bereich der Politik und nicht in den der Wissenschaft oder des Ingenieurwesens.
    Aber was dann? Statt für Konsens plädiere auch ich für Konsent. Der Begriff kommt aus der Soziokratie und ist hier und hier und hier näher beschrieben. Bei Konsententscheidungen geht es nicht darum, eine Mehrheit für einen Vorschlag zu finden. Wer dafür ist, ist quasi egal. Stattdessen sucht der Konsent nach Widerstand. Er wird daher auch die “Herrschaft des Arguments” genannt. Damit kommt die Sache ins Entscheidungsbild. Widerstände müssen sich nämlich auf den Vorschlag und die Sache bzw. ihren Kontext beziehen. Müdigkeit oder persönliche Beeindruckung haben da keinen Platz. Wer Widerstand leistet, d.h. einen begründeten Einwand hat, der muss auch persönlich hervortreten und sich erklären; damit auch hier keine Stimme untergeht, wird im Konsentprozess ebenfalls jedes Gruppenmitglied befragt. Das fokussiert die Diskussion ungemein. Konsententscheidungen sind daher effizient und effektiv. Das kann man von Abstimmungen nicht so einfach behaupten. Denn Mehrheit bedeutet nicht Effektivität (das Richtige entscheiden) und Mehrheit bedeutet auch nicht Effizienz (schnell entscheiden). Wie wir aus der Politik wissen, kann es bis zu einem Konsens lange dauern. Mehr zum Thema z.B. in meinem Vortrag “Agile Entscheidungen” auf dem Mathema Herbstcampus.

Zweck der Kreativphase ist es, den umzusetzenden Lösungsansatz zu finden. Dabei stolpert die Gruppe selbstverständlich über Lücken oder Divergenzen im Verständnis der Anforderungen. Die Kreativphase überlappt daher mit der vorgelagerten Phase der Anforderungserfassung. Die habe ich oben jedoch nicht aufgeführt, weil sie nicht direkt mit der Softwareentwicklung zu tun hat. Softwareentwickler erheben keine Anforderungen, sondern müssen sie “nur” verstehen. Wer mag, darf aber gern Phase 0: Anforderungserfassungsphase vor die Kreativphase setzen.

imageNicht nur durch die Diskussion und weiterentwicklung verschiedener Lösungsansätze, sondern auch den “Rücksprung” in die Anforderungserfassung ist die Kreativphase also kein linearer Prozess. Sie ist “non routine work” und damit auch außerhalb der Reichweite von üblicher motivations-/produktivitätsfördernden Maßnahmen. Zuckerbrot oder Peitsche wirken nicht, um die Kreativitätsphase zu befördern. Im Gegenteil. Soviel als Anmerkung zur Arbeit in Projekten, die ja noch ausgeprägtere Kreativitätsphasen brauchen. Wer das nicht glaubt, der siehe hier.

Fazit: Lösungsansätze in der Softwareentwicklung entstehen nur in einem kreativen Prozess. Heterogene Kollektive können in so etwas gut sein. Allerdings braucht es dafür Raum und Struktur. Drauflosreden und Abstimmungen sind nicht genug. Gerade divergentes Denken muss also moderiert werden. Das Kollektiv soll sich ja auf den Inhalt konzentrieren, nicht auf die Form. Auch hier gilt wieder das Single Responsibility Principle.

Konvergentes Denken für die Umsetzung

Die Umsetzung eines Lösungsansatzes ist etwas anderes als den Lösungsansatz zu finden. Die Umsetzung braucht kein divergentes Denken, kein Querdenken, keine Vielfalt – sondern System. Bei der Umsetzung ist konvergentes Denken gefragt. Das greift auf einen Fundus an Erfahrungen und Techniken zurück. Es übersetzt den Lösungsansatz als Modell in die konkrete Welt der Implementationstechnologien.

Konvergentes Denken ist nun allerdings in der Gruppe schwierig. Tut mir leid, das berichten zu müssen. Aber so ist es. So sagt es die Psychologie. Wo beim divergenten Denken eine heterogene Gruppe hilft, da hilft beim konvergenten Denken eine homogene Gruppe, also eine Expertenrunde.

Wenn ein Coding Dojo nun aber eine heterogene Gruppe darstellt und vor allem eben keine Experten zum Thema TDD zusammenkommen (sondern Entwickler, die es lernen wollen), dann kann ein Coding Dojo per definitionem für die Umsetzung keine guten Voraussetzungen bieten.

In diesem Licht erscheint es doppelt kontraproduktiv, die Kreativphase wie im letzten Coding Dojo anlässlich des dotnetpro.powerday zu überspringen. Nicht nur wurde kein Lösungsansatz erarbeitet, nein, es wurde die Gruppe sogar sofort in die Phase gestoßen, für die sie am schlechtesten aufgestellt ist. Weder existierte also eine gemeinsame Vorstellung von der Lösung, noch eine gemeinsame Vorstellung von der Umsetzung.

Vielleicht ist Ilker da zurecht stolz auf 2 grüne Tests und ca. 10 Zeilen Domänenlogik für die Kata BankOCR gewesen. Denn bei so schlechten Voraussetzungen ist beides vielleicht eine Leistung.

Frustrierend empfinde ich das Ergebnis dennoch. Denn die Gruppe ist um größere Erfolge betrogen worden, weil man nicht “artgerecht” mit ihr umgegangen ist.

Wie hätte es denn aber besser aussehen können? Meine Vorstellung von einer “artgerechten” kollektiven Umsetzungsphase sieht so:

  • Das Coding Dojo definiert explizit, worauf bei der Umsetzung geachtet werden soll. Das kann einmalig geschehen (“Bei allen unseren Dojo achten wir bei der Umsetzung auf ABC und XYZ.”) oder für jedes Dojo immer weder neu (“Heute wollen wir uns auf XYZ bei der Umsetzung konzentrieren.”).
    Wichtig ist in jedem Fall die ausdrückliche Formulierung eines Umsetzungsrahmens. Denn nur so kann das Dojo bei der Umsetzung effizient gehalten werden. Kommentare, die sich auf Verhaltensweisen beziehen, die nicht im Fokus der Umsetzung liegen, müssen nicht verfolgt werden. Das entspricht wieder dem Konsentprinzip.
  • Während der Umsetzung übernehmen einer oder mehrere Experten in Bezug auf die Umsetzungsgrundsätze die Moderation. Sie setzen selbstverständlich nicht allein um; die Gruppe soll so gut es geht auch die Umsetzung vorantreiben. Aber die Experten leiten die Gruppe an. Sie halten die Gruppe im Rahmen der Umsetzungsgrundsätze, geben Hilfestellungen, regen an, erklären.
    Experte kann jeder sein, der sich in Bezug auf Umsetzungsgrundsätze berufen fühlt. Dazu muss niemand eingeflogen werden. Auch der sprichwörtliche Einäugige kann die Blinden anleiten. D.h. wenn sich auch nur einer aus der Gruppe etwas mehr mit einem Umsetzungsgrundsatz auseinandergesetzt und eine Meinung dazu entwickelt hat, kann er die Moderation über nehmen – und sollte es auch.
  • Nach der Umsetzung folgt selbstverständlich eine Reflexion. Wer mag, kann sie als Phase 3 ansehen. Darauf möchte ich hier aber nicht näher eingehen.

Warum braucht das Kollektiv bei der Umsetzung eine Moderation in Bezug auf die Umsetzungsgrundsätze? Weil die Gruppe zusammengekommen ist, um eben diese Grundsätze zu lernen. Wo aber keine/nur wenig Kompetenz ist, entwickelt die sich nicht spontan aus ungeleiteten, tappenden Versuchen einer heterogenen Gruppe.

Wenn man 10jährige in einem Raum sperrt mit einem Haufen elektrischer Bauteile und dem Auftrag, eine Lampenwechselschaltung zu bauen, ist kaum zu erwarten, dass da etwas herauskommt. Die Gruppe wird sich nicht das Wissen aneignen können. Zusammen mit einem Lehrer jedoch ist das möglich. Der baut die Schaltung nicht selbst, lenkt aber immer wieder die Aufmerksamkeit. Didaktik und Methodik müssen stimmen. Dann findet effizientes und effektives Lernen statt.

Bei einem Coding Dojo ist das nicht anders. Ohne Anleitung findet kein verlässliches Lernen in Bezug auf die ausgelobten Inhalte statt. Das sind meist die Umsetzungsgrundsätze (z.B. TDD, Architektur, OOP). Experten führen die Gruppe hin zur Kompetenz in Bezug auf die Umsetzungsgrundsätze. Sie greifen dabei so wenig wie möglich in den Gruppenprozess ein, aber soviel wie nötig, um den Fokus zu halten.

Irgendwas kann natürlich jeder auch ohne Anleitung lernen. Ich z.B. habe beim letzten Dojo gelernt, wie man rechteckige Blöcke in VS2010 markiert. So nett das aber auch ist, dafür gehe ich nicht ins Coding Dojo. Das kann ich auch bei einer Plauderei einer .NET User Group erfahren.

Fazit: Damit die Umsetzung eines Lösungsansatzes zügig und erfolgreich ist, braucht es Können. Wo das Können fehlt, muss es über Experten in die Gruppe eingebracht werden. Aber auch wo das Können schon in der Gruppe ist, braucht es Moderation für eine kohärente Arbeit der Gruppe.

Kollektive Intelligenz mit System

Ich schätze Ilker für seine hochmotivierte Art, Dojos durchzuführen. Und ich schließe mich seinen Latifa-Werten an.

Aber ich möchte bei ihnen nicht stehenbleiben. Sie sind für mich nur ein Rahmen, in dem für mich das eigentliche Lernen stattfindet. Und dafür ist meine Vision, dass es verlässlich stattfindet in Bezug auf klar kommunizierte Ansätze in der Umsetzungsphase oder auch der Kreativphase.

Darüber hinaus bin ich auch der Überzeugung, dass solches Lernen nur funktioniert, wenn es nicht eine gewisse Führung gibt. Im besten Fall versteht die mehr vom Lerninhalt als der Rest der Gruppe. Sollte das jedoch einmal nicht der Fall sein, ist noch nicht alles verloren. Allerdings muss die Moderation dann besonders sensibel sein.

Bei allem Willen zu Spaß beim Dojo und selbstverständlich grundsätzlicher Gleichberechtigung aller Teilnehmer glaube ich allerdings nicht, dass demokratische Anwandlungen oder “code first” Denke zielführend sind.

Mir scheint, dass die bisherigen Dojos die Kompliziertheit von Problemen unterschätzen und die Leichtigkeit, mit der sich Ansätze wie TDD lernen lassen, überschätzen. Sie glauben, ohne Kreativitsphase (oder gar Anforderungserfassungsphase) auszukommen. Sie glauben, ohne Expertenmoderation in der Umsetzungsphase auskommen zu können. Ihr Credo: “Durch den Code zur Wahrheit und zur Erkenntnis”

Dass es nicht so einfach ist, zeigen dann aus meiner Sicht die magerer Erfolge der Dojos. Wie gesagt: Irgendwas wird schon gelernt. Aber wird das gelernt, was sich die Dojo-Macher wünschen? Ich bezweifle es. (Wenn Sie sich überhaupt etwas wünschen jenseits des Barnum-Mottos “Hier ist für jeden was dabei.”)  Wird gelernt, was sich die Teilnehmer wünschen? Ich bezweifle es. (Wenn die überhaupt einen konkreten Lernwunsch haben und nicht bloß aus Neugierde kommen.)

Damit will ich nicht das Engagement bisheriger Dojo-Macher kritisieren. Im Gegenteil! Ich finde es sehr cool, dass wir hier in der .NET-Welt eine solche Szene entwickelt haben und eifrig diskutieren. Nochmal danke an Vorreiter Ilker!

Aber was am Anfang gut und ausreichend gewesen sein mag, ist es vielleicht jetzt nicht mehr. Der Geschmack kommt beim Essen. Und da es nun Katas und Dojos gibt, ist es – so scheint mir – an der Zeit, weiter zu gehen. Bisher war Dojo 1.0. Ich möchte jetzt Dojo 2.0. Ich möchte Coding Dojos nach oben skizziertem Schema, die noch mehr bieten als die bisherigen. (Oder vielleicht nicht mehr, sondern Anderes.)

image Und deshalb präge ich jetzt mal einen weiteren Dojo-Stil: den Schwarm-Stil. Ilkers Stil ist der Latifa-Stil, meiner der Schwarm-Stil. Warum Schwarm in der Stilbezeichnung? Das ist für mich eine Anspielung auf das Buch “Der Schwarm” von Frank Schätzing. Darin kommt eine Intelligenz vor, die sich aus “sichtbaren” Individuen zusammensetzt, eine kollektive Intelligenz, eine Schwarmintelligenz.

Mir ist klar, dass am Begriff Schwarmintelligenz eine Menge hängt, was nicht zum Coding Dojo passt. Aber egal ;-) Ich finde ihn knackig und es steckt auch das Kollektiv drin.

Jetzt stellt sich nur die Frage, wann und wo das erste Schwarm-Stil Coding Dojo stattfindet. Mal überlegen… Wenn es konkreter wird, berichte ich hier im Blog.