Follow my new blog

Dienstag, 7. Dezember 2010

Ein Traum von Softwareentwicklung

Lassen Sie für einen Moment mal alle eingefahrenen Vorstellungen von Objektorientierung und Agilität und Schichtenmodellen hinter sich. Folgen Sie mir einfach auf einer Traumreise in ein anderes Land der Softwareentwicklung…

Alles beginnt mit einem Unternehen. Das möchte eine Software in Auftrag geben. Es stellt sich den weltbesten online Shop vor, der es im Nu auf Augenhöhe mit Amazon, eBay und Expedia bringt. Das Budget hat der Vorstand schon fixiert. Die Wünsche wurden in den Fachabteilungen gesammelt. Und die IT hat auch schon ihren Senf dazu gegeben, denn irgendwer muss die Software hinterher ja auch betreiben. Es kann also losgehen…

Als Unternehmen, will es natürlich soviel wie möglich für sein Geld. Und als Teilnehmer an den komplexen Märkten des 21. Jahrhunderts will es gleichzeitig maximale Flexibilität auf dem Weg zum Maximum für sein Geld. Das scheint ein Widerspruch. Doch das Unternehmen hat einen Weg gefunden, beide Wünsche unter einen Hut zu bringen: Es vergleicht jeden Tag den Stand der Softwareentwicklung mit den Bedürfnissen der Anwender.

Jeden Tag ziehen sich die Anwender (bzw. ihre Vertreter für die Softwareabnahme während der Entwicklungsdauer) den aktuellen Stand der Software und probieren die hinzugekommene oder veränderte oder korrigierte Funktionalität aus. Das Entwicklungsteam stellt also jeden Tag ein neues Release bereit, das wieder etwas besser ist, als das vom Vortag. Und diese Verbesserungen sind immer anwenderrelevant; die Anwender können einen Unterschied erkennen, der ihnen wichtig ist – und sei der auch nur klein. So können sie jeden Tag feststellen, ob die Software noch auf Kurs zu ihrem Ziel ist. Sehen sie Abweichungen, steuern die Anwender nach, indem sie ihre Anforderungen verändern. Und das jeden Tag aufs Neue, wenn sie wollen.

Das Softwareentwicklungsteam unterstützt den Wunsch der Anwender, jeden Tag eine verbesserte Version testen zu wollen. Dafür hat es zunächst einen automatischen Build- und Deliveryprozess aufgesetzt, der dafür sorgt, dass alle Softwareartefakte jeden Tag gebaut, überprüft, verpackt und für den Download durch die Anwender bereitgestellt werden.

Die Überprüfung besteht aus automatisierten Tests und stellt vor allem sicher, dass die Software frei von Regressionsfehlern ist. Soweit möglich, sollen die Tests aber natürlich auch “beweisen”, dass hinzugekommene Funktionalität von vornherein keine Fehler enthält und den formalisierten Anforderungen (Akzeptanztests) entspricht.

Damit der automatische Build- und Deliveryprozess auch jeden Tag etwas zu tun hat, muss die Codebasis natürlich jeden Tag anwenderrelevante Änderungen aufweisen. Das Entwicklungsteam muss also jeden Tag etwas Auslieferbares fertigstellen. Ganze User Stories, wie sie das Unternehmen an das Entwicklungsteam heranträgt, sind natürlich viel zu groß, um sie in einem Tag umsetzen zu können. Auch Features herausgelesen aus User Stories passen noch nicht in einen Tag. Aber Features Slices, d.h. hauchdünne Längsschnitte durch Features, die lassen sich in einem Tag realisieren. Manchmal braucht es dafür ein wenig Phantasie, um sie in den Features zu erkennen, doch allermeistens klappt das.

So teilt das Team die Anforderungen in mehreren Schritten in Happen von Tagesumfang. Ob das 3, 4 oder 6 Arbeitszeitstunden sind, hängt vom Team und seiner Möglichkeit und Fähigkeit zur Fokussierung ab.

In wenigen Stunden muss und will das Team also Anwendernutzen auf die Straße bringen. Dafür ist maximale Geschwindigkeit in der Umsetzung nötig. Die erreicht das Team dadurch, dass es alle Entwickler auf das Feature Slice des Tages ansetzt. Alle arbeiten daran parallel, so wie beim Formel 1 Boxenstopp die Mechaniker an einem Rennboliden.

Dass sich dabei die Teammitglieder nicht ins Gehege kommen, also durch Konflikte Zeit verlieren, stellt die Komponentenorientierung sicher. Jedes Teammitglied bekommt für die Dauer einer Feature Slice Realisierung eine Anzahl von Komponenten zugeteilt, für die es allein verantwortlich ist. Alle Arbeit findet dann pro Entwickler konzentriert an einer Komponente zur Zeit statt. Die Kontrakte der Komponenten stellen dabei sicher, dass Änderungen keine unerwartete Reichweite haben. Die Komplexität hält sich durch kontraktuelle Entkopplung in Grenzen und automatisierte Tests in Isolation helfen, die Fehlerzahl gering zu halten.

Komponenten und Entwicklerzuordnung (Feature Team) sind das Ergebnis der Arbeitsorganisation des Teams. Vor Beginn der Feature Slice Realisierung identifiziert es die betroffenen Komponenten und weist sie Teammitgliedern nach Kompetenz, Erfahrung, Wunsch, Verfügbarkeit usw. zu.

Welche Komponenten aber überhaupt zur Auswahl stehen, ergibt sich aus den Bauteilen eines Event-BasedComponents (EBC) Modells, die innerhalb eines Architekturrahmens und im Sinne maximal paralleler Realisierbarkeit zu Komponenten zusammengefasst werden. Das Modell definiert die Funktionseinheiten auf verschiedenen Abstraktionsebenen, die die Anforderungen erfüllen. Das tut es unabhängig von der Implementierung, um die Lösungsplanung schneller und flexibler zu halten.

Das Team modelliert die Lösung also, um ganz präzise zu wissen, welche Funktionseinheiten zu implementieren sind. Die werden bestimmt durch die Funktionalität, die User Stories/Features beschreiben. Andererseits sind aber auch nicht-funktionale Anforderungen zu erfüllen. Dafür direkt Funktionseinheiten während der Modellierung zu finden, hieße jedoch, die Modellierung zu überfrachten. Deshalb entwirft das Entwicklungsteam vor dem Modell eine Softwarearchitektur. Sie definiert einen Rahmen von architekturellen Funktionseinheiten, in den sich das Modell mit seinen Funktionseinheiten einpassen muss.

Präzise Kenntnis der zu implementierenden Funktionseinheiten, die die funktionalen wie nicht-funktionalen Anforderungen dienen, ist Voraussetzung für die tägliche Feature Slice Realisierung in Höchstgeschwindigkeit.

So wichtig ein guter Entwurf jedoch auch sein mag, um den Anwender ihre Flexibilität zur Kursänderung zu geben, er reicht nicht aus. Mit Höchstgeschwindigkeit können nur Entwickler arbeiten, die genau wissen, was sie tun. Wie genau kann nun aber Wissen sein, wenn sich nicht nur die Domäne bewegt, sondern vor allem der Markt der technischen Realisierungsmöglichkeiten?

Das Entwicklerteam trennt deshalb ganz klar zwischen “Performance”, d.h. Zeit, in der es in Höchstgeschwindigkeit entwickelt, und “Übung”. Feature Slices realisiert das Team selbstverständlich in seiner Performance-Zeit. Da sitzt jeder Handgriff. Alle arbeiten wie geschmiert miteinander. Was zu tun ist, liegt klar auf der Hand bzw. ist aus Architektur, Modell und Arbeitsorganisation für jeden abzulesen.

Damit Performance-Zeit möglich ist, reflektiert das Entwicklerteam jedoch ständig darüber, was es noch nicht genau weiß. Es hinterfragt ständig sein Wissen in Bezug auf Domäne und Technik. Und wo es Wissenslücken auftut, da setzt es ausdrückliche Übungszeit an, um sie zu schließen. Ist die Wissenslücke projektspezifisch technisch, dann wird sie im Rahmen einer Spike Solution angegangen. Ist die Wissenslücke allgemein, dann wird sie durch Fortbildung geschlossen.

Domänenwissenslücken schließlich trägt das Entwicklerteam an die Anwender heran. So schließt sich der Kreis. Der Anwender repräsentiert durch die Rolle ProductOwner ist die entscheidende Klammer um die Softwareentwicklung.

Der Anwender “zieht täglich am Team”, weil er den Fortschritt der Entwicklung kontinuierlich gegen seine Anforderungen halten will. Nur so kann er schnell reagieren. Nur so bekommt er auch das Maximum für sein Geld.

Und das Team “zieht täglich am Anwender”, um dessen Anforderungen von vornherein möglichst genau zu treffen. Das heißt, der Anwender steht immer für Rückfragen zur Verfügung, um das Team in seiner täglichen Feature Slice Produktion nicht zu behindern.

Damit der gegenseitige Zug wirklich funktioniert, braucht es abschließend natürlich eines: Verlässlichkeit. Das Entwicklerteam muss verlässlich Anforderungen in Software mit Anwendernutzen transformieren. Der Anwender muss die Software verlässlich sofort abnehmen. Und der Anwender muss verlässlich für Rückfragen zu den Anforderungen bereitstehen.

Ohne Verlässlichkeit ist die ganze Softwareentwicklung nichts. Oder wenn sie etwas ist, dann zumindest nichts, das wie geschmiert und mit Höchstgeschwindigkeit abläuft.

Und ohne Verlässlichkeit kein Vertrauen. Denn Vertrauen kann man nicht befehlen; es entwickelt sich nur, wo Verlässlichkeit erfahren wird.

Das hat das Unternehmen begriffen und verzichtet daher auf den üblichen Festpreismodus für die Entwicklung des online Shops. Es hat sein Budget fixiert, es hat Wünsche gesammelt – und dann überträgt es die Realisierung einem Entwicklungsteam, zu dem es Vertrauen hat, dass es die Realisierung in Höchstgeschwindigkeit beherrscht.

Da Vertrauen immer gegenseitig sein muss, stellt das Unternehmen natürlich sicher, dass es selbst dem Entwicklungsteam ein verlässlicher Partner ist, der die erwartete Höchstgeschwindigkeit mittragen will und kann.

Und so wird am Ende der Traum wahr: Das Unternehmen bekommt ohne Budgetüberschreitung das immer wieder flexibel neu bestimmte Maximum.

Jetzt können Sie die Augen wieder öffnen. Lassen Sie den Traum aber noch etwas nachwirken. Vielleicht bewirkt er ja etwas bei Ihnen. Vielleicht machen Sie sich ja auf den Weg, aus dem Traum Realität werden zu lassen… Ich glaube, das ist möglich.

Montag, 6. Dezember 2010

Was ist Softwarearchitektur – Teil 2

Softwarearchitektur ist eine der beiden Entwurfsdisziplinen der Softwareentwicklung. Ihr Ergebnis ist die fundamentale Struktur einer Software im Hinblick auf die nicht-funktionalen Anforderungen.

Das ist für mich schon eine recht präzise Definition – allerdings eine allgemeine. Sie beantwortet noch nicht alle Fragen in der Praxis. Gehört die Entscheidung für IIS und gegen NT Service eine Architekturentscheidung? Ist die Entscheidung für eine Aufteilung der Rechtschreibprüfung in Textzerlegung und Wortprüfung eine Architekturentscheidung? Oder ist die Entscheidung für die Zusammenfassung von Funktionalität zu einer Komponente eine Architekturentscheidung?

Um diese und andere Fragen zu beantworten, müssen Sie erstens mein allgemeines Verständnis von Struktur kennenlernen und zweitens meine Vision davon, wie Architektur Strukturen konkretisiert.

Was ist eine Struktur?

Als Struktur verstehe ich “etwas”, das aus Elementen besteht, die in Beziehung zu einander stehen.

image

Strukturen mit vielen Elementen und/oder Beziehungen sind unübersichtlich. Deshalb gehört zu Struktur auch noch die Abstraktion. Ein System wie im Bild beschreibt eine Struktur also nur auf einer Abstraktionsebene, andere können darunter/darinnen liegen…

image

oder darüber/darum…

image

Wer eine Struktur entwirft, muss sich also drei Fragen stellen:

  1. Welche Abstraktionsebenen hat die Struktur?
  2. Welche Elemente enthält eine Abstraktionsebene (innerhalb eines womöglich umschließenden Elements einer höheren Abstraktionsebene)?
  3. Wie sind die Elemente in einer Abstraktionsebene verbunden?

Die Abstraktionsebenen und Strukturelemente der Softwarearchitektur

Mit einem allgemeinen Strukturverständnis bewaffnet können Sie nun verstehen, dass Softwarearchitektur als Entwurf auf verschiedenen Ebenen betrieben werden sollte. Ohne diese Ebenen ist die Kompliziertheit eines Systems nicht in den Griff zu kriegen.

imageDie Abstraktionsebenen der Softwarearchitektur sind für mich diese:

  1. Das gesamte Anwendungssystem
  2. Bounded Contexts
  3. Partitionen/Apps
  4. (Virtuelle) Maschinen
  5. Betriebssystemprozesse
  6. Belange/Concerns

Ein paar Worte zu den Ebenen:

Anwendungssystem

Ein Entwicklungsauftrag wird gewöhnlich auf der Ebene des Anwendungssystems erteilt: “Schreiben Sie mir eine Software, die dies und jenes kann.” Das kann eine Warenwirtschaft sein, ein TicTacToe-Spiel oder eine Textverarbeitung. Wenn implementiert, erfüllt das Anwendungssystem alle Anforderungen.

Bounded Contexts

Das Gesamtsystem zerlegt der Architekt in Bounded Contexts, d.h. funktionale Untermengen, die unterschiedliche Datenmodelle haben. Sie gehören innerhalb des Gesamtsystems, der Anwendungsdomäne verschiedenen Sub-Domänen an. Bei einem Faktura-Anwendungssystem könnten das Rechnungslegung und Rechnungsverfolgung und Stammdatenverwaltung sein.

In jedem Bounded Context können Daten anders persistiert werden. Das drückt die kleine Tonne im Bild aus.

Nicht-funktionale Anforderungen, die auf dieser Ebene vor allem bedient werden: Evolvierbarkeit, Skalierbarkeit, Sicherheit

Apps

Innerhalb eines Bounded Context kann die Funktionalität weiter aufgeteilt werden auf Partitionen oder – neuerdings von mir so genannt – Apps. Jede App steht für eine klar umrissene Funktionalität, die durch ein spezifisches Userinterface bedient wird. iPhone/iPad Apps spiegeln diesen Gedanken sehr schön wider, finde ich. Oder die vielen kleinen Tools unter Unix.

Alle Apps in einem Bounded Context benutzen dieselbe Datenbasis.

Nicht-funktionale Anforderungen, die auf dieser Ebene vor allem bedient werden: Usability, Evolvierbarkeit

Maschinen

Jede App läuft auf mindestens einer Maschine – vom Server über den Desktop bis zum Smartphone –, kann aber auch mehrere Maschinen überspannen wie bei einer Web-Apps, die sowohl im Browser wie im IIS wie in einem Application Server wie in der Datenbank Codeteile hat.

Nicht-funktionale Anforderungen, die auf dieser Ebene vor allem bedient werden: Skalierbarkeit, Performance, Sicherheit, Verfügbarkeit, Robustheit

Prozesse

Auf jeder Maschine einer App läuft deren Code mindestens in einem Prozess, es können aber auch mehrere sein. Zum Beispiel könnten ein Application Server und ein Reporting Server auf derselben Maschine betrieben werden.

Nicht-funktionale Anforderungen, die auf dieser Ebene vor allem bedient werden: Skalierbarkeit, Performance, Evolvierbarkeit, Flexibilität

Concerns

Die Zerlegung des Anwendungssystems bis in Prozesse führt zu autonomen Funktionseinheiten. Die arbeiten jede für sich vor sich hin. Jede läuft auf mindestens einem eigenen Thread. Die Kommunikation ist mithin ganz fundamental nur asynchron zwischen ihnen möglich.

Diese autonomen Funktionseinheiten zu identifizieren, ist die vornehmste Aufgabe des Architekten.

Als Vorbereitung für die Modellierung und Implementierung ist das allerdings noch nicht genug, finde ich. Einen Schritt weiter sollte der Architekt noch gehen. Er sollte innerhalb der Prozesse auch noch die grundsätzlichen Concerns herausarbeiten, d.h. Bereiche, die gezielt nicht-funktionale Anforderungen realisieren. Das sind z.B. Frontend, Persistenz, Kommunikation, Sicherheit, Domänenlogik.

Die Modellierung hat es dann einfacher, zu einem Entwurf zu kommen, weil die Concerns eine Grundeinteilung für die Funktionseinheiten des Modells vorgeben. Wenn das Modell sich um Funktionalität kümmert, dann muss es wissen, um welche Funktionalität es geht. Domänenfunktionalität ist nicht die einzig zu modellierende.

Beziehungen in Abstraktionsebenen

Auf welchen Abstraktionsebenen der Architekturentwurf stattfindet, ist geklärt. Die wesentlichen Strukturelemente sind Bounded Context, App, Maschine und Prozess. Aber wie werden die mit Beziehungen innerhalb einer Ebene zu einem System verwoben? Ich denke, das sollte auch definiert sein, denn dann lassen sich weitere Fragen leichter beantworten.

Bounded Context

Bounded Contexts sind durch Datensynchronisationen miteinander verbunden. Sie teilen weder Datenbanken noch Datenmodelle, also müssen die Daten explizit zwischen Bounded Contexts synchronisiert werden. Das dauert natürlich seine Zeit. Deshalb ist über Bounded Contexts hinweg nur Eventual Consistency zu erwarten.

Fragen für die Kommunikation: Wie soll die Datensynchronisation implementiert werden? Gibt es Tools (Stichwort ETL)? Wie groß das die maximale Verzögerung sein? Kann es Konflikte geben, wie werden die behandelt?

Apps

Apps müssen nicht direkt miteinander kommunizieren, sondern teilen sich eine Datenbasis. Sie ist das Bindeglied. Zentral für die Apps eines Bounded Context ist daher das eine Datenmodell.

Fragen für die Kommunikation: Welche Persistenztechnologie soll verwendet werden (RDBMS, Dokumentendatenbank, Graphendatenbank, Dateisystem usw.)? Wie sieht das Schema aus?

Maschinen

Maschinen sind über (virtuelle) Leitungen miteinander verbunden; sie teilen keinen physischen Adressraum. Die Kommunikation kann daher nur nachrichtenorientiert und asynchron ablaufen. Und ich würde noch hinzufügen: Die Kommunikation sollte zwischen Maschinen auch immer als asynchron in der Implementierung zu sehen sein.

Fragen für die Kommunikation: Welche Kommunikationstechnologie soll zum Einsatz kommen, insb. wenn die Maschinen mit unterschiedlichen Betriebssystemen betrieben werden? Wie sollen Leitungsunterbrechungen behandelt werden?

Prozesse

Prozesse laufen zwar im selben physischen Adressraum, werden vom Betriebssystem darin jedoch sauber getrennt. Die Kommunikation kann zwischen ihnen kann daher auch nur nachrichtenorientiert sein. Allerdings ist zu erwarten, dass Prozesse in der selben Maschine verlässlicher vorhanden/erreichbar sind als solche in anderen Maschinen. Deshalb ist zu überlegen, ob die Kommunikation zwischen Prozesse in derselben Maschine die Kommunikation immer als asynchron in der Implementierung zu sehen sein muss. Im Augenblick tendiere ich dahin, meine Position, dass das immer so sein muss, zu lockern. Das bedeutet jedoch: Wenn ein Architekt sich für eine synchrone Kommunikation zwischen Prozessen entscheidet, weil er sie auf derselben Maschine laufen sieht, dann können diese Prozesse später nicht auf verschiedenen Maschinen deployt werden. Denn die sollen asynchron kommunizieren (s.o.). Soviel Grundsatz sollte sein, finde ich.

Fragen für die Kommunikation: Welche Kommunikationstechnologie soll zum Einsatz kommen? Welche Hosts für den Code sollen benutzt werden (z.B. IIS, Desktopanwendung, NT Service)?

Concerns

Concerns sind funktionale Bereiche innerhalb von Prozessen. Sie kommunizieren also im selben Adressraum synchron via Stack oder globale Speicherbereiche.

Fragen an die Architektur

Mit diesen Konkretisierungen lassen sich die obigen Fragen und andere beantworten. Ist die Entscheidung für IIS oder NT Service eine Architekturentscheidung? Das ist natürlich eine Architekturentscheidung auf Prozessebene.

Wer sich über WCF vs NServiceBus unterhält, unterhält sich ebenfalls über Architektur. REST vs SOAP? Auch ein Architekturthema – mindestens auf Maschinenebene. Was ist mit OAuth? Ein Architekturthema. Was ist mit NoSql? Ein Architekturthema. Was ist mit WPF? Nur insofern ein Architekturthema als dass WPF auf bestimmten Devices (nicht) möglich sein könnte. Was ist mit Bubblesort vs Quicksort? Kein Architekturthema, auf wenn es um die nicht-funktionale Anforerung Performance gehen mag; denn die Unterscheidung ist keiner Abstraktionsebene der Architektur zuzuordnen. Code im Application Server oder im Datenbankserver laufen lassen? Ein Architekturthema.

Architektur hat mit der Verteilung von Funktionalität zu tun, um nicht funktionale Anforderungen zu erfüllen. Wenn Funktionalität verteilt wird, dann stellen sich Fragen dazu, wie die Funktionalität an ihren Orten gehostet werden soll und wie die verteilten Teile miteinander kommunizieren sollen.

Verteilt wird Funktionalität in zwei Weisen:

imageArchitektur macht zunächst auf den Abstraktionsebenen Bounded Context und App Längsschnitte durch das Anwendungssystem. Architektur schneidet es in dünne Scheiben. Die sind vollständig insofern, als dass sie vom Frontend bis zum Backend reichen.

Der Entwurf beginnt also damit zu überlegen, wie die gesamten Anforderungen in überschaubare Happen zerlegt werden können. Welche für den Anwender sinnvollen Subsysteme lassen sich finden, um nicht sofort und immer alles umsetzen zu müssen? Bounded Contexts und Apps sind daher auch fundamental für die Entkopplung von Code. Sie dienen unmittelbar der Evolvierbarkeit.

imageApp, Maschine und Prozess zerlegt die Architektur dann transversal weiter. Die funktionalen Scheiben werden “gewürfelt”, um insbesondere Skalierbarkeit, Performance und Verfügbarkeit zu erreichen. Jedes Teil stellt dann nicht mehr für sich einen Anwendernutzen her, sondern trägt nur dazu bei. (Hier ist die klassische Schichtenarchitektur anzusiedeln.)

Zusammenschau

Meine Erfahrung mit der Definition von Architektur in dieser Weise ist, dass sie sich leicht erklären lässt und Fragen beantwortet, bei denen zumindest ich früher “rumgeeiert bin”. Ich behaupte nicht, dass diese Definition vollständig ist; ich empfinde sie lediglich als pragmatisch-praktisch-gut ;-) Nützlichkeit sowie Kommunikationsfähigkeit stehen für mich hier höher als formale Reinheit.

Was ist Softwarearchitektur? – Teil 1

Was ist eigentlich Softwarearchitektur? Dazu kann man natürlich eine Menge lesen. Aber bisher hatte ich deshalb noch kein so richtiges Gefühl dafür. Wissen (im Kopf) und “Gefühlsgewissheit” sind eben nicht dasselbe.

Nun hat sich das aber geändert. Jetzt fühle ich, was ich bisher vielleicht schon wusste. Oder ich habe das Gewusste nun so verändert, mir gemäß angepasst, dass ich auch wirklich dahinter stehen kann.

Also, was ist Softwarearchitektur? Bei Wikipedia steht: Softwarearchitektur ist…

[…] eine strukturierte oder hierarchische Anordnung der Systemkomponenten sowie Beschreibung ihrer Beziehungen. […] Dabei enthält eine Beschreibung der Software-Architektur nicht nur Informationen über die Struktur […] eines Software-Systems, sondern auch Informationen über die Kommunikation zwischen Komponenten, sowie deren Abbildung auf Hardware- oder Software-Ressourcen (Verteilung und Deployment). […] Sie wird wesentlich durch Softwarequalitätskriterien, also nicht-funktionale Eigenschaften wie Modifizierbarkeit, Wartbarkeit, Sicherheit oder Performance bestimmt (siehe beispielsweise FURPS). Eine einmal eingerichtete Softwarearchitektur ist später nur mit hohem Aufwand abänderbar. Die Entscheidung über ihr Design ist somit eine der kritischsten und wichtigsten Punkte im Entwicklungsprozess einer Software.

Hilft das weiter? Bis vor wenigen Tagen hätte ich gesagt, “Hm… vielleicht” ;-) Aber heute kann ich sagen: “Ja, das ist es!” Allerdings braucht es noch etwas mehr, als diese Worte. Die finde ich nun zwar richtig, doch sehr abstrakt. Die Abstraktheit war es bisher, die mir für eine “Gefühlsgewissheit” im Wege stand.

Damit es Ihnen nicht so geht wie mir, will ich im Folgenden versuchen, die Definition für Sie konkreter zu machen. Dafür stelle ich sie in einen Kontext, grenze ich sie ab und konkretisiere sie für .NET.

Vorher allerdings eine verkürzte Definition zum leichteren Erinnern:

Softwarearchitektur kümmert sich um die nicht-funktionalen Anforderungen an Software

Kontext

Software entsteht nicht einfach so, nachdem Sie Anforderungen verstanden haben. Sie setzen sich danach nicht sofort an die Tastatur und klimpern los. Naja, zumindest sollten Sie das nicht tun ;-) Softwareentwicklung besteht daher mindestens aus zwei Phasen: einer Planung und einer Ausführung. Die Ausführung nenne ich Implementierung.

image

Die Planung baut natürlich auf dem Verständnis der Anforderungen auf. Aber die lasse ich mal heute außen vor.

Unter Planung verstehe ich das Nachdenken darüber, wie das, was am Ende implementiert werden soll, aussehen soll, und wie man dieses Ziel am besten erreicht. Das ist natürlich eine ganze Menge. Also zerfällt die Planung nochmal in die Planung von Strukturen und die Planung der Herstellung der Strukturen. Ersteres nenne ich Entwurf, letzteres (Arbeits)Organisation.

image

Und wann kommt endlich die Softwarearchitektur ins Spiel? Jetzt. Sie ist einer der beiden Teile des Entwurfs. Softwarearchitektur entwirft die Strukturen einer Software soweit, dass sichergestellt ist, dass die nicht-funktionalen Anforderungen erfüllt werden. Um die funktionalen Anforderungen kümmert sich dann die Modellierung.

image

Es ergibt sich also das “Gleichungssystem”:

  • Softwareentwicklung = Planung + Implementierung
    • Planung = Entwurf + Arbeitsorganisation
      • Entwurf = Architektur + Modellierung

Das ist meine Verortung von Softwarearchitektur im Kontext der Softwareentwicklung. Sie hat sich bisher als pragmatisch-praktisch erwiesen. Ich strebe damit keine vollständigumfassendalleinseligmachende Definition an, sondern nur eine, die alltagstauglich ist, die ich leicht vermitteln kann.

Vor allem bekomme ich damit ein paar Begriffe unter einen Hut. Architektur/architecture und Entwurf/design waren für mich bisher nämlich nur unscharf gegeneinander abgegrenzt. Jetzt ist mir ihr Verhältnis klar. Entwurf/design ist der umfassendere Begriff; Architektur/architecture ist nur ein Teil des Entwurfs/design.

Oder Modellierung/modelling und Architektur/architecture: Nun kann ich sie klar unterscheiden, da Architektur/architecture sich um die nicht-funktionalen Anforderungen kümmert und Modellierung/modelling um die funktionalen. Natürlich bewegt sich  Modellierung/modelling dabei im von der Architektur/architecture vorgegebenen Rahmen. Denn Architektur/architecture entwirft das Softwaresystem auf einer grundlegenden Ebene; die entstehenden Strukturen sind vergleichsweise schwer zu ändern.

Wer sich einmal entscheidet, ein kleines Wochenendhaus am Hang eines Berges zu bauen, der kann daraus später nicht einen Wolkenkratzer machen. Und wer einen Sportwagen entwirft, der entwirft andere Strukturen, als würde er einen Trecker planen. In beiden Fällen stellen die nicht-funktionalen Anforderungen (z.B. Hanglage oder hohe Geschwindigkeit) Weichen, die den Entwurf der Strukturen zur Erfüllung der funktionalen Anforderungen auf gewisse Bahnen lenkt.

Daraus ergibt sich für die Anforderungserhebung: Die vornehmste Aufgabe eines Anforderungsanalysten ist es, die nicht-funktionalen Anforderungen zu erheben. Sie müssen möglichst früh und möglichst vollständig vorliegen. Aus ihnen leitet der Softwarearchitektur dann möglichst zügig und vor weiterer Planung die fundamentale Struktur des Softwaresystems ab.

Dazu mehr im nächsten Teil dieser kleinen Serien…

Montag, 22. November 2010

Vom Nutzen der Code Kata für das Entwicklerleben

imageGrad gibt es ja mal wieder Diskussion um eine Code Kata, die Kata Tennis. Siehe u.a. die Kommentare hier und hier. Anlass war ein online Coding Dojo der Online .NET User Group. Da gehen die Meinungen darüber, wie man am besten zu einer Lösung der Aufgabe kommt, nun auseinander. Das ist gut so. So kommen wir alle weiter, das macht grundsätzlich Spaß.

Allerdings hat die Diskussion für mich auch unbefriedigende Züge. Sie wird nämlich mühsam, wenn wir uns nicht auf technische Aspekte konzentrieren können, weil uns unterschiedliche Ansichten zur Form im Wege stehen, genauer sogar: unterschiedliche Ansichten zum Zweck von Code Katas.

Was soll das also mit den Code Katas?

Nutzen #1: Lernen

Code Katas sind Übungsaufgaben. An ihnen soll man irgendwas lernen. Sie bieten ein überschaubares Problem ab vom Projektalltag, an dem man Techniken, Konzepte, Tools, Vorgehensweisen ausprobieren kann, ohne Angst vor Fehlern haben zu müssen. Feedback stellt sich schnell ein, weil die Aufgaben klein sind. Hat man eine Lösung zum Laufen gebracht? Hat man die Technik, das Tool, die Vorgehensweise mit Gewinn eingesetzt? Das lässt sich in wenigen Stunden herausfinden. Und wenn es Differenzen zwischen Wunsch und Wirklichkeit gibt, kann man es dann nochmal probieren mit derselben oder einer anderen Kata.

Nutzen #2: Spaß

Code Katas sind von Thema und Umfang so geschnitten, dass es Spaß macht (oder zumindest machen sollte), sich mit ihnen allein oder in Gemeinschaft zu beschäftigen. Da ist für jeden etwas dabei. Ein bisschen Knobeln, aber nicht zuviel. Der Spaß entsteht durch Herausforderung auf einem für jeden selbst wählbaren Problemniveau mit Feedback in absehbarer Zeit.

Lernen mit Spaß: Darum gehts bei Code Katas, würde ich mal sagen. Nicht mehr und nicht weniger.

imageDas ist übrigens nicht neu. Solche kleinen Aufgaben gibt es schon seit Jahrzehnten. Die pragmatischen Programmierer haben sie also nicht erfunden. Da gibt es die legendären Programming Pearls von Jon Bentley. Oder den Bundeswettbewerb für Informatik, den International Collegiate Programming Contest der ACM oder hier eine wettbewerbsübergreifende Sammlung von Aufgaben.

Wer Übungen sucht, um seine Programmierfertigkeiten zu trainieren, der findet also reichlich Material. Wie wäre es z.B. mal mit einem APL Interpreter oder Barcodeerkennung statt der ewigen Fizzbuzzpotterbankocrtennisaufgaben? Zufinden hier. Vielleicht ist da dann auch mal was dabei, was nicht so trivial ist, dass wir ewig darüber diskutieren müssen, ob TDD allein ausreicht für eine Lösung. Denn am Ende sind die bisher im Umlauf befindlichen Katas eher so klein, dass man mit oder ohne spezielle Methoden und Konzepte zu einer lauffähigen Lösung kommt.

Übrigens: Wer Spaß am Knobeln und am Wettbewerb hat, kann mit “Code Katas” sogar Geld verdienen. Einfach mal bei TopCoder vorbeischauen.

Freiheit der Form

Womit wir bei der Frage sind, was denn unverbrüchlich zu Code Katas gehört, um den Nutzen zu erreichen? Gehört TDD dazu? Gehört Modellierung dazu? Gehört eine Gruppe dazu?

Ich würde sagen, nichts davon ist zwingend. Code Katas – anders als die anderen o.g. Aufgaben – sind zwar traditionell verbunden mit TDD, doch das halte ich nicht für unverbrüchlich. TDD zu üben, war mal ein Ausgangspunkt für bestimmte Leute zu einem bestimmten Zeitpunkt. Doch sich daran nun zu klammern, finde ich kontraproduktiv und dogmatisch.

Für mich ist daher die Durchführung einer Code Kata völlig frei in der Wahl der Form. Man kann sie allein oder zu mehreren im Coding Dojo bearbeiten. Man kann mit TDD oder F# oder UML oder auch ganz anders dran arbeiten. Nur der Nutzen sollte am Ende entstehen.

Minimaler Rahmen

Damit ein Nutzen sichergestellt ist, halte ich zweierlei dann aber doch für zwingend:

  1. Vor Beginn der Code Kata ist festzulegen, was genau gelernt werden soll. Lernziel und Erwartungshorizont sind zu definieren. Soll TDD geübt werden? Soll das Modellieren geübt werden? Soll Modellieren + TDD geübt werden? Soll die Lösung mit UML entworfen werden? Soll BDD geübt werden? Soll die Formulierung der Lösung in F# geübt werden ganz ohne Tests? Soll besonders auf SOLID oder KISS oder LOC geachtet werden? Egal. Es muss nur festgelegt werden. Ein für die Code Kata gültiger Grundsatz, ein Lernziel ist zu finden.
  2. Auf die Kata folgt eine Reflexion. In der wird besprochen, inwiefern der Nutzen realisiert wurde. Hatten alle Spaß? Wurden die vorher festgelegten Lerninhalte zielstrebig verfolgt? Was wurde davon gelernt? Wo gab es Schwierigkeiten? Wo wurde Unerwartetes gelernt?
Reflexion der Diskussion

Wenn ich diesen minimalen Rahmen für Code Katas mal in Anschlag bringe, sehe ich die Diskussion um die Kata Tennis ein wenig mit anderen Augen. Teile der Diskussion scheinen mir nun um ein unausgesprochenes Missverständnis zu gehen: die Lerninhalte der Diskutanten.

Über den Nutzen von Code Katas sind wir uns alle einig, glaube ich. Aber bei den Lerninhalten gehen unsere Meinungen auseinander.

Da gibt es manche, die vor allem eine Lösung finden wollen. Mehr oder weniger egal wie. Hauptsache, am Ende läuft es. Andere sind auch zufrieden, wenn keine lauffähige Lösung entsteht, es aber Spaß gemacht hat und irgendwas vorher nicht Spezifiziertes gelernt wurde. Wieder andere wollen vor allem TDD üben. Und noch andere wollen ein umfassenderes Vorgehen üben, das womöglich für die trivialen Code Katas überkandidelt erscheinen mag.

Ich denke, in zukünftigen Diskussionen über Code Katas sollten wir uns klarer darüber werden, was unsere Lerninhalte sind. Wir sollten dann Lösungen einfach so kritisieren, bei denen ein anderer als unser Lerninhalt im Vordergrund stand. “Warum hast du nur TDD benutzt?” ist nur eine valide Kritik, wenn z.B. “Softwareentwicklung üben” der Lerninhalt war, nicht jedoch wenn der lautete “TDD üben”. Kritik sollte sich entweder auf das Lernziel direkt beziehen, “Warum übst du A und nicht B?” Oder sich innerhalb des Lernziels bewegen, “Ich würde zwar B üben statt A, aber wenn du schon A verwendest, dann solltest du es anders machen.”

Vor meiner Kritik anderer Tennis-Lösungen hätte ich also z.B. erst fragen sollen, “Was war dein Lernziel, Björn?” Wenn er dann geantwortet hätte, “Ich wollte nur TDD üben.” oder “Ich wollte das State-Pattern üben.”, dann wäre meine weitere Agrumentation anders verlaufen. Dann wäre sie weniger Kritik gewesen als vielmehr schlichte Beschreibung.

So aber hatte ich angenommen, Björns Ziel sei dasselbe wie meines gewesen: “Üben, eine Lösung für ein Programmierproblem zu finden im Rahmen eines systematischen Vorgehens”. Da für mich zum systematischen Vorgehen auch Modellierung gehört, die bei Björn und anderen aber nicht sichtbar war, habe ich Kritik statt schlichter Beschreibung geäußert.

Nach ein wenig mehr Nachdenken weiß ich das nun. Zukünftig werde ich deshalb versuchen, zuerst mehr über die Ziele anderer herauszufinden. Wenn ich die kenne, kann ich entweder bewusst kritisieren, wenn ich anderer Meinung bin, oder eben nur Alternativen beschreiben. Das können alternative Ziele sein oder alternative Wege zum selben Ziel. Oder beides. Soviel als Beitrag von mir für heute zu mehr Harmonie und Frieden auf dem Entwicklererdball… :-)

Samstag, 20. November 2010

Wider die Patternmania

Heute morgen habe ich hier im Blog meinen Ansatz für die Kata Tennis beschrieben. Den kommentierte Björn Rochel mit

“Warum so abstrakt? Warum so viel Zeremonie? Eine Alternative wäre bsp. das State-Pattern. Finde ich persönlich deutlich einfacher und lesbarer.”

Nach meinem Antwortkommentar bin ich daraufhin aber nicht recht zur Ruhe gekommen. Sein Einwand hat an mir genagt; ich fand meine Entgegnung noch nicht fundiert genug. Nun kann ich meine Position aber besser formulieren, glaube ich. Für einen Kommentar ist sie mir allerdings zu wichtig. Also ein weiterer Blogartikel.

Zu abstrakt?

Ist ein Zustandsautomat zu abstrakt? Dass er abstrakt ist im Vergleich zu Code, ist klar. Aber ist er zu abstrakt, d.h. unnötig abstrakt? Gibt es also ein absolutes, für alle Entwickler der Kata Tennis bestes Abstraktionsniveau, auf dem sie denken sollten? Ich behaupte, nein.

Für mich einfachen Sterblichen ist ein Zustandsautomat zur Beschreibung der Tennis Zählregeln nicht zu abstrakt, sondern gerade richtig. Wenn ich die irgendwie formalisieren will, um eine bessere Übersicht zu bekommen, ist ein Zustandsautomat sehr, sehr praktisch. (Ob ich den grafisch entwerfe oder als Tabelle, ist egal.)

Was wäre denn auch die Alternative? Eine Reihe von Wenn-Dann-Regeln? Das kann Björn doch nicht wirklich meinen.

Meine Vermutung ist eher: Björn ist Tennisprofi und atmet die Regeln täglich, deshalb muss er darüber nicht mehr nachdenken. Deshalb findet er einen Zustandsautomaten zu abstrakt.

Dagegen halte ich, dass es nicht darum geht, ob ein Entwickler meint, Anforderungen verstanden zu haben, sondern er muss das dem Auftraggeber – auch einen imaginierten wie bei einem Dojo – demonstrieren. Als Entwickler müssen wir also darlegen können durch eine eigene Beschreibung der Anforderungen, dass wir wissen, worum es geht. Zum Anforderungstext zu nicken, ist zu wenig.

Wie hat Björn das aber dargelegt? Keine Ahnung. Sein Code bei github gibt darüber keine Auskunft. Wie hätte ich nach Darlegung der Anforderungen in Björn Vertrauen haben können, dass er korrekten Code schreibt? Weil er nach TDD vorgeht? Kaum. Das interessiert mich nämlich als Auftraggeber nicht. Vertrauen hätte aufbauen können, dass Björn mir in seinen Worten und/oder mit einer eigenen (abstrakteren) Darstellung widerspiegelt, was er verstanden hat. Meine Zustandsautomatengrafik ist so eine Darstellung, finde ich. Die könnte sogar ein Auftraggeber verstehen; aber wenn nicht, dann machts auch nichts. Dann kann ich mit dem Automaten in der Hand dem Auftraggeber erklären, wie ich danach die Regeln verstehe.

Ergo: Zu abstrakt finde ich den Zustandsautomaten nicht, sondern als Beschreibung meines Verständnisses der Anforderugen unabhängig vom Code absolut nötig.

Zu viel Zeremonie?

Nicht nur soll der Zustandsautomat zu abstrakt sein, nein, er soll auch zu zeremoniell sein. Damit meint Björn wohl, dass er unabhängig von der Beschreibung unnötig ist und nicht zum Kern der Lösung gehört.

Das verstehe ich nicht. Was ist an einem Zustandsautomaten unnötig für die Lösung? Er stellt vielmehr den Kern der Lösung dar. Er formalisiert das Tennis Zählregelwerk. Er fasst an 1 Ort zusammen, wie es geht. Auf dem Papier formuliert er kurz und knapp die Essenz der Zählung. Und in Code umgesetzt braucht er ganze 5 Zeilen

private readonly int[,] _stateTransitions = new[,] {
                           
  {1,2,3,6,5,6,-1,4,-1},
                              {0,1,2,4,7,4,-1,8,-1} };
internal int _currentState;

var transition = position == ScoringPositions.YouWin ? 0 : 1;
_currentState = _stateTransitions[transition, _currentState];

Ich bitte um Erhellung, wie das Regelwerk der Zählung knapper und auch übersichtlicher und auch leichter anzupassen hätte gefasst werden können. Björn braucht in seinem Code, der ohne Modellierung auskommt, immerhin mindestens 57 Zeilen und weitere, die ich auf die Schnelle nicht erkenne. Ist das ein Vorteil von weniger Zeremonie? Eine Zehnerpotenz mehr Codezeilen und weniger Verständlichkeit durch Verteilung der Verantwortlichkeit für die Zählung auf mehrere Klassen? Hm… ich bin im Zweifel.

Zu wenig Pattern?

Und schließlich eines meiner Lieblingsargumente – vor allem aus der Java-Ecke gehört: “Was du da machst, folgt nicht dem Pattern XYZ. Das ist nicht gut.” Oder in der allgemeineren Form: “Was du da machst, ist nicht wirklich objektorientiert.” Da muss ich immer schmunzeln. Als ob mehr Patterns oder mehr Objektorientierung irgendein Qualitätskriterium seien…

Patterns und Objektorientierung sind nur Mittel zu einem Zweck. Die Frage muss also immer sein: Wird ein Zweck mit einem Pattern oder der Objektorientierung am besten erreicht?

Diese Frage stelle ich an Björn:  Bist du wirklich der Meinung, dass du durch Befolgen eines Patterns, das dich 57+ Zeilen kostet – im Gegensatz zu meinen 5 Zeilen – irgendwie den Zweck “Tennis Zählregeln implementieren” besser erreichst?

Das kann ich nicht glauben.

Mein simpler “Beweis”: Wenn sich an der Zählregel etwas ändert, dann habe ich genau 1 Eingriffspunkt, nämlich die Tabelle mit den Zustandsübergängen. Ich muss mich also nur auf 2 Zeilen Code konzentrieren. Du hingegen müsstest schauen, ob eine oder mehrere deiner State-Klassen verändert werden müssten oder gar eine neue hinzukommen muss.

Und nun noch fundamentaler: Patterns und Objektorientierung sind Implementationsdetails. Ob und wie ich sie einsetze muss für die Lösung relativ egal sein. Denn die Lösung sollte nicht allein und schon gar nicht sofort in Code formuliert werden. C# ist auch ein Implementationsdetail.

Stattdessen – wie könnte es anders sein ;-) - sollte die Lösung zuerst modelliert werden. D.h. sie sollte unabhängig von Implementationsdetails formuliert werden. Keine Klassen, kein State-Pattern, keine Arrays… Genau das habe ich getan. Ich habe mir sprachunabhängig Gedanken gemacht, wie die Lösung aussehen könnte. Ein Zustandsautomat ist nicht an C# gebunden und das Datenflussdiagramme auch nicht.

Und erst als ich auf Modellebene zuversichtlich war, die Lösung formuliert zu haben, habe ich mich an die Übersetzung gemacht. Da sind dann 5 Zeilen für den Zustandsautomaten rausgekommen und ca. 60 Zeilen für die gesamte Logik – also knapp nur 50% der LOC bei Björn. Die habe ich sehr wahrscheinlich dann auch schneller getippt und schneller getestet.

Schneller und einfacher geändert sind sie auch, da es erstens viel weniger Funktionseinheiten (Klassen, Methoden) gibt und zweitens deren Verantwortlichkeiten nicht weniger klar sind als bei Björn.

imageNebenbei: Wer meint, meine Lösung enthalte zu wenig Pattern, der schlage in der Literatur nach. Dort wird man finden, dass ein Zustandsautomat das um Jahrzehnte ältere Pattern im Vergleich zum State-Pattern und jedem anderen Designpattern ist. Ich habe also sehr wohl auf Patterns gesetzt; nur eben nicht die seit 15 Jahren hippen Entwurfsmuster. Entwurfsmuster entlasten also nicht von der Mühe, sich mit Modellierung auseinanderzusetzen. Eine Leseempfehlung dazu: “Modellierung” von Kastens und Büning.

Fazit

Kritik der Art “zu viel von …” oder “zu wenig von …” finde ich wenig hilfreich. Sie ist letztlich nur eine Gefühlsäußerung und nicht argumentbasiert. Denn Argumente beziehen sich auf eine Differenz zwischen Zweck und Mittel. Die habe ich aus Björns Kritik nicht wirklich herauslesen können.

Aber ich bin offen: Wer mag, kann mir darlegen, warum meine gewählten Mittel Modellierung im Allgemeinen, Zustandsautomat und EBC-Diagramm im Besonderen und ihre Übersetzung in Code die Zwecke Testbarkeit und Evolvierbarkeit weniger gut erfüllen als eine andere Herangehensweise.

Spiel, Satz, Sieg fürs Nachdenken

imageGerade hat die .NET Online User Group die Kata Tennis beim online Coding Dojo bearbeitet. Leider konnte ich nicht teilnehmen. Da in Twitter dazu aber noch anschließend diskutiert wurde, habe ich mir gedacht: Warum nicht die Aufgabe nachträglich angehen?

Meine Lösung liegt hier in meinem Mercurial Google Repository. Anders als im Coding Dojo bin ich jedoch nicht streng nach TDD vorgegangen. Deshalb ist die Struktur der Implementation anders als bei den Dojo-Teilnehmern und auch meine Tests sehen anders aus.

Zum Hintergrund meiner Lösung an dieser Stelle daher ein paar kurze Worte.

1. Der API

Bevor ich mit der Implementation losgelegt habe, habe ich über die Lösung nachgedacht. Wie könnte die aussehen? Nicht nur an der “Oberfläche”, Stichwort API, sondern auch darunter.

Der API – der immer explizit vor Codierungsstart festgelegt werden sollte, wie ich meine – sieht so aus:

var g = new Game();
g.AddScore(Players.Player1);
g.AddScore(Players.Player2);
g.AddScore(Players.Player1);

Console.WriteLine(g.Winner);

Bei mir geht es also nur darum festzustellen, ob durch einen Ballsieg ein Gewinn eingetreten ist und wer der Gewinner ist. Die Aufgabenstellung der Kata Tennis lässt diese Interpretation zu. Dort ist nämlich von gar keinem API die Rede; es sollen lediglich “irgendwie” die Regeln implementiert werden.

2. Das Modell

Ausgehen vom API habe ich mir Gedanken gemacht, wie denn so ein Tennisspiel intern überhaupt repräsentiert werden könnte. Sofort fiel mir da ein Zustandsautomat ein. Die Zustände sind die Spielstände eines Spielers, die Übergänge ergeben sich aus Gewinn eines Balls bzw. ob ein Ball verloren wurde (rot). Hier meine Skizze, die ich auf meinem iPad gemacht habe:

image

So ein Zustandsautomat ist natürlich eine sehr abstrakte Funktionseinheit. Wo und wie läuft der denn im Zusammenhang, so dass er vom API genutzt wird? Das habe ich mit einem kleinen EBC-Diagramm für den AddScore() API-Aufruf modelliert:

image

Der Spieler, der einen Ball gewonnen hat, fließt hinein und wird übersetzt in eine Position für jeden der beiden Spieler. Beide Spieler sind repräsentiert durch ein Objekt (Player), das ihren Zustand hält. Dort läuft der Zustandsautomat. Der liefert nach einer Transition den neuen Spieler-Spielstand an ein abschließendes Bauteil, das ermittelt, wer gewonnen hat.

Beide Skizzen zusammen haben mich, hm, 10-15 Minuten gekostet. Der Zustandsautomat hatte daran den Löwenanteil, würde ich sagen. Er diente ja aber nicht nur der Lösungsmodellierung, sondern auch noch dem Anforderungsverständnis.

Am Ende der Modellierung kannte ich dann alle relevanten Funktionseinheiten und konnte loslegen – und zwar wo ich wollte. Denn die Funktionseinheiten sind ja durch EBC wunderbar entkoppelt.

3. Implementation

Die Implementation habe ich mit den beiden kleinsten Funktionseinheiten begonnen: Ballgewinner in Position übersetzen und Gewinner aus den Spieler-Spielständen ermitteln. Die Herausforderung Zustandsautomat hab ich also an den Schluss gelegt.

Die ersten beiden Funktionseinheiten waren so klein, dass ein Test-first Ansatz nicht nötig war. Ich habe sie deshalb einfach runtergeschrieben und danach Tests geschrieben. Das war ohne Verlust an Korrektheit befriedigender.

Test-first/TDD sollte ja kein Dogma sein. Wenn die Zwecke von TDD anders/leichter erreicht werden können, dann sollte man den leichteren Weg gehen. Und was sind die Hauptzwecke von TDD? 1. Code in überschaubare, leicht testbare Einheiten strukturieren; 2. Eine gute Testabdeckung sicherstellen. Beides habe ich durch die Modellierung erreicht. Denn die hat zu kleinen Funktionseinheiten geführt – ohne später refaktorisieren zu müssen –, die testbar sind und die ich mit wenigen Tests gut abdecken kann – auch im Nachhinein.

Bei der Implementation des Players habe ich dann jedoch nach Test-first gearbeitet. Viel herausgekommen ist dadurch jedoch nicht ;-) Denn auch der Player ist am Ende so einfach mit dem Zustandsautomaten, dass sich eine weitere Zerlegung nicht lohnt:

internal class Player
{
    private readonly Dictionary<ScoringPositions,
                                Dictionary<Scores, Scores>>
                                _stateTransitions =
        new Dictionary<ScoringPositions, Dictionary<Scores, Scores>>
            {
                {ScoringPositions.YouWin,
                    new Dictionary<Scores, Scores>
                    {
                        {Scores.Love, Scores.Fifteen},
                        {Scores.Fifteen, Scores.Thirty},
                        {Scores.Thirty, Scores.Forty},
                        {Scores.Forty, Scores.Win},
                        {Scores.Deuce, Scores.Advantage},
                        {Scores.Advantage, Scores.Win},
                        {Scores.Win, Scores._Invalid},
                        {Scores._Advantage, Scores.Deuce},
                        {Scores._Win, Scores._Invalid},
                    }},
                {ScoringPositions.YouLoose,
                    new Dictionary<Scores, Scores>
                    {
                        {Scores.Love, Scores.Love},
                        {Scores.Fifteen, Scores.Fifteen},
                        {Scores.Thirty, Scores.Thirty},
                        {Scores.Forty, Scores.Forty},
                        {Scores.Deuce, Scores._Advantage},
                        {Scores.Advantage, Scores.Deuce},
                        {Scores.Win, Scores._Invalid},
                        {Scores._Advantage, Scores._Win},
                        {Scores._Win, Scores._Invalid},
                    }},    };
    internal Scores _currentState;
 
    public void Adjust_score(ScoringPositions position)
    {
        _currentState = _stateTransitions[position][_currentState];
        this.Out_CurrentScore(_currentState);
    }

 
    public void In_Deuce()
    {
        _currentState = Scores.Deuce;
    }
 
    public Action<Scores> Out_CurrentScore;
}

Etwas zusammenreißen musste ich mich vielmehr bei den Tests. Ich war schon dabei, möglichst viele Zustandsübergänge zu testen, als ich merkte, dass ich damit “Feedback” verzögerte. Also habe ich nur ein paar essenzielle Transitionen geprüft und mich dann an die Integration aller Bauteile gemacht.

Damit konnte ich dann schon “beweisen”, dass das Gesamtmodell grundsätzlich funktioniert. Zwei Szenarien zeigen Funktionsweise und Umgang mit dem API. Hier zum Beispiel ein Spiel mit Gewinn nach Tie Break:

[Test]
public void Game_with_a_tie_break()
{
    var sut = new Game();
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player2);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.None, sut.Winner);
    sut.AddScore(Players.Player1);
    Assert.AreEqual(Players.Player1, sut.Winner);
}

Fazit

Ich bin zufrieden mit meinem Vorgehen. Erst modellieren – ja, auch in so kleinen Szenarien – und dann moderat testgetrieben implementieren hat mich schnell, flexibel und sicher gemacht. Alle Funktionseinheiten waren überschaubar und gut testbar. Ich konnte mich auf die Implementation konzentrieren, ohne immer wieder über Refaktorisierungen nachdenken zu müssen.

Es scheint mir ein Nachteil von TDD zu sein, die Modi Implementation und Refaktorisierung so zu verquicken. In der Schrittfolge red-green-refactor sind sie zwar getrennt, doch da diese Schleife immer wieder und schnell in kleinen Schritten durchlaufen werden soll, verschmelzen Implementation (red-green) und Refaktorisierung wie die Einzelbilder eines Films. Als Entwickler muss ich schwebende Aufmerksamkeit für beide haben.

Bei meinem Vorgehen hingegen sind beide Modi klar getrennt. Ergebnis der Modellierung sind Funkionseinheiten, die zunächst mal nicht mehr refaktorisiert werden müssen. Solange ich die implementiere, kann ich mich auf die Implementation konzentrieren. Das entlastet mich mental. Wenn ich damit dann fertig bin – z.B. nach einer Stunde wie in diesem Beispiel –, dann kann ich mich zurücklehnen und den Modus wechseln. Das empfinde ich als Entspannung und wertvolle Phase der Reflexion. Dann habe ich die “Ruhe des Erfolgs” in mir, weil ich ja schon eine Menge geschafft habe.

Ergo: Ich finde weiterhin, dass Implementierung ohne Modellierung harte Arbeit ist und nicht smarte. Man kommt damit auch zum Ziel – aber warum so anstrengen, wenn es auch einfacher geht? TDD ist ne gute Sache – in Maßen. Es ist keine Silberkugel, sondern nur eine Methode unter vielen, die im Zusammspiel zur Produktion von Code genutzt werden sollten. Und somit empfände ich es als künstliche oder gar unrealistische Reduktion, wenn ein Coding Dojo sich nur auf die Anwendung von TDD zur Lösungsfindung beschränken würde.

PS: Danke an Krzysztof Eraskov für seine Hinweise auf Fehler in der Implementierung und Inkonsistenzen im Modell.

Samstag, 30. Oktober 2010

Lesenswerte Widerlegung

Eine Allaussage geht um: “Es kann nur sein, wie es ist.”

image
Unternehmen geht es schlecht, weil der Markt halt so ist, wie er ist; böse Globalisierung. Oder die Mitarbeiter sind halt, wie sie sind; böse Ausbildungsdefizite und Konsumentenhaltung. Daran kann man kaum was ändern. Höchstens sollte man die bisherigen Anstrengungen verstärken: “Nachsitzen”, wenn das Release noch raus muss; mehr Incentives, damit endlich die Softwarequalität steigt; strengere Budgetschrauben, damit alles unter Kontrolle bleibt; weniger Investition in Mitarbeiterfortbildung, weil dadurch wertvolle Zeit zur Kundenwunscherfüllung verloren geht. Usw. usf. ad nauseam.

[Pause]

Mir ist ein wenig übel geworden, da ich diese Litanei geschrieben habe. Die komprimiert und verkürzt zwar, ist aber ein Abbild der Stimmung, die mir in Beratungen, Trainings oder auf Community-Veranstaltungen entgegenschlägt. Früher war vor allem Klagen über Tools oder Microsoft. Veteranengeschichten folgten dann schnell. Wie es damals mit dem C64 war… und die Lochkarten…

Heute scheint das weniger Thema. Vielleicht liegt es auch an meinem neuen Fokus, der nicht mehr auf Technologien, sondern auf Softwarequalität liegt. imageHeute höre ich Klagen über Arbeitsbedingungen. Immer wieder werden da Mauern beschrieben, gegen die Entwickler laufen, weil sie nicht so dürfen, wie sie wollen. ReSharper darf nicht angeschafft werden: zu teuer. Automatisiertes Testen darf nicht in Anschlag gebracht werden: dafür hat der Kunde nicht bezahlt. Eine Fortbildung darf nicht besucht werden: vier Monate Urlaubssperre, weil die Projekte weit jenseits des Plans liegen.

Dahinter – wie schon gesagt – der Glaubenssatz: Irgendwie kann es nicht anders sein. So ist die Welt halt. Vor allem ist sie kein Wunschkonzert. Dem Schicksal und den Marktgesetzen den ehernen sollte man sich daher ergeben.

Widerlegung

Die Allaussage, der unumstößliche Glaubenssatz wird ad absurdum geführt durch den Nachweis der Existenz auch nur eines Gegenbeispiels.

image
Wenn es auch nur einmal anders gehen kann, als die Überzeugung diktiert… dann ist die Welt anders als gedacht. Dann ist Hoffnung auch in schlimmsten Zeiten.

Und solche Hoffnung gibt es. Der campus Verlag hat eine mehr als 200 Seite starke Widerlegung des allgegenwärtigen Glaubenssatzes herausgebracht, die Unternehmensverhältnisse könnten kaum anders sein. Ihr Titel: “Nur Tote bleiben liegen” vom Autorenduo Förster und Kreuz.

image

Gelesen habe ich das Buch, weil Rezensionsexemplare davon im Blog der Autoren an andere Blogger vergeben wurden. Da konnte ich nicht nein sagen ;-)

Gefallen hat mir das Buch aber nicht wegen seiner Kostenlosigkeit für mich. Nein, keine Sorge. Es ist seine “Schwingung”, die gute Laune, der Optimismus, die Hoffnungsfülle, der Gegenentwurf, das Ja zu Neu und Anders, die es für mich zu einer Empfehlung machen.

Ich will deshalb auch gar nicht lange auf den Inhalt eingehen. Man muss es einfach aufschlagen und loslesen. Irgendwo. Jede Seite eine Widerlegung. (Naja, vielleicht übertreibe ich ein wenig ;-) Jede Seite ein Beispiel dafür, dass andere Arbeitsverhältnisse, andere Organisation von Unternehmen möglich ist. Und zwar ganz handfest. Dies ist kein Thesenbuch, sondern eine Beispielsammlung. Förster und Kreuz berichten aus der Realität. Die mag mal fern sein, auf anderen Kontinenten, aber sie ist immer relevant. Auch das ein Effekt der schlimmen Globalisierung. Oder ist die gar nicht so schlimm?

Wer glaub, man können nicht anders umgehen mit der Arbeitszeit oder der Führung oder der Aus-/Fortbildung oder der Kontrolle oder dem Kunden, der schlage dieses Buch auf uns lasse sich eines Besseren belehren. Es geht anders, als die allermeisten Unternehmen es heute tun und meinen nicht anders tun zu können. Irgendwo geschieht schon die kleine oder große Revolution, die Kunden zufrieden macht, Unternehmen prosperieren lässt und Mitarbeiter ganz einfach motiviert.

Internet, Globalisierung, neue Medien, anspruchsvolle Kunden, Digital Natives als Mitarbeiter… das alles und mehr muss keine Bedrohung althergebrachter Ruhe und Ordnung sein. Es liegt an uns, das Glas als halb voll und nicht als halb leer zu betrachten. Die Chancen auf etwas Besseres lauern überall. Das beweist “Nur Tote bleiben liegen” mit einem Feuerwerk an weltweit gesammelten Impressionen.

Im Maul vom Gaul

So empfehlenswert ich das Buch finde, man darf keine Tiefründigkeiten erwarten und auch keine Tipps für die Praxis. Wer von der Lektüre motiviert das Berichtete selbst ausprobieren möchte, ist auf sich gestellt.

Das soll keine Klage sein, sondern nur ein Hinweis, um falsche Erwartungen zu zerstreuen. Förster und Kreuz haben einen hochfrequenten Impulsgeber und Motivator geliefert, kein Handbuch.

Nein, eine tiefergehende Kritik am Inhalt habe ich nicht. Man nehme einfach das Buch für das, was es will und kann. Die knapp 25 EUR für das Hardcover sind nicht zuviel für die Portion Zuversicht, die sich daraus löffeln lässt.

Aber -- denn ohne Aber geht es nicht ;-) -- eines hat mir nicht gefallen. Es ist mir erst nach einiger Zeit aufgefallen, als ich nachfragte, ob es auch eine elektronische Version des Buches gäbe, die ich mit meinem iPad auf den prio.walk hätte nehmen können. Das, was fehlt, ist eben oft schwerer zu erkennen, als das, was da ist.

Nein, ich meine nicht eine eBook-Version oder ein Hörbuch. Beides gibt es.

Ich meine die Abwesenheit von Innovation bei Verlag und Autoren.

Es hat einen Moment gedauert, weil auch ich in Jahrzehnten konditioniert wurde, bei Büchern ersteinmal an Papier zu denken. Aber was für ein Quatsch! Bücher müssen weder aus Papier bestehen noch als zusammenhängendes PDF ausgeliefert werden. Sie müssen auch nicht daheim am Schreibtisch verfasst, zu einem Verlag getragen und dann von einem Vertriebler an den Buchhändler gebracht werden. All das inklusive eBook und Hörbuch ist so Buch 1.0, dass es kaum auszudenken ist. Das ist so un-innovativ und so un-mutig, dass es mich bei diesem Buch erschreckt und enttäuscht hat. Schade.

Warum haben Förster und Kreuz, die als Erfolgsautorenduo genügend Reichweite auch ohne Verlag haben, ihr neues Buch “im stillen Kämmerlein” geschrieben? Sie haben natürlich ein Blog, dessen Inhalte früher oder später wahrscheinlich in der einen oder anderen Weise auch wieder in einem Buch auftauchen werden. Dennoch findet das Schreiben ab von der Öffentlichkeit und somit ab vom Feedback statt.

Wie Buch 2.0 ist dagegen dieser Titel:

image

Der Autor hat sich schon beim Schreiben dem detaillierten Feedback der Community gestellt, indem er den kompletten Text online veröffentlicht hat – mit der Möglichkeit, absatzweise Kommentare zu geben. Hier ein Auszug:

image

Das ist Web 2.0 gelebt. Vom Autor wie vom Verlag.

Oder warum haben Förster und Kreuz den potenziellen Rezensenten überhaupt als Default ein Papierbuch angeboten? Warum nicht einen Link auf ein PDF oder einen Kindle-Gutschein? Der ganze Aufwand mit Verpackung und Porto ist überflüssig. Allemal für Rezensenten. Mich interessiert doch nur am äußersten Rand, ob das Papierbuch geschmeidig in der Hand liegt. Auch hier also eine merkwürdige Zurückhaltung in Bezug auf das, was möglich und zukunftsweisend ist.

Wo ist auch das virale Marketing? Warum nicht das Buch launchen im Blog mit einem befristeten kostenlosen Download für jedermann? Von mir aus kann es dabei ja durch Einbinden des Namens des Herunterladenden personalisiert werden.

Oder wenn schon nicht ganz umsonst für eine gewisse Zeit, dann zumindest Zahlen mit einem Tweet. Das ist ja trivial aufzusetzen: http://www.paywithatweet.com/

Nett, dass die Blogeinträge der Rezensenten am Ende von den Autoren zusammengefasst werden. Aber auch das ist letztlich Buch 1.0, weil es auf Zentralisierung setzt.

imageOder wie wäre es, das Buch sofort in “Module” zu zerlegen. Und nicht nur dieses Buch, sondern auch die bisherigen von Förster und Kreuz? Dann die Module mit Tags versehen oder in eine Concept Map o.ä. einbinden und ab ins Internet damit.

Dann könnte der Inspiration suchende Leser sich durch das Förster und Kreuz Universum bewegen, online lesen z.B. mit Scribd, sich am Ende sein persönliches Buch aus den interessantesten Modulen zusammenstellen, alles in ein PDF “zusammenschweißen” und das womöglich noch ad hoc in der Auflage 1 für sich z.B. durch www.epubli.de drucken lassen. Das (!) wäre Buch 2.0. Das würde dem amerikanischen Sprichwort “Put your money where your mouth is!” entsprechen.

Aber ich will nicht abschweifen. Dies ist ja nur eine Buchrezension und keine Verlagsberatung ;-) Eine Beratung wäre für viele Verlage wohl auch eher das falsche Mittel, um aus deren Klagehaltung herauszukommen. Da scheint mir eher eine Therapie angezeit. Aber das ist ein anderes Thema…

Also komme ich mal zum Schluss:

“Nur Tote bleiben liegen” finde ich lesenswert, weil kurzweilig und inspirierend. Lockere Schreibe, lockerer Inhalt, macht gute Laune. Einfach mal auf den Wunschzettel für Weihnachten setzen – oder gleich dem Chef schenken ;-)

Denn wer sagt, es ginge nicht anders in den Unternehmen, Erfolg und Zufriedenheit seien nur mit Verstärkung tradierter Maßnahmen – Management 1.0 – zu erreichen oder gar nicht, der findet in diesem Buch einen bunten Strauß an Widerlegungen.

Und ganz vielleicht sind Förster und Kreuz ja beim nächsten Buch auch selbst mutiger. Raus aus der Komfortzone gilt nicht nur für die, über die sie berichten, würd´ ich mal sagen.