Montag, 31. Dezember 2012

Prinzip der gegenseitigen Nichtbeachtung

Mehrschichtigkeit ist immer noch tief in den Köpfen verankert. Und wenn nicht in den Köpfen, dann im Code. Übel daran ist nicht nur, dass Mehrschichtigkeit viele Fragen weder beantwortet noch stellt, sondern dass damit Abhängigkeiten geschaffen werden. Code der einen Schicht hängt irgendwie von Code der anderen ab. Die eine Schicht braucht die andere.

Aber welche Schicht sollte welche brauchen? Darüber hat Jens Schauder in einem Blogartikel nachgedacht. Den habe ich gelesen – und dann habe ich gestutzt. Jens erkennt zurecht Abhängigkeiten als Problem – doch dann sieht er eine Lösung nur darin, dass er sie umdreht. Aus dem üblichen

image

wird bei ihm nur

image

Die Domäne soll Interfaces definieren, die UI/Persistence dann nutzen bzw. implementieren [1].

Das empfinde ich als keinen großen Schritt nach vorne. Klar, irgendwie wird jetzt die Domäne etwas mehr betont. Das ist nicht schlecht. Aber das grundsätzliche Problem der Abhängigkeiten wird nicht angegangen.

Was ist dieses grundsätzliche Problem der Abhängigkeiten? Naja, dass das Abhängige das Unabhängige braucht, um etwas leisten zu können. Wer vom Alkohol abhängig ist, der ist nicht arbeitsfähig ohne. Und wo ein UI abhängig ist von der Domain, da kann das UI nichts tun, solange es keine Domain-Schicht in der Hand hat.

Auch das hochgelobte Prinzip IoC macht da keinen großen Unterschied. Aus statischen Abhängigkeiten werden damit nur dynamische. Zur Laufzeit – also auch bei Tests – muss die Abhängigkeit erfüllt werden. Attrappen sind insofern weniger Lösung als Problem, dito DI-Container. Beide kaschieren die fundamental starre Verbindung, die Abhängigkeit selbst.

Ich sehe dafür auch keine Entsprechung in der sonstigen Technik. Wenn ich UI+Domain+Persistence mal zusammen als Application bezeichne, dann könnte die Analogie doch ein Motor sein, oder?

image

Eine Application besteht aus Teilen, ein Motor auch. Nur im Zusammenspiel aller Teile ergibt sich die gewünschte Funktion des Ganzen.

Jetzt aber der Unterschied, der entscheidende:

In der Application braucht und kennt UI die Domain, und bei Jens braucht und kennt Persistence ebenfalls Domain. UI wie Persistence sind unvollständig ohne Domain. Oder allgemeiner: das Abhängige ist unvollständig ohne das Unabhängige.

Im Motor hingegen… da weiß eine Zündkerze nichts vom Zylinder. Ein Zylinder weiß nichts von einer Zündkerze. Die Kurbelwelle weiß nichts von Kolben und umgekehrt.

In der materiellen Technik gibt es keine Abhängigkeiten wie in der Software.

In der materiellen Technik gibt es nur Teile, die für sich genommen eine Form und eine Funktionsweise haben. Sie folgen dem, was ich mal

Das Prinzip der gegenseitigen Nichtbeachtung
(principle of mutual oblivion (PoMO))

nennen will.

Eine Zündkerze beachtet kein anderes Teil. Ein Kolben, eine Kurbelwelle, ein Keilriemen auch nicht. Alle sind für sich geformt und ausgeprägt.

Dass man sie zusammenfassen kann zu etwas Größerem: klar. Dieses Größere definiert einen Kontext und fordert, dass die Teile zusammenpassen. Es ist die Existenzberechtigung für die Teile. Ohne das Größere müssten wir gar nicht drüber reden.

Aber das große Ganze kann eben in unterschiedlicher Weise seine Teile formen. Es kann das wie beim Motor tun. Das bedeutet, die Teile beachten einander nicht – sondern dienen dem Ganzen, das sie integriert.

Oder das Ganze kann es tun wie in der Software üblich. Das bedeutet, es macht die Teile abhängig von einander.

Konkret steckt diese Abhängigkeit in zwei unheiligen Verbindungen:

  • Es wird Input mit Output verbunden: Request/Response ist der vorherrschende Kommunikationsstil zwischen den Softwareteilen.
  • Es werden mehrere Funktionalitäten im selben Interfaces zusammengefasst.

Die erste unheilige Verbindung macht den Übergang von synchroner zu asynchroner und damit zu verteilter Kommunikation schwierig. Außerdem führt sie zu einem unbeschränktem Wachstum von Methoden.

Die zweite unheilige Verbindung macht die Umkonfiguration von Funktionalität schwierig – trotz allem ISP- und IoC-Aufwand, den man treiben mag.

Beim PoMO hingegen ist das alles kein Thema mehr. Denn beim PoMO gibt es keine Abhängigkeiten mehr zwischen “Kooperationspartnern”. Funktionseinheiten kennen einander nicht mehr. “Dienstleister” UI kennt keinen “Dienstleiste” Domain oder umgekehrt, dito “Dienstleister” Persistence.

Wenn die Anwendungsarchitektur dem PoMO folgt, dann sieht sie zunächst so aus:

image

Welch Wonne! Keine Abhängigkeiten, kein Testattrappendrama!

Aber natürlich sind die “Schichten” jetzt gänzlich unverbunden. Da entsteht noch keine Gesamtfunktionalität im Sinne der Application.

Dafür tut nun die Application etwas. Und das tut sie ganz offiziell. In der Abhängigkeitsarchitektur war das nicht so offensichtlich. Aber bei PoMO geht es nun nicht mehr anders. Insofern führt PoMO zu einer fundamentalen Separation of Concerns:

UI, Domain und Persistence gehören zum Concern (Aspekt) “Operation”. Und die Application vertritt den Aspekt “Integration”. Die Application verbindet die Teile so, dass sie zusammen etwas sinniges tun. Diese Verbindung könnte so aussehen:

image

Grün integriert, schwarz operiert. Teile wissen nichts davon, ob und wie sie verbunden sind. Und die integrierende Funktionalität weiß nichts davon, wie die Teile funktionieren. Sie ist ausschließlich von deren Form abhängig.

Und genau das macht den Unterschied aus zwischen einer PoMO-Architektur und dem Üblichen: Bei den üblichen Abhängigkeiten ist der Abhängige nicht nur von der Form des Unabhängigen abhängig, also dass zum Beispiel B eine Funktion mit einer Signatur wie Func<string,int> implementiert. Nein, A ist auch vom Namen der Funktion abhängig und damit von ihrer Semantik, dem, was sie leistet.

Das klingt ganz natürlich. Aber es ist eine Last. Das ist eine doppelte Abhängigkeit: eine von Form und Inhalt. Und dann obendrein noch die Abhängigkeit davon, dass diese Funktion auf einem bestimmten Interface implementiert ist und auch noch, dass sie ein Ergebnis zurückliefert. Puh… das sind ganz schön viele Abhängigkeiten, oder?

Bei PoMO jedoch, da kennt ein A kein B. Und es gibt keine Rückgabewerte; Daten fließen unidirektional. Und ob Methoden von der einen oder einer anderen Funktionseinheit angeboten werden, ist der integrierenden Funktionalität egal. Sie bringt “Angebote” einer Funktionseinheit mit “Nachfragen” anderer zusammen. Das ist alles.

Integration interessiert sich somit nur für Semantik. Das muss sie ja auch, denn sie definiert ein höheres Abstraktionsniveau.

PoMO löst damit mehrere Probleme von Abhängigkeiten. Manche dadurch, dass Abhängigkeiten abgeschafft werden. Manche dadurch, dass Abhängigkeiten fokussiert werden im Sinne von SoC und SRP.

Das scheint mir the way to go, um mal raus aus dem Mehrschichtigkeitssumpf zu kommen.

Fußnoten

[1] Am Anfang seines Artikels stellt Jens die Frage, was denn Teams überhaupt mit einem Pfeil “–>” in einem Diagramm meinen. Ob da immer Klarheit herrsche? Das kenne ich auch: Es wird bildlich entworfen, Kästchen, Kreise, Pfeile, Linien werden gemalt… Aber was bedeuten die? Zwei Entwickler, drei Meinungen ;-)

Für mich gibt es zwei zentrale Symbole für Beziehungen zwischen Funktionseinheiten:

  • Pfeil, A –> B
  • Lolli, A –* B

Der Lolli bezeichnet eine Abhängigkeitsbeziehung: A ist abhängig von B, d.h. A braucht B in irgendeiner Weise. A hat zur Laufzeit ein B in der Hand.

In den meisten UML-Diagrammen und auch bei Jens steht für diese Art der Beziehung allerdings der Pfeil. Der ist mir dafür aber verschwendet, weil bei ihm gar nicht recht klar wird, was die Pfeilspitze aussagt. Ein Pfeil ist anders als eine Linie asymmetrisch. Das ist schon passend zur Abhängigkeit. Abhängiges und Unabhängiges haben unterschiedliche Rollen. Doch die Pfeilspitze trägt für mich dabei zuviel Gewicht. Sie ist verschwendet bei einer simplen Asymmetrie.

Bei einem Datenfluss hingegen kommt die Pfeilspitze viel besser zum Einsatz. Da zeigt sie deutlich die Flussrichtung. Das entspricht ihrer üblichen Bedeutung von “Bewegungsrichtung”. In Diagramme stehen Pfeile für mich also dort, wo Daten fließen, z.B. von Funktionseinheit A zu Funktionseinheit B.

Solcher Datenfluss ist auch asymmetrisch; Datenproduzent und Datenkonsument haben unterschiedliche Rollen. Und Datenfluss ist unidirektional.

Freitag, 28. Dezember 2012

Der Feind des Besseren ist der heutige Erfolg

Eigentlich soll ja alles immer besser werden, oder? Die Prozessoren der nächsten Jahre sollen auf die eine oder andere Weise besser sein als die heutigen – so wie die heutigen besser sind als die der 1990er und die wiederum besser als die der 1980er usw. Auch unsere Autos sollen immer besser werden – so wie sie seit mehr 125 Jahren immer besser geworden sind. Das selbe gilt für Fernseher, Mixer und Solarzellen.

Aber nicht nur solch handfeste Dinge sollen besser werden. Auch bei Dienstleistungen mögen wir es, wenn die sich zum Besseren verändern. Wer möchte heute noch einen Gesundheitscheck wie vor 20 oder gar 100 Jahren? Wo wären wir, wenn die Flugabfertigung immer noch wie vor 60 Jahren abliefe? Selbst beim Friseur schätzen wir den Fortschritt.

Dito bei der Arbeitsweise unserer Unternehmen, oder? Und genauso bei der Software. Auch die soll besser werden, vom Betriebssystem bis zum Smartphone Game. Selbstverständlich nehmen wir die Software, die wir selbst entwickeln, da nicht aus.

Ja, das ist eine Welt, wie sie sein soll: alles wird immer besser und besser.

Was ist “besser”?

“Besser” ist dabei ein relativer Begriff in zweierlei Hinsicht. Zum einen steckt in “besser” ein Vergleich zum Bisherigen. Zum anderen gilt “besser” genauso wie das vorgelagerte “gut” nur in einem Kontext, d.h. in Bezug auf eine Umwelt – und darin in Bezug auf einen Wertmaßstab. Es geht mithin um einen Unterschied, der einen Unterschied macht.

Ein Fernseher mit Fernbedienung ist nicht einfach besser als einer ohne Fernbedienung. Aber die Fernbedienung ist schon mal ein Unterschied zwischen beiden.

Doch wenn dieser Unterschied #1 dazu führt, dass Käufer eine andere Entscheidung treffen (Unterschied #2), d.h. die Fernbedienung sie motiviert, den Fernseher mit Fernbedienung zu kaufen statt den ohne Fernbedienung… dann beurteilen wir den Fernseher mit Fernbedienung als besser im Vergleich zu dem ohne.

Der Wertmaßstab ist in diesem Fall zum Beispiel der Umsatz, der mit Fernsehern gemacht wird. Der Fernseher mit Fernbedienung ist besser in Bezug auf den Umsatz – aber vielleicht schlechter in Bezug auf den Kalorienverbrauch der Fernsehzuschauer (Wertmaßstab Gesundheit) oder den Anfall von Elektronikschrott (Wertmaßstab Nachhaltigkeit).

Wenn wir nun in die Welt schauen, dann – so glaube ich – stimmen wir überein, dass vieles über die Zeit tatsächlich besser und besser geworden ist. Der Spruch “Früher war alles besser.” stimmt also nicht pauschal, auch wenn uns der heutige Stand mancher Entwicklung frustriert [1].

Bedingungen für die Möglichkeit zur Verbesserung

Verbesserungen in allen Bereichen unseres Lebens scheinen so natürlich – aber wie kommt es eigentlich dazu? Wie funktioniert diese Evolution unserer Hilfsmittel und Dienstleistungen?

Mir scheint es zwei zentrale Bedingungen dafür zu geben:

  1. Es müssen überhaupt Ideen für Veränderungen produziert werden. Was könnte der Unterschied #1 zum Ist-Zustand sein? Dafür braucht es ein Milieu, das motiviert, solche Ideen zu produzieren. Sensibilität für Unzufriedenheit, Kreativität, Mut sind nötig.
  2. Die Ideen für Veränderungen müssen implementiert werden können, um zu sehen, ob es zu einem positiven Unterschied #2 in Bezug auf einen für Produzenten wie Konsumenten relevanten Wertmaßstab kommt.

Die Natur als Beispiel

Die Natur macht uns das sehr schön seit Milliarden von Jahren vor. Sie erfüllt beide Bedingungen: die erste durch genetische wie epigenetische Veränderungen, die zweite durch Reproduktion.

Krankheit und Tod zeigen auf, wo Lebewesen noch nicht zufriedenstellend an die Umwelt angepasst sind. Mutationen sind äußerst kreative Veränderungen – oft jenseits unseres Vorstellungsvermögens; wir hätten es nicht besser machen können. Und Mut hat die Natur genügend, da sie durch keine Glaubenssätze beschränkt ist.

So werden Lebewesen von Generation zu Generation besser und besser bzw. erhalten sich die Güte ihrer Überlebensfähigkeit.

Der Markt als Beispiel

Auch der Markt macht uns vor, wie die Bedingungen erfüllt werden können. Konkurrierende Anbieter generieren kontinuierlich Ideen für Veränderungen, die sie über neue Modelle an den Markt bringen.

Vor allem im Bereich des Materiellen funktioniert der Markt damit wie die Natur. Besser wird es durch Generationen von Modellen. Ein Fernseher hat eine Lebensdauer, ebenso ein Flugzeug und ein Röntgengerät. Spätestens nach dieser Lebensdauer kann es durch etwas Besseres ersetzt werden.

Tod als Herz der Evolution

Natur und Markt gleichen sich. In ihnen schlägt das selbe Herz der Evolution: der Tod. Das in der Vergangenheit Gute macht durch seinen Tod ganz automatisch Platz für das inzwischen besser gewordene.

Das Ganze - die Summe aller Lebewesen, d.h. das Ökosystem, bzw. die Summe aller Modelle, d.h. der Markt – erhält seine eigene übergeordnete Lebensfähigkeit also dadurch, dass seine Teile in einem ständigen Prozess von Werden und Sterben stehen.

Allemal gilt das für seine kleinsten Teile, aber durchaus für Subsysteme größerer Granularität. Die Lebensdauer bzw. die Dauer des Verbleibs im System wächst dabei tendenziell mit der Größe eines Subsystems.

Perverse Unsterblichkeit

Lebewesen sterben. Das gehört zu ihrer Definition. Misslich ist es für uns Menschen, dass wir von unserer Sterblichkeit wissen. Sie macht uns nur allzu oft Angst. Da helfen alle Maßnahmen der Gesundheitsvorsorge und Reparaturmedizin auch nicht. Nach rund 100 Jahren machen wir Platz für die Nachgeborenen.

Materielle Produkte haben auch nur eine überschaubare “Lebenserwartung”. Selbst bei bester Pflege fordern Physik (z.B. durch Reibung oder thermische Kräfte) oder Chemie (z.B. durch Oxidation) früher oder später ihren Tribut. Und wo das dem Hersteller zu lange dauert, da setzt er auf planned obsolescence.

Aber wie ist es bei Unternehmen? Wie ist es bei Software?

Bei immateriellen Hilfsmitteln [2] herrscht ein anderes Ideal: sie sollen unbegrenzt lange existieren. Niemand gründet ein Unternehmen mit einer Idee von dessen Lebenserwartung [3]. Und niemand schreibt Software mit einem Verfallsdatum [4].

Ist das nicht pervers, d.h. anormal, verkehrt, unnatürlich – für etwas, das ständig besser werden soll?

Markt und Natur, also die umfassenden Systeme, sollen nicht besser werden, sondern beherbergen das, was immer besser werden soll, um sich als Ganzes zu erhalten [5]. Die umfassenden Systeme kennen keinen auf gleicher Ebene existierenden Konsumenten. Es sind autopoietische Systeme, sie erhalten sich selbst. Wenn sie dienen, dann eher nach innen, ihren Konstituenten gegenüber.

Für mich sieht es so aus, als würden wir Unternehmen sowie Software heute in einem Widerspruch herstellen bzw. betreiben:

Wir strukturieren sie einerseits wie materielle Produktmodelle, d.h. vergleichsweise starr. Das haben wir ja auch über Jahrhunderte gelernt. Sie erfüllen zu einem Zeitpunkt einen Zweck, sind temporär also gut. Eine Verbesserung können wir erst mit einem neuen Modell erwarten.

Andererseits denken wir sie unsterblich, d.h. als übermaterielles System, das es durch ständige Veränderung im Inneren zu erhalten gilt.

Ja, was denn nun? Innen starres Hilfsmittel, das über Generationen evolviert – oder innen flexibles System mit unbegrenzter Lebenserwartung?

Da passt doch irgendetwas nicht, oder?

Hinderlicher Erfolg des Immateriellen

Die Quelle für diesen Widerspruch sehe ich in einer scheinbaren Wahl, die wir haben. Immaterielles wie ein Unternehmen oder Software unterliegt keinem natürlichen Verfall – also können wir die Lebenserwartung selbst bestimmen. Und da bestimmen wir mal, dass die unendlich sein soll. Geht doch. Und ist bestimmt unterm Strich billiger, als immer wieder etwas Neues von Grund auf herzustellen, oder?

Dass sowohl Unternehmen wie Software dann doch nicht ewig existieren, ist eine andere Sache. Shit happens. Potenziell könnten sie es jedoch. Darum gilt es, das anzustreben.

Der Erfolg zu einem bestimmten Zeitpunkt scheint in die Zukunft verlängerbar. Ein Auto funktioniert ja auch mit guter Wartung nicht nur heute, sondern auch noch in Jahrzehnten.

In Wirklichkeit steckt hier jedoch das Missverständnis. Erfolgreich heute – also in Bezug auf heutige Umwelt und Wertmaßstäbe gute Systeme – bedeutet nicht, erfolgreich morgen.

Umwelt und Wertmaßstäbe für Unternehmen und Software sind solchem Wandel unterworfen, dass in ihnen ständig umgebaut werden muss. Ein treffendes Bild scheint mir das einer stehenden Welle: die äußere Form bleibt trotz allem Fluss im Inneren erhalten.

Solange Erfolg mit einem Ist-Zustand an Strukturelementen und Beziehungen gleichgesetzt wird, funktioniert das aber nicht. Erfolg muss davon abgekoppelt werden. Welche Konstituenten ein System ausmachen, ist für die langfristige Existenz des Systems als Ganzem unwichtig. Es gibt nichts per se Erhaltenswertes. Es gibt immer nur einen Systemaufbau, der angesichts einer Umwelt und eines Wertmaßstabs gut genug ist – und sich leicht an Veränderungen anpassen lässt.

Anpassungsfähigkeit ist die einzige erhaltenswerte Eigenschaft. Alles andere steht ständig zur Disposition. Es gibt kein einfach so extrapolierbares Erfolgsrezept im Konkreten, sondern um im Abstrakten.

Praktische Konsequenz

Vielleicht sind unsterbliche Unternehmen und Softwareprodukte wünschenswert. Derzeit sehe ich jedoch nur Versuche zu solcher Unsterblichkeit unter zunehmenden Schmerzen. Wir wissen einfach nicht so richtig, wie wir Unsterbliches aufbauen müssen. Oder wenn wir es wissen, dann setzen wir dieses Wissen nicht systematisch um.

Was also tun? Wir müssen raus aus dem Widerspruch.

Die Blaue Pille nehmen würde bedeuten, bei den aktuellen Konstruktionsweisen zu bleiben. Dann sollten wir aber zumindest die Lebenserwartung von Unternehmen und Software von vornherein begrenzen. “Wir gründen unseren online Shop mit einer Lebenserwartung von 8-12 Jahren.”, “Wir legen die Software auf eine Nutzungsdauer von 3-5 Jahren aus.” – und danach ist Schluss. Ende. Tot. Unternehmen und Software werden dann ersetzt.

Da diese Endlichkeit allen von Anfang an klar ist, kann vorgesorgt werden. Ableger können rechtzeitig gezeugt werden, die neue Erkenntnisse umsetzen – wozu das schon länger Existierende nicht wirklich fähig ist. Die Ausgründung, die Neuentwicklung sind dann keine Überraschungen, sondern ganz natürlich.

Erfolg heute wäre dann kein Ballast mehr für das Bessere. Erfolg wäre nicht narzistisch, sondern bescheiden.

Die Rote Pille nehmen würde hingegen bedeuten, die aktuelle Konstruktionsweise umzukrempeln. Das scheint mir schwieriger – aber nicht unmöglich. Wir müssten lernen, Unternehmen und Software intern eher so zu strukturieren wie ein Ökosystem. Das würde bedeuten, ihre Konstituenten würden evolvieren wie der Automarkt oder die Arten auf einer einsamen Insel.

Hier würde Erfolg per definitionem nicht dem Besserem im Wege stehen. Anpassung wäre in die Systeme eingebaut.

Blaue Pille oder Rote Pille – oder ein Pillencocktail? Ich weiß nicht. Nur sollte es nicht so weitergehen wie bisher. “Aber wir haben doch die letzten 5 Jahren tolle Umsätze gemacht, wir hatten Erfolg… Warum sollten wir jetzt etwas verändern?” darf kein Argument mehr sein, mit dem Veränderungen geblockt werden.

Wir müssen die Bedingungen für die Evolution herstellen: Ideen müssen nicht nur her, sondern auch in bunter Vielfalt implementierbar sein. Evolution ist verschwenderisch. Ressourcenverbrauch auf momentane Bedürfnisse hin zu optimieren, steht längerfristigem Überleben im Wege.

Der wahre Feind der Verbesserung lauert also ständig schon im eigenen System: es ist der heutige Erfolg. Diese Situation müssen wir systematisch bekämpfen.

Fußnoten

[1] Warum wird der Spruch dennoch immer wieder geäußert. Je älter Menschen werden, desto eher scheinen sie ihn auszusprechen. Ich denke, das liegt daran, dass es mit dem Alter erstens überhaupt möglich wird, einen Vergleich zwischen früher und heute anzustellen. Und zweitens hat der Spruch weniger mit den Eigenschaften eines Gegenstandes oder einer Dienstleistung zu tun, als vielmehr mit dem Wertmaßstab.

Es kann sein, dass der vergleichsweise unverändert ist; er hat sich nicht auf die Co-Evolution von Angeboten und Umwelt eingelassen.

Oder es kann sein, dass er sich einfach nur in eine andere Richtung entwickelt hat. Mit zunehmendem Alter verschieben sich Bedürfnisse eben.

[2] Unternehmen subsummiere ich hier mal unter dem Begriff Hilfsmittel. Gegenüber seinen Kunden, ist ein Unternehmen natürlich kein Hilfsmittel, sondern produziert welche. Aber Eigentümer und Angestellte sehen Unternehmen natürlich als Hilfsmittel zur Existenzsicherung.

[3] Ausnahmen gibt es natürlich: Filmproduktionsfirmen haben u.U. eine bewusst auf die Herstellungsphase begrenzte Lebensdauer. Auch Fonds können so organisiert sein, dass ihre Firma nur für die Laufzeit existiert. Oder “Blasenfirmen”, die nur solange irgendwie lebensfähig und vor allem vielversprechend sein müssen, bis sie jemand kauft.

[4] Von Software, die durch absehbare Gesetzesänderungen nur begrenzt relavant ist, sehe ich einmal ab. Meist betreffen solche Termine der Umwelt ja auch nur Teile einer Software.

[5] Erhaltung kann natürlich auch als eine Form der Verbesserung angesehen werden – aber auf einer höheren Ebene. Wenn die Natur heute aber anders aussieht als vor 100 Millionen Jahren, für wen macht das einen Unterschied #2?

Freitag, 21. Dezember 2012

Verteilte Weihnacht mit der Flow Runtime

Für die Freunde des Flow-Designs hier zu Weihnachten eine kleine Überraschung: Flows verteilen mit der Flow Runtime einfach gemacht. Als Beispiel soll Bestellung und "Aufbau eines Weihnachtsbaums dienen.

Wer kennt das nicht: Vor lauter Weihnachtsfeier- und Jahresendprojektstress wird der Kauf eines Weihnachtsbaums bis zum letzten Tag verschoben. Und dann muss man sehen, was übrig bleibt. Das macht die Familie gar nicht glücklich.

Aber jetzt gibt es eine Weihnachtsbaumanwendung für den Windows Desktop :-) Mit der kann man sich einen Weihnachtsbaum in verschiedenen Größen bestellen. So siehts aus:

image

Mit dem Slider auf der rechten Seite stellt man die gewünschte Zahl der Äste ein – und schon wird der Weihnachtsbaum hergestellt und “aufgebaut”. Naja… nur auf dem Bildschirm. Aber immerhin. Bevor es gar keinen gibt… ;-)

Die Herstellung des Weihnachtsbaums übernimmt eine TreeFactory:

image

In ihr gibt es drei “Herstellungsstationen”, die ein Baum nacheinander durchläuft. Der Flow dafür sieht so aus:

image

Zuerst werden nur die Äste in Form von *-Reihen erzeugt, dann werden diese Reihen so eingerückt, dass das charakteristische Dreieck entsteht, und schließlich kommt darunter ein kleiner I-Stamm.

Lokale DIY-Herstellung

In der ersten Ausbaustufe wird der Weihnachtsbaum lokal hergestellt, sozusagen xmas tree fabbing. Die TreeFactory läuft in der Desktop-Anwendung im Rahmen eines allgemeineren “Weihnachtsfabrik”-Flusses. Weihnachtsbäume sind ja auch nur eine, was man zum Fest herstellen könnte:

image

Die Anwendung bestellt beim Start sogleich einen Baum mit 2 Ästen - und danach immer wieder, wenn der Schieberegler bewegt wird. Und das alles from the comfort or your living room :-)

image

Auslagerung der Produktion

Das Weihnachtsbaum fabbing ist hübsch modern. Aber leider kommt dabei nur ein Weihnachtsbaum aus “Plastik” heraus. Ein natürlicher Weihnachtsbaum wäre schöner, oder? Oder einer, bei dem man aus verschiedenen Materialien wählen kann? Jedenfalls scheint eine Auslagerung der Weihnachtsbaumproduktion Vorteile zu bieten.

Aus der lokalen Weihnachtsbaumproduktion soll deshalb eine entfernte werden. Allerdings ohne große “Off-Shoring-Schmerzen”. Wie kann das mit Flows gehen?

Aus der Desktop-Anwendung wird ein Desktop-Client und die Produktion wandert in einen Server.

Dazu wird der Flow xmasfactory in der Anwendung durch eine generische so genannte Stand-In Operation ersetzt:

image

Die sieht nach außen immer genauso aus wie die Funktionseinheit, die sie ersetzt – intern jedoch leitet sie Input über ein Kommunikationsmedium weiter an die entfernte Funktionseinheit und leitet deren Output zurück in den lokalen Flow. Im Beispiel geschieht das mit WCF als API und TCP als Medium.

Der Code ändert sich dadurch wie folgt:

image

Die WcfStandInOperation bekommt eine lokale Adresse, auf der sie entfernten Output empfängt, und eine entfernte Adresse für die “off-shore” xmasfactory, an die sie Input sendet.

In der Flow-Definition wird der bisherigen lokalen xmasfactory der Name der Stand-In Operation mit “#” voran gesetzt. Dadurch wird die Stand-In Operation adressiert, behält über die Instanzkennung [1] nach dem “#” aber die Information, welche Funktionseinheit sie ersetzt.

Die WcfStandInOperation kommt aus der Assembly npantarhei.distribution.wcf, die der Client nun referenzieren muss. Die ist nun Teil des NPantaRhei Repositorys.

image

Soweit der Client. Er wird im Grunde einfacher. Die Servicefunktionalität entfällt und wird ersetzt durch ein Stand-In.

Aber keine Sorge, der Server, in dem sie neu aufgehängt wird, ist leicht herzustellen. Der enthält die extrahierte Funktionseinheit nun allein auf oberster Ebene in einem Flow:

image

Daran ist keine Veränderung vorzunehmen; sie muss sogar so aussehen, wie zuvor, weil sie sonst nicht durch das Stand-In korrekt repräsentiert würde.

Allerdings wird die xmasfactory nun direkt über Flow-Ports angesteuert. Wie sollen die heißen, damit sie automatisch bestimmt werden können? Aus xmasfactory.Build_tree wird .Build_tree@xmasfactory.

Dieser kleine Trick ist nötig, weil in einer Portbeschreibung nur ein Punkt vor dem Portnamen stehen soll. Die Beschreibung .xmasfactory.Build_tree wäre daher ungültig. Also wird der Portname nach vorne gezogen und der Funktionseinheitsname mit “@” dahinter gesetzt. Das ist nicht besonders schön, funktioniert aber erstmal. So können aus der Instanzkennung und dem Portnamen vom Stand-In die serverseitigen Portbeschreibungen bestimmt werden und umgekehrt.

Damit Requests an den Server zu Input für den Flow werden und Output als Response zum Stand-In zurückfließen kann, wird die Flow Runtime in einem so genannten Operation Host gehalten:

image

Der Operation Host empfängt über einen transportmediumspezifischen Transceiver Daten als Input für Flows in der Runtime; und Output aus Wurzelports der Runtime leitet er über den Transceiver dorthin zurück, von wo der zugehörige Input gekommen ist. Den Bezug stellt er über eine out-of-band Correlation-Id her, die er dem Input bei Empfang aufprägt und die über den ganzen Flow erhalten bleibt.

Was sich vielleicht kompliziert anhört, ist in der Codepraxis aber simpel:

image

In der Flow-Definition taucht die xmasfactory wie bekannt aus der lokalen Anwendung 1:1 auf. Ansprechbar wird sie gemacht durch die globalen Port des Wurzel-Flows wie oben beschrieben.

Ansonsten kommt nur der WcfOperationHost dazu, der die Flow Runtime aufnimmt und eine Adresse erhält, auf der er angerufen werden kann.

image

Fertig!

image

Mehr ist nicht zu tun, um eine Funktionseinheit entfernt zu betreiben. Der Flow im Client merkt davon im Grunde nichts. Und die entfernte Funktionseinheit auch nicht. Sie müssen also nicht immer wieder neue Servicedefinitionen vornehmen wie bei WCF. Mit denselben Bausteinen – Stand-In Operation und Operation Host – können Sie jede Funktionseinheit verteilen. Nur die Daten, die über das Transportmedium als Input dorthin und als Output von dort zurück fließen, müssen [Serializable] sein.

Fazit

Die Verteilung von Flows sollte so einfach wie möglich sein. Ich glaube, das ist nun ganz grundsätzlich der Fall. Die WCF-Bausteine sind dabei nur erste einfache Adapter für ein Transportmedium als Beispiele dafür, wie es geht. Nähere Informationen dazu und auch zur Behandlung von Exceptions in verteilten Flows, in der dotnetpro 2/2013.

Ich hoffe, es ist rüber gekommen, dass Sie Flows jetzt lokal entwickeln und testen können – und anschließend einen beliebigen Teil einfach verpflanzen. Dass die Kommunikation dann asynchron wird, ist nicht so schlimm, weil Flows darauf ohnehinn viel besser vorbereitet sind.

Indem Sie vor der Verteilung zum Beispiel ein Operation im zu entfernenden Flow auf asynchrone Verarbeitung setzen oder den Verarbeitungsmodus der Flow Runtime auf asynchrone Breitenorientierung setzen, können Sie deklarativ ganz einfach simulieren, wie sich die Verarbeitung nach der Verteilung verhalten wird.

Oder sie betreiben die Flow Runtime für den Client und die Flow Runtime für den Server im selben Adressraum mit einem kleinen Bus verbunden statt über WCF. Dazu finden Sie eine Hilfestellung in der Assembly npantarhei.distribution, auf die npantarhei.distribution.wcf aufbaut.

Fußnoten

[1] Instanzkennungen sind schon lange ein Feature der Flow Runtime. Sie kommen immer dann zum Einsatz, wenn eine Funktionseinheit in einem Flow an mehreren Stellen mit dem selben Namen benutzt werden soll.

Das ist bei Stand-In Operationen auch der Fall. Die selbe Stand-In Operation soll für verschiedene entfernte Funktionseinheiten stehen können.

Dienstag, 18. Dezember 2012

Die TDD Single Responsibility

Gerade wird wieder eine TDD Demo über Twitter herumgereicht. Corey Haines hat sich an die Kata Roman Numerals gemacht.

image

Mal abgesehen davon, dass TDD anscheinend ein unerschöpfliches Thema ist und die Katas auf die Dauer langweilig werden… Mir gefällt die Darstellung aus einem anderen Grunde nicht so gut.

Corey gibt sich Mühe. Alles läuft seinen kanonischen TDD Weg. Es könnte ein schönes Video sein. Wäre da nicht die ständige Überraschung.

Ja, es hört sich so an, als würde Corey in die Lösung des Problems “Übersetzung arabischer Zahlen in römische” stolpern. Er zaubert Testfälle aus dem Hut und erstaunt sich dann immer wieder selbst mit der Lösung.

Und ich meine hier wirklich die Lösung und nicht den Code.

Da scheint mir ein Grundproblem im Umgang mit TDD zu liegen. Das habe ich auch neulich auf den XPdays in Coding Dojos gesehen. Das Muster ist so:

  1. Es wird ein Problem vorgestellt.
  2. Es wird mit dem Codieren à la TDD begonnen.

Das Ergebnis? Regelmäßig kein Code, der die Aufgabe vollständig erfüllt [1].

Mit dieser Realität sollten wir nicht streiten, denke ich. So ist es einfach. Man bemüht sich redlich um die rechte TDD-Schrittfolge. Das kommt mir vor wie beim Tanzen. Alle starren gebannt auf ihre Füße und hoffen, niemanden anzurempeln. Nur leider geht dabei das große Ganze verloren. Beim Tanzen der Spaß an der Bewegung und am Miteinander. Und bei der Softwareentwicklung lauffähiger Code. Vor lauter TDD-Rhythmus und versuchtem Design funktioniert es nicht mal.

Wie frustrierend, wie tragisch. Kein Wunder, dass auch 2012 immer noch TDD hoch und runter evangelisiert werden muss.

Dabei scheint mir die Rettung der Situation einfach: mehr Systematik.

Ja, tut mir leid, dass ich mit so einem lästigen Wort komme. Das klingt nach Einschränkung, nach viel Aufwand ohne schnellen Nutzen… doch das Gegenteil ist der Fall. Systematik macht frei. Aus Komplexem macht sie Kompliziertes.

Fehlende Systematik überfrachtet TDD. TDD soll plötzlich die ganze Softwareentwicklung retten. Endlich wird das mit der Korrektheit besser. Und dann auch noch bessere Dokumentation durch TDD. Und außerdem höhere Evolvierbarkeit durch besseres Design (lies: bessere Strukturen). Vor allem aber nicht zu vergessen: eine Lösung stellt sich auch wie von selbst ein.

Kommt das niemandem merkwürdig vor? One size fits all?

Ich bin ein großer Freund der Prinzipien Single Responsibility (SRP) und Separation of Concerns (SoC). Danach scheint mir ein bisschen viel Last auf den Schultern von TDD zu liegen.

Das ist es auch, was mich an Coreys Demonstration wieder stört. Er steht dabei nur als einer von vielen, die TDD zeigen. Die Vermischung von Lösung und Design stößt mir auf. Sie ist es nämlich, die zu den erwähnten Misserfolgen in den Dojos führt.

TDD bedeutete zunächst Test-Driven Development. Da ging es also um eine bestimmte Art zu codieren. Red-green-refactor. Das hat vor allem zu hoher Testabdeckung geführt.

Dann wurde aus dem Development das Design: Test-Driven Design. Die Betonung wurde damit auf den Refactoring-Schritt gelegt. Zur hohen Testabdeckung sollte dann auch noch eine “gut” Codestruktur kommen.

Und heute? Mir scheint, dass es gar nicht mehr um TDD geht, sondern um TDPS: Test-Driven Problem Solving. Nicht nur sollen Tests zu einem Design führen – denn über das Design soll man ja vorher nicht nachdenken, es soll entstehen, in minimaler Form. Nein, jetzt sollen am besten die Tests auch noch die Lösung herbeiführen.

Wenn Sie sich nun fragen, was denn da der Unterschied sei, dann rühren Sie genau an dem Problem, das ich meine: Es wird kein Unterschied zwischen Lösung und Code gesehen. Oder vielleicht sollte ich sagen, zwischen Lösungsansatz und Code? Ist es dann deutlicher?

Hier zwei Beispiele für den Unterschied.

Als erstes eine Lösung für das Problem des Sortierens eines Feldes. Text und Bild beschreiben einen Ansatz, sie beschreiben ein Vorgehen, sie sagen, wie man das Ziel ganz grundsätzlich erreichen kann:

image

Und jetzt ein Design in F# für diesen Lösungsansatz:

image

Im Design, im Code finden sich natürlich die Aspekte und Schritte des Lösungsansatzes wieder. Aber der Code ist nicht der Lösungsansatz. Er implementiert ihn in einer bestimmten Programmiersprache mit bestimmten Sprach- und Plattformmitteln.

Als zweites ein Lösungsansatz für ein Problem im Compilerbau, die Erkennung von Bezeichnern. Hier ein Syntaxdiagramm dafür:

image

oder alternativ ein Deterministischer Endlicher Automat:

image

Dass es überhaupt eine Phase zur Erkennung von Bezeichnern gibt (lexikalische Analyse), ist ebenfalls Teil eines Lösungsansatzes:

image

Das konkrete Code-Design, die Implementierung des Lösungsansatzes, könnte dann so aussehen:

image

Lösungsansatz – oder auch Modell – und Code in einer bestimmten Struktur – nach TDD auch Design genannt –, sind einfach verschiedene Aspekte. In den TDD-Vorführung wie bei Corey und den TDD-Selbstversuchen in den Dojos werden die jedoch nicht sauber getrennt. Immer wieder wird gehofft, durch Red-Green-Refactor nicht nur ein evolvierbares Design herzustellen, sondern auch eine Lösung zu bekommen.

Das (!) halte ich für falsch. Erstens ganz praktisch aus der Beobachtung heraus, dass so selten lauffähiger Code entsteht, der die Aufgabe erfüllt. Zweitens eher theoretisch aus dem Gedanken heraus, dass wir Menschen damit schlicht unterfordert sind. Das über Jahrtausende geschliffene Werkzeug “Denken” wird nicht genutzt. Man hofft vielmehr, durch Mustererkennung beim Code, irgendwie zu einer Lösung zu kommen.

Das funktioniert manchmal tatsächlich, wenn man genau hinschaut. Die Kata Roman Numerals könnte dafür ein Fall sein. Nur ist nicht zu erwarten, dass das immer so geht. Auf den Quicksort Lösungsansatz kommt man nicht durch TDD, davon muss man einfach eine Vorstellung entwickeln – eben einen Lösungsansatz. Im Kopf. Durch Nachdenken.

Und wie sollte es dann anders aussehen mit TDD?

Systematischeres Vorgehen

Systematisierung, Entzerrung, Entlastung, Fokus finde ich wichtig. Aus meiner Sicht sollte das Vorgehen diese Schritte beinhalten:

  1. Problem vorstellen
  2. Lösungsansatz entwickeln
  3. Testfälle ermitteln und priorisieren
  4. Lösungsansatz mit TDD implementieren

Schritte 2. und 3. können dabei mehrfach durchlaufen werden. Und wenn sich bei 4. noch neue Erkenntnisse zum Lösungsansatz ergeben sollten, dann ist das auch ok. Aber 4. ohne explizites 2. und 3. zu beginnen, halte ich für eine Überlastung.

Damit wären Lösungsansatz und Design getrennt. Damit würde – da bin ich ganz sicher – die Erfolgsquote jedes Coding Dojos steigen. Und wenn nicht, dann würde man genau sehen, woran es liegt: Liegt es an mangelnden TDD-Fähigkeiten oder liegt es an mangelndem Problemverständnis und dadurch fehlender Lösungsphantasie?

Die Single Responsibility von TDD liegt für mich bei der Testabdeckung und bei einer hohen Strukturqualität im Kleinen [2].

Was jedoch da überhaupt in Code gegossen und strukturiert werden soll… das ergibt sich nicht durch TDD, sondern durch Nachdenken. Den Lösungsansatz zu finden und die Testfälle zu priorisieren, das ist nicht Teil von TDD – muss aber gezeigt werden. Denn wird es nicht gezeigt bzw. wie bei Corey mit dem TDD-Vorgehen vermischt, entsteht entweder eine Überlastung, die die Aufgabenerfüllung behindert. Oder es entstehen “Wunderlösungen”, über die man nur staunen, sie aber eher nicht nachvollziehen kann. Wer “Wunderlösungen” goutiert, der wird es schwer haben, selbst Lösungen für andere Probleme zu finden.

Fußnoten

[1] Damit will ich nicht sagen, dass es keine Teams gibt, die die Aufgaben schaffen. Aber das passiert eben nicht regelmäßig und systematisch. Ich erinnere mich lebhaft an ein Dojo auf den XPdays und an eines auf der DDC im letzten Jahr. 5-10 Teams gab es jeweils. Und keines hat funktionierenden Code für die Kata Roman Numerals bzw. die Kata Zeckendorf (Übersetzung einer ganzen Zahl in eine Zeckendorf-Sequenz) abgeliefert.

[2] Wobei sogar diese Strukturqualität im Kleinen nicht einfach so entsteht, wie ich an anderer Stelle schon ausgeführt habe. Dafür braucht es Refactoring-Kompetenz und –Willen. Die kommen nicht aus TDD. Dafür braucht es aus meiner Sicht noch Hilfestellung, die Refactorings näher legt. Dadurch kann sich ein TDD 2.0 auszeichnen. Aber dazu ein andermal mehr…

Freitag, 14. Dezember 2012

Pervertierte Dienerschaft

Was kann uns der Deal zwischen den USA und der HSBC über die Softwareentwicklung sagen?

image

Dass der stellvertretende US Generalstaatsanwalt die Verantwortlichen für Milliarden-Geldwäscheaktionen ungeschoren davon kommen lässt, ist natürlich ein Skandal. Aber ist das das Hauptproblem? Nein.

Das Hauptproblem ist, dass überhaupt eine Begründung gegeben werden konnte, wie sie gegeben wurde:

Eine andere Strafe hätte nicht nur die HSBC destabilisiert, sondern das weltweite Finanzsystem. Weil die HSBC systemrelevant ist.

Dass es überhaupt 1 Firma, also eine Organisation außerhalb eines Staates geben kann, die eine Größe hat, die sie im Grunde außerhalb von Gesetz und Ordnung stellt… das (!) ist der wahre Skandal.

Ob die HSBC so eine Organisation ist, ist zweitrangig. Erstrangig ist, dass es denkbar, gar hoffähig ist, dass es solche Organisationen gibt.

Da gibt es ein Kartellrecht, das begrenzt, wie sich Organisationen entwickeln, um einen Markt nicht zu dominieren. Eine gute Sache!

Doch wo ist das Recht oder noch vorher das Prinzip oder der Wert, um Systemrelevanz zu vermeiden? Das finde ich viel wichtiger.

Wir hängen von vielen Systemen ab: Finanzsystem, Verkehrssystem, Kommunikationssystem, Energieversorgungssystem, Gesundheitssystem, ökonomisches System, politisches System usw. Dass es diese Systeme gibt, sei akzeptiert. Das nennen wir auch Zivilisation.

Doch dann sollten wir doch tunlichst darauf achten, dass wir diese Systeme funktionsfähig halten. Beim ökonomischen System und dem politischen System haben wir doch davon eine sehr präzise Vorstellung: Zur Funktionsfähigkeit, zur Flexibilität, zur Erhaltung von Freiheit gehört Vielfalt, also eben die Abwesenheit von Dominanz. Wir wollen keine Monopole in der Wirtschaft, wir wollen keine politischen Einseitigkeiten (Ein-Parteien-System, Diktatur).

Und dann lassen wir es zu, dass es im Finanzsystem so große “Player” gibt, die das System gefährden? Oder auch im Verkehrssystem: Die Deutsche Bahn hat dort systemrelevante Größe. Wir müssen uns der Bahn beugen. Für den Schienenfernverkehr ist sie alternativlos. Oder im Energiesystem: RWE & Co sind so groß, dass sie die Politik bestimmen.

Denn das ist es, was bei systemrelevanter Größe unweigerlich entsteht: eine “Inversion of Control”. Nicht das System (bzw. die Politik) kontrolliert die ”Player”, sondern ein “Player” kontrolliert das System. Systemrelevanz stellt den “Player” quasi außerhalb des Systems. Aus einem Diener wird ein Herr. Das ist pervers. Es widerspricht dem gesunden Menschenverstand, der von einer Bank, einem Verkehrsbetrieb, einem Energieproduzenten usw. erwartet, dass er den Menschen dient. Unternehmen sind Mittel und keine Zwecke.

Anders jedoch im Fall der HSBC: Hier hat sich das Mittel zum Zweck aufgeschwungen, den es zu verfolgen gilt. Die Erhaltung der HSBC ist wichtiger, als Recht in vollem Umfang zu sprechen.

Bezug zur Softwareentwicklung

Was für unsere zivilisatorischen Systeme gilt, gilt natürlich auch für unsere technischen. Wenn Systembestandteile systemrelevante Größe erreichen, beginnen sie ein Eigenleben. Sie stellen sich außerhalb sachlicher Entscheidungen. Sie tendieren dazu, sich selbst zu erhalten – unter allen Umständen.

Nicht mehr Werte und Prinzipien bestimmen dann den Mitteleinsatz. Sondern Mittel werden eingesetzt, weil sie bereits eingesetzt sind. Ihr Platz im System und/oder in den Köpfen ist so groß, dass ihr Austausch ein unabsehbares Risiko ist.

Dafür haben wir auch einen Begriff: lock-in. Der wird zwar gewöhnlich in Zusammenhang mit einem Hersteller gebraucht (vendor lock-in), doch der lock-in lauert überall. Letztlich ist lock-in auch immer selbstgemacht. Kein Hersteller kann uns fesseln und einsperren; die Entscheidung zur Aufgabe von Freiheiten, zum Eintritt in einen lock-in liegt immer bei dem, der einen Hersteller, ein Paradigma, eine Methode, ein Tool einlädt und zu systemrelevanter Größe heranwachsen lässt.

Am Anfang sieht das meist nicht so schlimm aus. Was später ein lock-in wird, beginnt als Vorteil irgendeiner Art. Und warum auch nicht? Niemand sollte per se etwas gegen SAP oder Oracle oder das relationale Datenmodell oder Entity Framework oder Sharepoint oder Java oder Ruby on Rails oder oder oder haben.

Vorteile bei Kosten, Performance, Skalierbarkeit, Entwicklerverfügbarkeit, Sicherheit oder sonst was sollen eingestrichen werden.

Ich denke jedoch, dass all diese “Vorteilsnahme” immer eine Begrenzung erfahren sollten, sobald ihr Mittel droht, systemrelevante Größe zu erreichen. Wenn SAP, Oracle, Entity Framework, Sharepoint, Java oder Ruby alternativlos werden, wenn man sich nicht mehr vorstellen kann, sie zu ersetzen… dann ist Gefahr im Verzug. Dann hat man alles auf eine Karte gesetzt und seine Seele verkauft.

Systemrelevante Technologien, Tools, Datenmodelle, Paradigmen sind Perversionen. Sie dienen nicht mehr dem System, sondern sind das System. Das ist falsch.

Beispiel: Das “System” “Nachhaltige Softwareentwicklung” kann sich entscheiden, Objektorientierung als Mittel zu wählen, um sein Ziel zu erreichen. Oder es kann sich entscheiden, auf die Sprache C++ zu setzen. Das ist völlig ok – solange klar erkannt wird, dass Objektorientierung und C++ eben nur Mittel sind – die austauschbar bleiben sollten.

Wer daran jedoch nicht denkt und 10 Jahre Millionen von Zeilen C++ produziert – der hat das ursprüngliche “System” “Nachhaltige Softwareentwicklung” untergraben. Es ist hohl geworden, weil Entscheidungen im Sinne der Nachhaltigkeit nicht mehr frei getroffen werden können. Immer sitzt die systemrelevante Größe OOP bzw. C++ mit am Tisch – und hat ein Interesse, sich zu erhalten.

Neue Trends – C# oder Funktionale Programmierung – haben dann kaum eine Chance. Sie zerschellen an der systemrelevanten Größe, die nicht nur die Artefakte dominiert, sondern auch raumfordernd in den Köpfen gewuchert ist.

So stirbt dann ein System. Es bricht nicht zusammen, sondern erstarrt durch Perversion eines oder weniger Konstituenten.

Systemrelevanz lauert überall. Plattformen oder Produkte sind dabei noch einfach auszumachen. Aber auch Tätigkeiten (z.B. bug fixing) oder Personen (so genannte Wissensinseln) oder Vorgehensmodelle oder Glaubenssätze (z.B. Präsenzarbeit) können Systemrelevanz annehmen, d.h. alternativlos und somit einschränkend sein.

Was mit der HSBC geschehen ist, oder besser: was eben nicht geschehen ist, das ist skandalös. Doch wir sollten nicht glauben, dass das nur eine Perversion ist, die irgendwo da draußen passiert. Pervertierte Dienerschaft existiert auch in unseren Systemen in vielerlei Gestalt.

Wer Nachhaltigkeit ernsthaft erreichen will, der muss jeden Tag darauf achten, das Wachstum der gewählten Mittel unterhalb systemrelevanter Größe zu halten. Nachhaltigkeit kann es nur in Vielfalt und Alternativenreichtum geben.

Mittwoch, 12. Dezember 2012

TDD skaliert nicht

Dieser Tage beschäftige ich mich intensiv mit TDD. Ich kann sogar sagen, dass ich ein rechter Freund von TDD geworden bin. TDD ist cool – oder genauer: TDD 2.0 :-) - das ist nämlich TDD as if you meant it (TDDaiymi).

Nicht trotz dieser Freundschaft, sondern wegen ihr schaue ich aber auch genauer hin. Und da sehe ich ganz klar eine Beschränkung in TDD. Und das ist die mangelnde Skalierbarkeit.

TDD ist und bleibt eine Methode für 1 Entwickler (oder auch 1 Entwicklerpaar). That´s it.

imageTDD setzt mit einem Test irgendwo an einer Schnittstelle an und treibt von dort eine Struktur aus. Die wird feiner und feiner. Erst eine Methode, dann mehrere; erst eine Klasse, dann mehrere. Growing Object-Oriented Software, Guided by Tests beschreibt das recht gut.

Bei der Lektüre wird die Begrenzung von TDD aber natürlich nicht so deutlich. Daran haben erstens die Autoren kein Interesse und zweitens ist das Buch selbst ein “Single User Medium”. Man liest es allein und kann deshalb dem TDD-Vorgehen gut folgen.

Doch was ist, wenn 2, 3, 5, 10 Leute im Team sind? Dann kann jeder nach TDD vorgehen. Super! Aber was geht jeder dann mit TDD an?

TDD skaliert, wie Holzhacken skaliert. Wenn ein großer Haufen Holz rumliegt, kann man an jeden Holzklotz einen Holzhacker setzen. Das funktioniert, weil das Gesamtergebnis so einfach ist: ein Haufen Holzscheite. Und jeder Holzklotz ist auch so überschaubar, dass man nur schwer 2 oder 3 Holzhacker an einen setzen wird.

Anders ist das bei einem Sägewerk. Da arbeiten auch mehrere Holzarbeiter – aber an ganz verschiedenen Positionen. Die machen eben nicht alle dasselbe. Die tun Verschiedenes – und koordinieren ihre Arbeit.

Genau diese Koordination fehlt TDD jedoch. TDD ist eine Axt, mit der einer Anforderungen zu Code zerkleinern kann. Und was die anderen tun… Egal. Die machen irgendwo anders mit ihren Äxten auch irgendwas.

TDD skaliert also insofern nicht, als dass damit Zusammenarbeit mehrere Entwickler nicht befördert wird. In TDD selbst steckt keine Idee von Teamwork. TDD ist nur eine Methode, die auf eine gegebene Schnittstelle angewendet werden kann, um dahinter Strukturen auszutreiben.

Aber wie kommt man zu dieser Schnittstelle? Wo ist die im Big Picture einer Software angesiedelt? Dazu hat TDD keine Ahnung.

Und deshalb verfallen Teams immer wieder auf die eine unvermeidbare Schnittstelle als Ausgangspunkt, die jede Software hat: die Benutzerschnittstelle. In der werden Features verortet – und dann je 1 Entwickler an 1 Feature gesetzt, um daran mit TDD zu rumzuhacken.

Klar, damit kann man 1, 2, 5 oder 10 Entwickler beschäftigen. Nur kommt dabei jedes Feature nicht maximal schnell voran. Das meine ich mit “TDD skaliert nicht”. In TDD steckt kein Ansatz, eine Anforderung beliebiger Granularität auf mehr als 1 Entwickler zu verteilen, um es in konzertierter TDD-Aktion schnellstmöglich umzusetzen.

Das meine ich gar nicht als Kritik an TDD. Eine Axt kann ich ja auch nicht dafür kritisieren, dass in ihr keine Idee davon steckt, wie man in konzertierter Aktion mit mehreren Holzarbeitern einen Baum fällt. Eine Axt zerteilt das Holz unter ihr. TDD strukturiert Code hinter einer Schnittstelle unter sich.

Wer die Softwareentwicklung skalieren will, wer schneller 1 Anforderung umsetzen will, um schneller Feedback zu bekommen, der sollte von TDD keine Hilfestellung erwarten. Dafür muss man sich woanders umschauen.

Eine Menge von Schnittstellen, die irgendwie zusammen an der Lösung von 1 Anforderung beteiligt sind und auf die mehrere Entwickler gleichzeitig mit TDD angesetzt werden können, muss sich durch eine andere Methode ergeben.

Deshalb sind TDD-Beispiele auch immer überschaubar. Man kann mit TDD allein nichts Größeres angehen. Code Katas – so nützlich sie sein mögen, um die Methode zu üben – sind winzig. Und wer dann nur mit Code Katas TDD übt, der lernt eben auch nicht mehr als TDD. Der lernt Programmierung in Bezug auf eine Schnittstelle. Das ist eine nützliche Kompetenz so wie das Feilen, Sägen, Schweißen usw. Doch nur weil 20 Leute da sind, die alle feilen, sägen, schweißen können, entsteht noch lange keine Dampfmaschine.

Über TDD hinaus müssen wir uns also mit der Arbeitsorganisation beschäftigen. Wir müssen Wege finden, wie wir Anforderungen in etwas transformieren können, das von mehreren Entwicklern gleichzeitig mit TDD umgesetzt werden kann. Was TDD fehlt, ist eine TDD-Vorbereitung. Sich Gedanken über Testfälle zu machen, ist allerdings zu wenig. Denn auch die beziehen sich ja nur auf eine Schnittstelle.

Also: Was tun vor TDD, damit TDD den größten Effekt hat, damit die Entwickler zum Besten eingesetzt werden, damit endlich aus einer Gruppe von Entwicklern ein Team wird?