Follow my new blog

Donnerstag, 8. November 2012

Was andere schon richtig machen – Qualitätsbewusstsein

imageDas ist Beatrix Biczok. Sie ist Fitness Trainerin in Hamburg. Von ihr können wir als Softwareentwickler etwas lernen.

Als Fitness Trainerin ist es ihr Aufgabe, ihren Kunden zu helfen, Ziele zu erreichen. Die möchten vielleicht kräftiger oder beweglicher werden oder sie wollen abnehmen. Beatrix leitet sie zu diesem Zweck bei körperlichen Übungen an. Ob dabei Geräte zum Einsatz kommen oder nicht, hängt vom Ziel ab.

In jedem Fall müssen die Übungen in bestimmter Weise durchgeführt werden. Es gibt also richtig oder falsch. Vom Liegestütz über Klimmzug bis zum Umgang mit der Langhantel kann man effektiv trainieren oder eben nicht. Man kann Übungen so ausführen, dass sie größtmöglichen Effekt in Bezug auf das Ziel haben. Man kann Übungen so ausführen, dass sie den Körper schonen. Gelenke sollen sich nicht abnutzen, Muskeln nicht reißen, Sehnen nicht überdehnt werden, der Kreislauf nicht überlastet werden.

Zentral für die Arbeit von Beatrix ist, dass sie gerade darauf besonders achtet. Ihr ist die Qualität der Ausführung der Übungen gerade in Bezug auf die Schonung wichtig. Die steht bei ihr über der Zielerreichung. Oder genauer: Die gibt den Rahmen für die Zielerreichung vor.

Bei ihr kann man sein Ziel nur so schnell erreichen, wie es bei körperschonender Ausführung der Übungen möglich ist. Beatrix ist natürlich kundenorientiert. Sie nimmt die Ziele ihrer Kunden absolut ernst – allerdings nicht unter allen Umständen. Sie erlaubt sich, ihren Kunden nämlich insofern reinzureden, als dass sie ihnen Grenzen aufzeigt. Ihr Qualitätsbewusstsein steuert den Fortschritt, nicht der unbedingte Wille des Kunden.

Insofern handelt sie im Sinne Heinz von Försters ethisch: Sie bemüht sich darum, die Zahl der körperlichen Optionen Ihrer Kunden zu vergrößern – und mindestens zu erhalten. Denn Raubbau an der Gesundheit durch falsche Ausführung von Übungen mag zwar kurzfristig größere Schritte in Richtung Ziel ermöglichen – langfristig jedoch verringern sich dadurch die Optionen, wenn Schäden an Bewegungsapparat oder Kreislauf entstehen. Ein Ergebnis, das viele ehemalige Spitzensportler bezeugen können.

Wo ist der Bezug zur Softwareentwicklung?

Softwareentwicklung hat auch den Zweck, ihren Kunden zu helfen, Ziele zu erreichen. Wer eine Software kauft/in Auftrag gibt, der verspricht sich davon Nutzen. Möglichst schnell, möglichst umfassend. So wie ein Fitnessjünger auch möglichst schnell und umfassend kräftiger, beweglicher, schlanker werden will.

Das ist verständlich. Solches Ziel zu erreichen, wollen und sollen Fitnesstrainer wie Softwareentwickler erreichen helfen. Nach Kräften. Aber eben nicht bedingungslos. Denn eine Forderung stellt jeder Kunden implizit immer: Hilfe soll nachhaltig sein. Sie soll seine Optionen in naher Zukunft vergrößern – aber nicht auf Kosten seiner Optionen in fernerer Zukunft. [1]

Qualität in der Ausführung in jedem Moment ist für Beatrix mithin kein Selbstzweck. Das hat nichts mit Eleganz, romantischer “Handwerkskunst” oder Prinzipienreiterei zu tun. Es ist vielmehr Ergebnis der Erkenntnis, dass unbegrenzter Eifer letztlich zu persönlicher Unzufriedenheit oder gar volkswirtschaftlichem Schaden führt.

Qualität ist keine Spinnerei, sondern ökonomisches Primat – wenn, ja, wenn die mittelfristige Zukunft Bedeutung hat.

Wenn Softwareentwicklung das Ziel einer (nicht-)funktionalen Anforderungen ins Auge fasst, da sollte sie eingedenk der Erfahrung im Fitness Bereich sein. Sie sollte auf hohe Qualität bei jedem Schritt achten, um die Zahl der Optionen ihres Kunden in der Zukunft nicht auf Kosten schneller Erfolge zu verringern. Wenn Sorgfalt dann etwas mehr Zeit brauchen sollte… dann ist das eben so. Nicht der Kunde bestimmt die innere Qualität einer Ausführung, sondern der Softwareentwickler. Nur er kann sich wirklich ein Urteil darüber erlauben. Nicht der Fitnessjünger bestimmt, ob er eine Übung gut ausgeführt hat, sondern eine Qualitätssicherungsinstanz wie Beatrix.

Lassen wir uns also nicht die Butter vom Brot nehmen in der Softwareentwicklung. Kein Laie – weder Kunde, noch Manager – soll uns sagen, wie wir Software zu entwickeln haben. Wir wollen gern helfen, ihre Ziele zu erreichen – aber die Qualität darf dabei nicht auf der Strecke bleiben. Und wenn die Auftraggeber das nicht verstehen, dann müssen wir es ihnen immer und immer wieder erklären.

Beim Körper sind die Symptome falscher Ausführung von Übungen bekannt: Muskelkater, Zerrung, Muskelriss, Entzündung, Knochenbruch, zerplatzte Adern… Oder einfacher: Schmerz ist das Resultat qualitativ minderwertiger Ausführung. [2]

Und was sind die Symptome falscher Ausführung der Softwareentwicklung? Was, wenn dort nicht bei jedem Schritt auf Qualität geachtet wird? Dann leidet die Organisation Schmerzen. Der drückt sich entweder in persönlichen Schmerzen der Mitarbeiter aus: da reicht er von Lustlosigkeit über “Dienst nach Vorschrift” und psychische/somatische Krankheit bis zum Verlassen des Unternehmens. Oder er drückt sich in Mängeln auf Systemebene aus: Zeitmangel, Geldmangel, Personalmangel.

Schmerz zeigt an, wo Kompensationsfähigkeit überschritten ist.

Der Zweck von Fitness Training ist nun aber, Kompensationsfähigkeit aufzubauen. Es tut daher gut daran, keine Schmerzen zu erzeugen. Dito die Softwareentwicklung. Daher sollte Qualität den Rahmen bei der Softwareentwicklung vorgeben, in dem andere Ziele angestrebt werden, und nicht ein nice-to-have Anhängsel sein.

Das können wir von Beatrix als Fitness Trainerin lernen.

Fußnoten

[1] Von Notsituationen, in denen ungewiss ist, ob es überhaupt eine fernere Zukunft gibt, sehe ich einmal ab. Wer sich in einer Unfallsituation durch eine “körperliche Übung” in Sicherheit bringen muss, kann und soll nicht auf deren saubere Ausführung achten. Es zählt einzig der unmittelbar maximale Effekt. Wenn dabei eine Zerrung oder Schlimmeres entsteht, die langfristig eine Einschränkung darstellt, dann ist das im Vergleich zum Schaden vernachlässigbar. Es wurde unter Umständen überhaupt eine Zukunft erschaffen, was ja eine fundamentale Erhaltung von Optionen darstellt.

Dito in der Softwareentwicklung. Hier mag es zum Beispiel bei einem Startup nötig sein, überhaupt eine Software herzustellen, um fundamental Optionen in der Zukunft zu haben. Wenn die nicht sauber ausgeführt ist, also eigentlich nicht nachhaltig, dann ist das für den Moment verzeihlich.

[2] Dass es auch Menschen gibt, die Schmerz lieben, ihn quasi als Qualitätskriterium ansehen, ist nicht zu leugnen. Jenseits einer gewissen Grenze, kann das aber als Perversion bzw. pathologisch angesehen werden. Ob wir uns dann als Befriedigungsgehilfen einspannen lassen… das muss jeder für sich überlegen. Als Normalzustand sollten wir es nicht ansehen.

Sonntag, 4. November 2012

Notorische Aspektlosigkeit

Immer wieder, wenn ich Resultate von Coding Dojos sehe, beschleicht mich ein ungutes Gefühl. Irgendetwas fehlt mir bei den Lösungen. Dabei ist es egal, ob das Coding Dojo in der Gruppe durchgeführt wurde oder sich jemand allein zum Einzeltraining ins Dojo begeben hat.

Anlässlich eines kundeninternen Coding Dojos habe ich nun nochmal länger über die gefühlte Lücke nachgedacht. Als Aufgabe wurde die Kata Word Wrap bearbeitet. Der Abend war gut vorbereitet: Laptop, Beamer, Catering, Aufgabenbeschreibung. Und sogar Testfälle waren hübsch priorisiert schon notiert, damit sich die Gruppe auf die TDD-Fingerfertigkeit konzentrieren konnte [1].

Nach knapp 90 Minuten war denn auch die Lösung fast wie aus dem Buch erarbeitet. Alles war harmonisch verlaufen. Kein Haareziehen, keine Weigerungen, mal nach vorn zu gehen und im Pair zu codieren. Alles hätte also gut sein können.

Allein… der Code sah so aus:

image

oder vielleicht auch so:

image

oder er könnte auch so ausgesehen haben:

image

Letztlich ist es egal, wie genau er ausgesehen hat. Ich habe ihn nicht mitgeschrieben, die Beispiele oben sind aus den Weiten des Internet. Alle haben aber das gemeinsam, was ich als fehlend empfunden habe.

Mir fehlen die Aspekte.

Coding Dojo Lösungen sind nach meiner Erfahrung notorisch aspektlos. Oder anders ausgedrückt: die Prinzipien SRP und SoC finden selten Anwendung. SLA kommt allerdings hin und wieder vor.

Worüber ich immer wieder gestolpert bin ist also die Unverständlichkeit der Lösungen. Wie funktioniert denn so ein Word Wrap. Schauen Sie sich einfach das erste Listing an und formulieren Sie in einem ganzen Satz, wie (!) das geht mit dem Wortumbruch.

Ich bezweifle nicht, dass die Lösungen funktionieren. So unterschiedlich sie aussehen, die kanonischen Testfälle absolvieren sie fehlerfrei. Aber das ist ja seit 50 Jahren eher nicht das Problem in der Softwareentwicklung. Wir können lauffähige Software herstellen. Und auch noch solche, die performant, skalierbar, sicher usw. ist.

Dass in der Eile da mal ein paar Bugs durch die Maschen schlüpfen… Das ist nicht schön, kriegen wir aber in den Griff, wenn wir mehr automatisiert testen. TDD hilft auch.

Aber ist der nützliche Code, den wir da produzieren auch verständlich?

Eher nicht. Und das beginnt bei so kleinen Beispielen wie den obigen.

Vielleicht stelle ich mich jetzt dumm an? Vielleicht schade ich geradezu meinem Ansehen, wenn ich meine Begriffsstutzigkeit so deutlich dokumentiere? Keine Ahnung. Ist mir auch egal. Fakt ist, dass ich es schwierig finde, aus den obigen Beispielen herauszulesen wie (!) so ein Wortumbruch funktioniert.

Vielleicht schauen Sie ja einfach drauf und sagen nach 10 Sekunden: “Achsooo! So geht das. Klar. Hätte ich auch so gemacht.” Kann sein. Dann beneide ich Sie. Ich würde das auch gern können.

Bitte verstehen Sie mich richtig: Ich kann natürlich den Code lesen und herausfinden, wie er funktioniert. Ich kann auch selbst eine Lösung herstellen.

Mir geht es aber nicht darum, ob ich irgendwie zu einem Codeverständnis gelangen kann, sondern wie schnell.

Ich habe den vielleicht verwegen klingenden Anspruch, dass sich im Code die Domäne widerspiegelt. Dass oben drüber Wrap() steht, ist mir nicht genug. Dadurch weiß ich nur, worum es irgendwie geht. Das hilft mir wenig, wenn ich den Code ändern soll, weil ein Bug drin steckt oder sich die Anforderungen geändert haben.

Sorry to say, die obigen Lösungen sind im Wesentlichen alle Monolithen. Kleine Steine, aber immer noch ziemlich strukturlose Steine. Nur Uncle Bob hat es etwas besser gemacht. Aber dazu gleich mehr.

Ich hab es einfach satt, im Steinbruch zu arbeiten. Ich will nicht mehr Archäologie betreiben und darüber rätseln, was eine Entwicklerintelligenz irgendwann mal ersonnen hat, das ich mir nun mühsam wieder erarbeiten muss. Ich will nicht bei jedem 20-Zeiler Reverseengineering betreiben. Das ist einfach teuer. Das kostet wertvolle Lebenszeit. “Was will uns der Autor sagen?” ist eine Frage für das Feuilleton oder den Deutschunterricht.

Jeder genügend obskure Code erscheint magisch ;-) (frei nach Arthur C. Clark)

Was ist stattdessen will, ist eine irgendwie lesbare Lösungsbeschreibung – im Code. Ja, genau, im Code. Mir geht es nicht um Papierdokumentationsberge und auch nicht um mehr Kommentare. Ist schon so, im Code steckt die ultimative Wahrheit. Aber wenn das so ist, dann doch bitteschön etwas leichter erkennbar als bei den Upanishaden oder der Kabbala.

Also, wie funktioniert ein Word Wrap? Keine Ahnung, wie die obigen Autoren meinen, dass es funktioniert. Aber hier mal ein Vorschlag:

  1. Der Input-Text wird in Worte zerlegt.
  2. Die Worte werden in Silben zerlegt.
  3. Die Zeilen des Output-Textes werden aus Silben zusammengesetzt, bis die maximale Zeilenlänge erreicht ist.

Das ist mein Vorschlag für eine Lösung auf hohem Abstraktionsniveau, sozusagen ein Entwurf. Ich habe das Problem in drei Aspekte zerlegt: Wortermittlung, Silbenbestimmung, Zeilenzusammenbau. Der Entwurf folgt damit meiner Interpretation der Aufgabenstellung, die da lautet “KataWordWrap”.

  1. Ich nehme den Wortumbruch ernst. Deshalb taucht in meiner Lösungsskizze der Begriff “Wort” auf.
  2. Die Silben sind meine Hinzudichtung. Die stehen nicht in der Aufgabenstellung. Mit ihnen meine ich auch nicht un-be-dingt Sil-ben, sondern Abschnitte von Worten, zwischen denen getrennt werden darf. In der Problemstellung ist das jeder Buchstabe. Den Begriff “Silbe” habe ich hinzugedichtet, um das Problem an etwas Bekanntes anzuschließen.

Mit dieser Lösungsidee im Hinterkopf schaue ich nun auf den obigen Code und sehe… nichts. Dort tauchen die Begriffe “Wort” oder “Silbe” nicht auf. Und Lösungsschritte sehe ich auch nicht.

Allerdings macht Uncle Bob eine Ausnahme. Dessen Code liefert einen gewissen Hinweis auf seinen Lösungsansatz. Allerdings lese ich auch da nichts von “Wort” oder “Silbe”, sondern von “Zeilenbruch”. Hm… das ist interessant.

Meine Interpretation: Uncle Bob löst ein anderes Problem als ich ;-) Uncle Bob löst das Problem “Zeilenlängenbegrenzung” (oder so ähnlich) und nicht das Problem “Wortumbruch”. Beides mag ähnlich klingen, ist aber etwas anderes.

Wer hat nun Recht? Im Ergebnis würden wir wohl dasselbe liefern – aber unsere Lösungen würden sich eben unterscheiden. Wir sehen unterschiedliche Domänen.

Aber auch wenn Uncle Bob eher die Domäne im Code repräsentiert, wie er sie aus der Aufgabenstellung herausgelesen hat, finde ich seinen Lösungsansatz noch nicht leicht zu lesen. Was bedeutet zum Beispiel if (s.charAt(col) == ‘ ‘)?

Klar, dass kann ich mir irgendwie zusammenreimen. Aber ich will mir nichts mehr zusammenreimen. Ich habe mal Informatik studiert und nicht Ethnologie oder Archäologie. Wenn der Code nicht intention revealing ist, dann habe ich damit ein Problem.

Code muss ich lesen und verstehen, nicht wenn und weil er läuft, sondern wenn er eben nicht läuft wie gewünscht, d.h. wenn ich an ihm etwas verändern soll. Dann muss ich verstehen, wie er seine Aufgabe heute erledigt und leicht die Ansatzpunkte identifizieren, wo ich Veränderungen im Sinne neuer Anforderungen anbringen muss.

Was könnten denn Veränderungen sein, die an “Word Wrap” Code herangetragen werden?

  • Die maximale Output-Zeilenlänge ändert sich. Es verändert sich nur eine Konstante.
  • Der umzubrechende Text liegt nicht in einer Zeile, sondern in mehreren vor. Es verändert sich also die Struktur der Quelldaten.
  • Der Quelltext hat mehrere Absätze, die im Output erhalten bleiben sollen. Eine Veränderung nicht nur in der Quelldatenstruktur, sondern auch in der Zieldatenstruktur.
  • Worte sollen nicht mehr an beliebiger Stelle geteilt werden dürfen, sondern nur noch zwischen Silben.
  • Beim Umbruch sind Satzzeichen zu berücksichtigen. So darf zum Beispiel ein Bindestrich nicht am Anfang einer neuen Zeile stehen.

Ich behaupte nun nicht, dass eine Kata-Lösung all dies und mehr vorhersehen sollte. Keinesfalls! Die Lösung soll ausschließlich den aktuellen Anforderungen genügen. Auch wenn meine Lösungsskizze oben Silben erwähnt, würde ich also keine echte Silbenerkennung implementieren, sondern den Umbruch bei jedem Buchstaben zulassen.

Dennoch ist ja eines immer wieder so überraschend wie Weihnachten: dass sich Software über die Zeit ändert.

Deshalb sollten wir Vorkehrungen treffen, damit uns das leicht fällt.

Für mich bestehen solche Vorkehrungen mindestens darin, die Aspekte des Lösungsansatzes klar im Code sichtbar zu machen. Und genau das geschieht eben nicht in den ersten beiden obigen Lösungen und nur bedingt bei Uncle Bob.

Wir können Aspekte in Methoden oder Klassen verpacken. Je nach Umfang. Das ist doch kein Hexenwerk. Und wir können auch noch die fundamentalen Aspekte Integration und Operation trennen.

Dann würden unsere Lösungen lesbarer, glaube ich.

TDD is not enough

Noch ein Wort zur Ursache der Unlesbarkeit. TDD ist da sicherlich nicht schuld. TDD ist ne gute Sache. Soll man im Coding Dojo gerne üben.

Aber man sollte eben die Heilsbringererwartungen nicht zu hoch schrauben. TDD dreht sich am Ende doch “nur” ums Testen. Auch wenn es red – green – refactor heißt, so führt das eben nicht automatisch zu lesbarem, evolvierbarem Code.

Eine gut priorisierte Liste von Testfällen plus red – green liefert Code, der in kleinen Schritten hin zu einer funktionalen Lösung wächst. Die Codeabdeckung wird groß sein. Das halte ich für das erwartbare Ergebnis, sozusagen das kleinste garantierte Resultat von TDD.

Der Rest jedoch… der hat eben nichts mit TDD zu tun. Verständlichkeit, Evolvierbarkeit, punktgenaues Testen (also Unit Tests statt Integrationstests [2])… all das ist optional bei TDD. Es entsteht nicht zwingend. Das ist meine Erfahrung aus vielen Coding Dojos und zeigt sich auch an den obigen Codebeispielen.

Damit behaupte ich nicht, dass das nie entstehen würde. Ich sage nur, das entsteht nicht durch TDD, sondern durch Refactoring-Mühe und –Kompetenz.

Refactoring mit TDD gleichzusetzen, wäre jedoch falsch. Refactoring ist eine unabhängige Disziplin. Deshalb sage ich, Verständlichkeit und Evolvierbarkeit entstehen eben nicht durch TDD. Und wenn man in einem Coding Dojo TDD übt, dann übt man de facto eben eher nicht Refactoring. Das wäre auch eine Überlastung für die Beteiligten, ein Verstoß gegen das SRP, sozusagen. Mit der Definition von Tests, mit dem red-green-Rhythmus, mit KISS bei der Implementation, mit all dem haben die Beteiligten schon genug Mühe. Wenn Refactoring da nicht quasi im Stammhirn verankert ist, also schon als Reflex existiert, dann findet Refactoring nur auf Alibiebene statt, da wo es einem schlicht ins Auge springt, weil so deutliche Anti-Muster im Code zu sehen sind.

Was dabei aber eher nicht entsteht, das ist Aspekttrennung. Und schon gar nicht entstehen getrennte Tests für Aspekte.

Indem ich das so “anprangere” will ich nicht die Praxis der Coding Dojos herabsetzen. Mir geht es nur um realistischere Erwartungen. Die Ergebnisse sind schlicht und einfach Ergebnisse der grundlegenden Wahrheit: you always get what you measure. Denn was bei TDD “gemessen” wird, worauf das Augenmerk liegt, das sind die Tests. Es heißt ja “Test-Driven Development” und nicht “Design-by Testing” oder so. Tests stehen im Vordergrund und im Mittelpunkt. Und die kriegt man. Alle sind zufrieden, wenn sie im red-green-Rhythmus arbeiten und schrittweise die Lösung hinkriegen. Und auf dem nächsten Level dann noch bei jedem green in ein VCS einchecken.

Das ist alles super. Nur eben auch nicht mehr. Es kommen Lösungen heraus, die testabgedeckt sind. Wunderbar. Man merkt ihnen an, dass sie stolz geschrieben wurden. Ans Lesen und spätere Verändern wurde dabei wenig Gedanke verschwendet. Während des Coding Dojos ist ja der ganze Kontext auch in allen Köpfen, die Lösung entsteht vor aller Augen. Niemand hat ein Problem mit der Verständlichkeit. Noch nicht.

Deshalb erhärtet sich bei mir weiter der Verdacht: TDD is not enough. Wenn wir das sehr löbliche Üben auf TDD beschränken, dann tun wir zuwenig.

Dass es darauf beschränkt ist, finde ich ja verständlich. red – green + KISS ist so schön greifbar. Da sieht man den Erfolg. Und KISS ist ganz im Sinne des alten Eleganzgedankens.

Leider sind das Ergebnis dann lokale Optima. Knapper testabgedeckter funktionaler Code ist im Sinne dieses Vorgehens optimiert – aber hat nicht das Big Picture im Blick. Zu dem gehören für mich Verständlichkeit und Evolvierbarkeit.

In diesem Sinne ist auch die Freude aller zu verstehen, wenn die Lösung kleiner wird. Weniger LOC sind gut. Als sei darauf ein Preis ausgelobt.

Es gilt jedoch nicht Verständlichkeit == Kürze. Verständlichkeit mag mit Kürze korrelieren, deshalb ist jedoch der kürzeste Code nicht automatisch der verständlichste. Für mich darf also etwas “Fluff” in einer Lösung sein, gar etwas Redundanz, wenn dadurch Verständlichkeit und Evolvierbarkeit steigen.

Versuchen Sie es doch mal. Einfach so als Experiment. Beim nächsten Coding Dojo könnten Sie sich bewusst mal die Frage stellen, welche Aspekte Sie erkennen – und dann überlegen Sie, wie Sie die in der Lösung deutlich sichtbar machen. Darauf aufbauen könnten Sie nach einer Formulierung der Lösung suchen, die ihre Funktionsweise leicht erkennbar macht. Wie arbeiten die Aspekte einer Lösung zusammen, um die Anforderungen zu erfüllen?

Fußnoten

[1] Dass Testfälle schon ausgearbeitet vorlagen, fand ich allerdings nicht so schön. Das schien mir dem Lerneffekt eher abträglich. Denn darin liegt ja eine der zentralen Herausforderungen bei TDD: relevante Testfälle überhaupt erst einmal erkennen und dann noch in eine Reihenfolge bringen, die ein kleinschrittiges Wachstum des Codes begünstigt.

[2] Selbst bei Uncle Bob gibt es nur Tests für wrap(). Die herausgelöste Funktion breakline() wird nicht separat getestet, obwohl sie ein Arbeitspferd der Lösung darstellt. Damit sind alle Tests seiner Lösung Integrationstests – auch wenn sie auf einem höheren Abstraktionsniveau die Unit wrap() testen.

Sonntag, 14. Oktober 2012

Flow Operationen automatisch registrieren

Flow Designs zu implementieren, ist nun einen ganzen Schritt einfacher geworden. Endlich habe ich nämlich Zeit gefunden, die Flow Runtime mit etwas mehr "Intelligenz" zu versehen.

Bisher bestand die Implementation eines Flow Designs aus drei Teilen:

  • Flow
  • Operationen
  • Bindung

Als Beispiel für einen Flow hier der für die Umformatierung einer Datei in ganz einfacher Weise. Ihre Zeilen sollen auf eine neue maximale Länge gebracht werden:

/
.run, Zeilen_lesen
Zeilen_lesen, Umbrechen
Umbrechen, Zeilen_schreiben

Die Operationen sind Methoden, die wohl ganz angemessen auf zwei Klassen verteilt werden können:

class Textdatei {
  private string _dateiname;

  public string[] Zeilen_lesen(string dateiname) {…}

  public void Zeilen_schreiben(string[] zeilen) {…}

class Formatierung {
  public static string[] Umbrechen(string[] zeilen) {…}

Gebunden werden die Operationen an den Flow über die Konfiguration der Flow Runtime:

var td = new Textdatei();

var config = new FlowRuntimeConfiguration()
                   .AddStreamsFrom("umbrechen.root.flow", Assembly.GetExecutingAssembly())

                   .AddFunc<string, string[]>("Zeilen_lesen", td.Zeilen_lesen)
                   .AddAction<string[]>("Zeilen_schreiben", td.Zeilen_schreiben)
                   .AddFunc<string[], string[]>("Umbrechen", Formatierung.Umbrechen);

using(var fr = new FlowRuntime(config)) {
  fr.Process(".run", args[0]);
  fr.WaitForResult();
}

Das funktioniert wunderbar. Doch es ist bei der Konfiguration umständlich. Die stellt nämlich auch einen Widerspruch zu DRY dar. In ihr werden ja Dinge wiederholt, die anderswo schon stehen. Vor allem ist es aber mühsam, in der Konfiguration die Signaturen der Operationsmethoden anzugeben. Das macht die dynamische Natur der kleinen Flow DSL quasi nutzlos.

Damit ist nun Schluss!

Jetzt ist die Konfiguration viel, viel einfacher. Sie schrumpft im Grunde auf zwei Zeilen Code, die sich im Verlauf der Evolution einer Anwendung kaum mehr ändern müssen:

var config = new FlowRuntimeConfiguration()
                   .AddStreamsFrom("umbrechen.root.flow", Assembly.GetExecutingAssembly())
                   .AddOperations(new AssemblyCrawler(Assembly.GetExecutingAssembly()));

Eine Zeile liest den Flow, eine weitere Zeile lädt alle Operationen.

Der AssemblyCrawler durchläuft alle Typen der ihm übergebenen Assemblies und schaut, ob die Operationsmethoden definieren. Wenn ja, registriert er sie automatisch. Berücksichtigt werden Klassen, die mit einem von drei Attributen markiert sind:

[InstanceOperations]
class Textdatei { … }

[StaticOperations]
class Formatierung { … }

Klassen, die mit [StaticOperations] gekennzeichnet sind, werden auf statische Methoden überprüft, Klassen, die mit [InstanceOperations] markiert sind, werden zuerst instanziert, um dann auf Instanzmethoden überprüft zu werden. Und schließlich gibt es noch [EventBasedComponent], um Instanzen zu registrieren, die Output über Events hinausgeben.

Relevant als Operationen sind in jedem Fall nur Methoden, die den Signaturen entsprechen, die auch über AddAction<>() und AddFunc<>() registriert werden können. Einen Unterschied gibt es dabei jedoch: Output-Ports, die über Continuations definiert sind, haben nicht mehr die Namen ".out0" und ".out1", sondern die der Continuation-Parameter.

Der Crawler macht die Registrierung aller Operationen in den übergebenen Assemblies sehr einfach. Wer jedoch etwas mehr Kontrolle haben möchte, kann mit Attributen markierte Klassen auch von Hand registrieren:

var config = new FlowRuntimeConfiguration()
                   .AddStreamsFrom("umbrechen.root.flow", Assembly.GetExecutingAssembly())
                   .AddInstanceOperations(new Textdatei())

                   .AddStaticOperations(typeof(Formatierung)); 

Ich habe jetzt einige Tage mit dieser Form der einfachen Konfiguration gearbeitet… und möchte sie nicht mehr missen. Jetzt endlich kann ich mich wirklich auf Flow und Operationen konzentrieren. Jetzt endlich löst die Flow Runtime ihr Versprechen ein, simpler und gleichzeitig leistungsfähiger als EBC-Verdrahtung zu sein.

Hier geht's zum Download der aktuellen Flow Runtime NPantaRhei.

Sonntag, 7. Oktober 2012

Was andere schon richtig machen - Halden vermeiden


Das ist Wim Wenders. Er ist einer der bekanntesten zeitgenössischen deutschen Regisseure mit Weltruf. Von ihm können wir als Softwareentwickler etwas lernen.

Als Regisseur ist es seine Aufgabe, gewisse Ziele zu erreichen: Er hat ein inhaltliches/künstlerisches Ziel, er hat ein Terminziel und er hat natürlich auch ein Budgetziel. Gerade Termin und Geld zwingen dazu, die Produktion genau zu planen. Das beginnt beim Drehbuch, denn ohne Drehbuch ist ja ungewiss, was an Drehorten, Schauspielern, Bühnbild und schließlich Zeit und Geld überhaupt nötig wäre. Auf dessen Basis entsteht dann ein Drehplan. Der sagt, was wann wie wo genau zu drehen ist, so dass die kosten minimal sind. Was im Drehbuch wie in einer Theaterstück in einer für die Geschichte passenden Reihenfolge steht, wird dafür in Teile zerlegt und neu gemischt. Szenen am selben Ort zu unterschiedlichen Zeiten im Film, werden zusammengelegt, so dass der Drehort nur einmal besucht werden muss. Und was am Ende passiert, wird an den Anfang gelegt. Der Dreh ist mithin nicht chronologisch.

Das bedeutet, etwas das früh gedreht wird, aber erst spät im Film auftaucht, muss 100% so gedreht werden, dass es später passt. Der Regisseur muss also genau wissen und dem ganzen Team klar machen, was vorher passiert sein wird, damit die früh gedrehten späteren Szenen auch brauchbar sind. Sie liegen dann nach der Aufnahme auf Halde. Nicht nur steckt in ihnen jetzt Kapital, sie zwingen den Rest des Drehs auch in eine Form, der sie anschlussfähig macht.

Der nicht-chronologische Dreh ist damit eine lokale Optimierung im Sinne des Geld-/Zeitbudgets - und stellt ansonsten nur eine Belastung dar: Die Continuity steht vor einer großen Herausforderung, um visuelle Sprünge zwischen aneinander geschnittenen Einstellungen zu vermeiden, die mit großem Abstand gedreht wurden. Die Schauspieler stehen vor großen Herausforderungen, da sie Präsenz in ihren Rollen "auf Zuruf" ganz ohne Kontext herstellen müssen. Der Regisseur steht vor der großen Herausforderungen, das Ganze in seinem Zusammenhang und seiner Entfaltung allen Beteiligten immer wieder präsent zu machen, auch wenn jeden Tag nur beliebige Ausschnitte gedreht werden.

Als Resultat entsteht eine wachsende Halde von Takes, deren Qualität im Rahmen eines Ganzen erst im Schnitt deutlich wird. Nicht nur wird sich dann erst erweisen, ob alles so anschlussfähig ist, wie geplant.

Insofern kann denn auch das Drehbuch als Halde angesehen werden. Es ist ein Plan für die Herstellung einer Wirkung beim Zuschauer. Ob der aufgeht, zeigt sich ebenfalls erst im Schnitt.
Monatelang wird Geld in ein Drehbuch investiert, ohne dass man weiß, ob das Endresultat dem künstlerischen Anspruch gerecht wird. Wochenlang wird dann Geld in einen Dreh investiert, ohne dass man weiß, ob der in Summe das Drehbuch angemessen umsetzt und im Schnitt das gewünschte Ergebnis entstehen kann.

Während seiner ersten 10 Filme hatte Wim Wenders versucht, diese Unsicherheiten durch viel Planung zu kompensieren. Er hatte Drehbücher nicht nur umgesetzt, sondern auch geschrieben. Nicht umsonst war er Mitgründer des Filmverlags der Autoren. Damit vermied er Reibungsverluste zwischen Regisseur und Drehbuchautor. Seine Planung begann also schon bei der Idee.

Während des Drehs dann hatte er - wie er heute während eines Colloquiumgesprächs an der Hamburger Hochschule für Musik und Theater berichtete - alles genau vorausgeplant. Jede Einstellung, jeder Auftritt, jeder Abgang, alles hatte er spätestens am Vorabend eines Drehs genaustens ausgetüftelt. Damit sollten Reibungsverluste zwischen Schauspielern und ihm oder auch der Technik während des Drehs reduziert werden. Es galt ja, die knappe Drehzeit bestmöglichst zu nutzen.

So war das. Bis zum Film "Paris, Texas".

Da machte er es anders. Motiviert hatte ihn seine Erfahrung mit der Inszenierung eines Theaterstücks. Denn im Theater wird anders gearbeitet. Es gibt nur eine Einstellung, es gibt nur eine chronologische Darstellung. Und hat das Stück begonnen, muss der Regisseur auf die Schauspieler vertrauen. Während der ganzen Vorstellung kann er nicht mehr eingreifen.

"Paris, Texas" hat Wim Wenders dann anders gemacht.

Das Drehbuch war nur halb fertig bei Drehbeginn. (Eine zweite Hälfte existierte nur für die Geldgeber. Sie war nie für die Produktion bestimmt, sondern ein Fake.)

Und der Dreh verlief streng chronologisch.

Das Ergebnis? Ein Film der von der Kritik gelobt und mit der Goldenen Palme in Cannes ausgezeichnet. Das Ergebnis lässt sich also sehen - und das ganz ohne Aufhäufung von Halden. Für Wim Wenders ist klar, dass der Film seine Qualität nur erhalten konnte, weil eben nicht zwanghaft ein Ende vor Drehbeginn vorgedacht worden war und sich die Schauspieler mit ihren Figuren entwickeln konnten. Das Geheimnis für das gute Ergebnis, ja, das bessere Ergebnis als bei vorhergehenden Filmen besteht für ihn mithin in der Vermeidung von Halden. Er hat in nichts investiert, von dem er nicht wusste, ob es so später gebraucht werden würde. Er hat sich mit keiner Halde von Vorproduziertem die Möglichkeit zur Reaktion auf Entwicklungen während des Drehs eingeschränkt.

Mir scheint das eine kopierenswerte Haltung. Auch in der Softwareentwicklung sollten wir es vermeiden, ein ausgefeiltes Drehbuch von Anfang bis Schluss zu schreiben. Vor Beginn der Entwicklung zunächst ein Backlog satt zu füllen, versenkt Geld in Anforderungen, von denen nicht sicher ist, wann, wie und ob sie später umgesetzt werden sollten, ob sie einem Anwender wirklich den intendierten Nutzen bieten. Ein pralles Backlog schränkt durch die Investitionen auch die spätere Reaktionsmöglichkeit ein. Denn was im Backlog steht, muss ja umgesetzt werden. Immerhin steckt da ja einige Mühe drin. Die darf nicht umsonst sein. Wenn in Wochen oder Monaten Neues auftaucht, dann hat sich das hinten anzustellen. Mit einem ausführlichen Backlog gilt das Prinzip "First come, first serve".

Ebenso sollte die Softwareentwicklung vermeiden, Code auf Halde zu produzieren. Der nicht-chronologischen Drehreihenfolge beim Film entspricht eine Entwicklungsreihenfolge, die sich von Befindlichkeiten der Softwareentwicklung leiten lässt. Wenn die meint, es müsse zuerst eine Infrastruktur aufgesetzt werden, ein Persistenzframework geschrieben, ein Security-API entwickelt werden, bevor auch nur ein Anwender etwas in die Hand bekommt… dann produziert sie Code auf Halde. Von dem weiß auch niemand, wann, wie, ob er mal Nutzen entfalten wird.

Zum Glück vertritt die Agilitätsbewegung diese Sichtweise schon länger. Sie ermahnt, Code nicht auf Halde zu produzieren, sondern nur in nützlichen Inkrementen. Sie ermahnt, nicht zuviel in Vorabplanung von Anforderungen und Entwürfen zu investieren. In der Projektrealität ist das allerdings immer noch nicht überall angekommen. Deshalb finde ich es motivierend, aus ganz anderer Richtung davon zu hören, wie die Reduktion von Halden erfolgssteigernd wirken kann. Dass selbst die Filmproduktion davon profitiert… Wer hätte das gedacht?

Vielen Dank, Wim Wenders, für diese Plauderei aus dem Nähkästchen.


PS: Woody Allen hat übrigens auch erkannt, dass Drehbuchhalde und Takehalde sich abträglich auf das künstlerische Ziel auswirken können. Er stellt sie zwar noch komplett her - doch er erlaubt sich spätere Korrekturen. In den Verträgen seiner Schauspieler steht, dass sie - wenn ich es recht erinnere - bis zu einem halben Jahr nach Drehschluss noch für Nachdrehs zur Verfügung stehen müssen. Für Woody Allen kann sich einfach im Schnitt herausstellen, dass Haldenmaterial in seiner Qualität ungenügend ist. Dann bessert er aufgrund der gewonnenen Erkenntnisse einfach nach.
Das scheint mir auch in Linie mit der Agilität zu sein. Woody Allen hat erkannt, dass sich Ziel und/oder Weg mit der Zeit durch die Produktion verändern können. Das überrascht ihn nicht mehr, sondern er weiß es und trifft also vertragliche Vorkehrungen.

Donnerstag, 4. Oktober 2012

Gesucht: dreckige Realität

In einem Blogartikel hat Matthias Bohlen seinen Ansatz beschrieben, die Form eines Softwaresystems zu entwerfen. Leider hat mich die Lektüre unbefriedigt zurückgelassen.

Dass Matthias einen anderen Ansatz verfolgt als ich, erklärt das allerdings nicht. Warum sollte mich das auch stören? Der Entwurf hat mich ja nicht in frustriert-sprachloses Erstaunen versetzt. Also erwächst aus der Andersartigkeit seines Ansatzes eher energievolle Spannung. Ich werde seinem Entwurf auch einmal meinen gegenüberstellen. Dabei kann ich wieder für mich etwas lernen. Sie können für sich vergleichen. Und vielleicht ergibt sich daraus auch noch ein konstruktives Gespräch.

Nein, der Ansatz ist es nicht, der mich frustriert hat. Es war eher die Aufgabe. Aber auch wiederum nicht konkret die Bankendomäne, sondern die, hm, Präsentation und der Scope. Beides war so typisch. Nämlich typisch – sorry to say, Matthias – flach.

Ich habe es schon so oft gesehen in Büchern und Artikeln: Da wird eine Technologie oder Methode vorgestellt anhand eines Beispiels, bei dem es einfach funktionieren muss.

Bis zu einem gewissen Grad ist das auch legitim oder sogar nicht zu vermeiden. Auch ich habe schon so Technologien oder Methoden vorgestellt; manchmal liegt es an der Zeit, die zur Verfügung steht. Gerade deshalb bin ich da aber vielleicht auch sensibel. Ich möcht es besser machen für Sie und auch für mich.

Das Problem bei einer flachen Aufgabenstellung, bei Anforderungen, die eine Technologie oder Methode quasi zum Selbstgänger machen ist schlicht, dass man sich in die Tasche lügt. Das kann dem Präsentierenden selbst nicht gefallen. Und es ist natürlich unschön für die Leserschaft.

Ja, das ist es, das hat mich bei Matthias Lösung gestört. Das unselige, weil schon so oft zitierte Überweisungsszenario ist so trivial, dass sich damit ja fast jede Methode erfolgreich demonstrieren lässt. Ein “normal” objektorientierter Ansatz hätte dabei genauso sinnvoll dargestellt werden können wir ein prozeduraler oder ein funktionaler. Ich kann jedenfalls nicht erkennen, dass Matthias’ DCI-Ansatz einem anderen überlegen ist.

Damit will ich keine Aussage über seinen Ansatz machen. Bei der Größenordnung an Aufgabe macht er für mich schlicht keinen deutlichen Unterschied. Er funktioniert. Klar. So wie der “normale” objektorientierte Ansatz auch vorher funktioniert hat.

Was wir bei Matthias sehen ist eine Momentaufnahme eines kleinen Szenarios. Und wenn wir in ein Buch zur Objektorientierung oder zu DDD schauen, dann sehen wir dort auch Momentaufnahmen. Da hat jemand lange an Code gearbeitet, ihn fein ziseliert – und präsentiert das kunstvolle Ergebnis.

Nur ist so leider nicht die Realität. Die ist dreckig und hektisch und nicht so strukturiert in ihren Anforderungen. Außerdem sind die deutlich umfänglicher. Da gibt es Seiteneffekte. Da müssen nicht-funktionale Anforderungen berücksichtigt werden. Und da verändert sich so einiges über die Zeit.

Wir brauchen mehr Demonstrationen von Technologien und Methoden, die es damit aufnehmen. Auch und gerade, wenn es um Methoden geht. Denn ob eine Technologie funktioniert oder nicht, das lässt sich leicht selbst feststellen. Zur Laufzeit gibt es gewöhnlich blitzschnell Feedback. Doch ob eine Methode funktioniert bzw. ob man sie richtig anwendet, das ist viel schwieriger festzustellen. Womöglich stellt sich das erst nach Wochen oder Monaten heraus, wenn man sich damit in eine Ecke gepinselt hat.

Matthias’ Lösung kommt mir insofern “erschlichen” vor. Das Problem ist so abstrakt, isoliert dargestellt, dass sich quasi alles wie von selbst fügt. Erklärungen sind kaum nötig. Jeder nickt sofort, wenn da die Rollen aufgetischt werden… und jeder hält es dann für ganz natürlich, dass ein Konto das andere auffordert etwas zu tun.

Aber das kann doch nicht Matthias’ Ernst sein. In welcher Bankenwelt sollen denn Konten direkt miteinander sprechen? Auch wenn ich kein Banker bin, hat das für mich keinen Rückhalt in der Realität der Bankdomäne. Deshalb scheinen mir die Rollen Quellkonto und Zielkonto “erschlichen”. Wie bei einer petitio principii, wo das zu Beweisende vorausgesetzt wird. Oder so ähnlich wie die Antwort auf die Frage aus Ottos Quizparodie, wer denn der berühmte Maler in Rembrandts Haus sei.

Aber nochmal: Damit will ich Matthias´ Ansatz nicht per se kritisieren, sondern nur sagen, dass ich seinen Wert anhand des flachen Beispiels noch nicht erkennen kann. Wo die Anwendungsfälle so klein sind, dass Objekte und Rollen auf der Hand liegen, kann ich jede Methode rechtfertigen, die Objekte und Rollen favorisiert.

Um mich zu überzeugen braucht es also etwas Größeres und Dreckigeres. Etwas, das nicht schon für eine Methode analysegerecht kleingehackt ist.

Aus diesem Grund habe ich schon vor längerer Zeit in Anlehnung an die kleinen Code Katas größere Application Katas beschrieben. Einige finden sich hier: http://clean-code-advisors.com/ressourcen/application-katas. Technologisch sollten sie keine Herausforderungen darstellen. Aber methodisch kann man sich an ihnen abarbeiten. Deshalb liegen sie in mehreren Iterationen vor.

Wie passt darauf der Ansatz von Matthias? Wie flott segelt er durch diese Anforderungen? Wie leicht lässt sich eine DCI-Struktur über die Iterationen verstehen und verändern?

Diese Fragen gelten aber natürlich nicht nur für Matthias. Jeder, der eine Methode für die Softwareentwicklung im Ärmel hat, kann sich daran probieren. “Normale” Objektorientierung genauso wie Funktionale Programmierung oder der de facto Stil eines Teams. Ich selbst demonstriere Flow-Design immer wieder an solchen Beispielen im Rahmen von Artikeln oder im Seminar “Agile Architektur” der Clean Code Developer Akademie.

Doch die AppKatas sind weit weg von Matthias’ Aufgabenstellung. Da mag es schwer fallen zu sehen, was ich mit “mehr dreckige Realität” meine. Deshalb hier meine Version seines Überweisungsszenarios, als ganze Anwendung mit einem UI und persistenten Daten.

Application Kata - Banküberweisung

Auch das ist natürlich immer noch eine Spielzeugvariante dessen, was in Banken tatsächlich läuft. Aber es sind zumindest mehr Facetten darin enthalten, so dass es schwieriger wird, seinen Entwurfsweg zu finden. Die Entscheidungen sind nicht vorgekaut. Ein wie immer geartetes Modell – um das es ja beim Entwurf geht – liegt nicht auf der Hand.

Also, das ist eine Art von Herausforderung, wie ich sie häufiger formuliert und dann angegangen sehen möchte. Alles andere ist Spielkram.

 

PS: Wer sich beklagen möchte, in der Aufgabenstellung seien zu viele Dinge nicht ausformuliert und deshalb sei es schwierig zu entwerfen, der kann gern in den Kommentaren hier Fragen stellen. Dann konkretisiere ich als “Kunde” :-)

Mittwoch, 26. September 2012

Form Follows Feasibility

Code verändern, ist schwer. Wer kämpft sich schon gern durch einen Dschungel aus Legacy Code? Viel schöner ist die Entwicklung auf einer grünen Wiese. Code neu schreiben, ist demgegenüber leicht.

Wenn es in Projekten knirscht, weil die Last der Brownfield Codes erdrückend ist, ist Neuentwicklung allerdings nur selten eine Option. Der Aufwand wäre gewaltig; dafür gibt es kein Geld und keine Zeit.

Aus dem Morast scheint dann nur der trübe Weg langwieriger Refaktorisierungen zu führen. Wochen und Monate arbeitet sich das Team daran ab. Spaß macht das nicht. Motvierend für andere ist das auch nicht. Wenn die Codebasis an einem solchen Punkt ist, leidet das Produkt unter doppelt schlechter Qualität: nicht nur die des Codes lässt zu wünschen übrig, sehr wahrscheinlich ist auch die Qualität des Teams – oder zumindest seine Attraktivität – suboptimal.

Natürlich kann Software nicht dauernd neu geschrieben werden. Aber wenn es soviel leichter ist, Legacy Code neu zu schreiben, statt ihn zu ändern, warum gibt es dann kein Bemühen, der Leichtigkeit des Greenfield näher zu kommen?

Ich meine, darum sollte sich der Entwurf bemühen. Die Softwarearchitektur sollte die Machbarkeit der Neuentwicklung als nicht-funktionale Anforderung sehen, ohne die Nachhaltigkeit nicht zu erreichen ist.

Zunächst einmal wird Code natürlich zum Bug Fixing und für neue Anforderungen erweitert. Das geht auch eine ganze Zeit gut. Aber wie lange? Niemand weiß das genau. “Es kommt halt darauf an…” Das ist wahr – nur führt diese weise Haltung gewöhnlich eben zu dem monolithischen Code, der dann nicht mehr mit vertretbarem Aufwand veränderbar ist.

Deshalb glaube ich, dass die Entscheidung, wie lange an Code rumgeschraubt werden sollte, weniger weise, weniger emotional ausfallen sollte, sondern viel pragmatischer und regelhafter.

Hier mein Vorschlag:

  1. Eine Codebasis sollte in Komponenten von max. 10.000 LOC aufgeteilt werden.
  2. Eine Codebasis sollte in Services von max. 60.000 LOC aufgeteilt werden.

Unter Komponenten verstehe ich hier binäre Codeeinheiten mit separatem Kontrakt auf derselben Plattform. Komponenten machen also keinen Aufwand bei der Kommunikation untereinander.

Unter Services hingegen verstehe ich Gruppen von Komponenten mit einem separatem gemeinschaftlichen Kontrakt – allerdings auf u.U. unterschiedlichen Plattformen. Die Kommunikation zwischen Services ist also kein no-brainer mehr.

Die Aufteilung in Komponenten und Services dieser Größe schlage ich vor, weil sich für mich damit Grenzen machbarer Neuentwicklung ergeben.

Komponenten neu entwickeln

Eine Komponente von 10K LOC [1] kann ein Team in einem Monat [2] neu entwickeln, wenn es hart auf hart kommt. Das scheint mir ein überschaubarer Zeitraum aus Sicht des Managements. Eine Entscheidung dafür sollte das Geschäft nicht aufs Spiel setzen.

Eine Software wird von vornherein komponentenorientiert entwickelt. Das Team beobachtet das Wachstum der einzelnen Komponenten. Seien wir ehrlich: dabei kontinuierlich den Komponentencode sauber zu halten, ist eher eine Idealvorstellung. Kleinere Refaktorisierungen werden vorgenommen – aber wenn größere notwendig werden, ist dafür eher keine Zeit. Die werden vertagt… Also kommt der Tag, an dem eine Komponente 10K LOC umfasst und eigentlich so richtig refaktorisiert werden müsste.

An dem Punkt entscheidet sich das Team nun jedoch für eine komplette Neuentwicklung. Statt mühselig Code zu säubern, wird auf der grünen Wiese neu angefangen – im Rahmen des Komponentenkontrakts. Der resultierende Code ist dann nicht nur sauber, sondern rein ;-) Damit meine ich, dass Refaktorisierung nur zu Clean Code zweiter Wahl führt, weil sie sich im Rahmen des Existierende bewegt. Das schränkt die Kreativität ein, das behindert die Innovationsmöglichkeiten. Ein Beginn auf der grünen Wiese hingegen ist frei von solchen Altlasten. Da ist alles erlaubt und denkbar – solange der ursprüngliche Kontrakt eingehalten wird.

Services neu entwickeln

Einen Service von 60K LOC kann ein Team in 6 Monaten neu entwickeln, wenn es hart auf hart kommt. Das ist kein ganz kurzer Zeitraum, das entscheidet man nicht zwischen Tür und Angel – doch es ist immer noch viel überschaubarer als die Neuentwicklung einer kompletten Anwendung von 500.000+ LOC. 6 Monate sind absehbar. 6 Monate sind in vielen Teams ein Releasezyklus oder gar weniger.

Der Trick bei den Services ist nun, dass ihre Neuentwicklung noch mehr Freiheitsgrade bietet. Nicht nur kann die interne Struktur über Komponentengrenzen hinweg rein gemacht werden, nein, es kann sogar ein Plattformwechsel stattfinden. Damit ist die Bedingung für die Möglichkeit kontinuierlicher Innovation geschaffen. Denn die hängt nicht nur an den Fähigkeiten der Entwickler, sondern auch am technologischen Fortschritt.

Wer sich vor 5 Jahren einmal für Java entschieden hat, muss dann nicht bis zum Lebensende (der Software) alles in Java entwickeln. Für jeden Service kann vielmehr immer wieder neu entschieden werden, ob ein Plattformwechsel Vorteile bietet. Eine Entscheidung ist möglich, weil die Servicegrenze in der Architektur überhaupt gezogen wurden – und weil darauf geachtet wurde, die Größe im Rahmen machbarer Neuentwicklung zu halten.

Günstig mag ein Plattformwechsel sein, wenn eine andere Plattform bessere technische Möglichkeiten bietet. Aber der Wechsel kann auch durch Erhaltung der Attraktivität der Codebasis motiviert sein. Plattformen unterliegen Moden. Um dauerhaft Entwickler für die Mitarbeit zu begeistern, mag es angezeigt sein, Plattformmoden zu folgen. Wer will denn heute eine große Cobol-, Fortran-, PHP- oder VB6-Codebasis pflegen? Durchschnittlich attraktiv sind C# oder Java oder Ruby oder Python – dahinter lauern aber schon Scala, Groovy, Clojure, Erlang, F#, JS und andere.

Nur wer “Sollbruchstellen” im Code in Form von Servicegrenzen vorsieht, hat die Chance, technologisch kontinuierlich uptodate zu bleiben.

Heute wird vielen Softwaresystemen eine solche Grenze nachträglich durch den Wunsch nach mobile applications aufgezwungen. Da entsteht plötzlich ein zusätzlicher Service in Form einer App. Da tritt nach Jahren mal wieder – gezwungenermaßen – eine neue Plattform auf den Entwicklungsplan. Da entsteht plötzlich Attraktivität. Leider sind damit aber auch viele Teams überfordert, weil sie jahrelang keine Übung gehabt haben im Plattformwechsel oder gar auch nur im Neuanfang auf einer grünen Wiese.

Fazit

Refaktorisierungen zur Herstellung und Erhaltung evolvierbarer Stukturen sind nicht überflüssig – aber überbewertet, würde ich mal sagen. Viel öfter sollte Neuentwicklung statt Refaktorisierung gedacht werden. Dazu bedarf es aber klarer Grenzen, in denen Neuentwicklung auch machbar ist. Ich habe hier mal zwei solche Grenzen unterschiedlicher Granularität vorgeschlagen. Ob die bei diesen LOC und diesen Zeiträumen verlaufen sollten, sei dahingestellt. Da mag sich jedes Team seine eigenen Grenzen setzen. Dass es jedoch Komponenten- und Servicegrenzen geben sollte, da bin ich sicher. Wir brauchen diese beiden Horizonte.

Der Servicehorizont liegt mir besonders am Herzen. Denn einer der größten Übelstände der Softwareentwicklung scheint mir derzeit die Erstarrung durch Konsolidierung und Homogenisierung. Dem muss entgegen gewirkt werden. Vielfalt muss möglich sein. Denn Vielfalt bedeutet natürliche Lebendigkeit. Nur so ist Innovation kein Kraftakt alle Jubeljahre, sondern jederzeit möglich.

Die Herausforderung für die Softwarearchitektur besteht also darin, die Form der Software so zu entwerfen, dass auch echte Reinigungen machbar sind.

Fußnoten

[1] Die LOC habe ich mal über den Daumen für ein Team von 5 Entwicklern berechnet, das jeden Tag pro Entwickler im Schnitt 100 LOC Produktionscode herstellt. Bei 20 Arbeitstagen/Monat ergibt das 10.000 LOC.

[2] Den Monat Aufwand für eine machbare Neuentwicklung einer Komponente bzw. die 6 Monate für einen Service meine ich nicht wörtlich. Was ein Unternehmen für machbar bei Neuentwicklungen hält, soll es selbst entscheiden. Mir scheint ein Monat für eine Komponente jedoch nicht ganz unrealistisch. Und ein halbes Jahr für die Möglichkeit eines Plattformwechsels, hören sich auch nicht so schlecht an, oder? Wenn möglich, können Services aber natürlich auch kleiner gehalten werden. 3 Monate für eine Neuentwicklung wären besser als 6.

Dienstag, 25. September 2012

Vorsicht Co-Evolution!

In zwei Teams habe ich es in der letzten Woche gesehen: Dass die Entwicklung eines Teams behindert wird durch die Software. Und wenn ich an andere Teams denke, dann kann ich das selbe Muster sehen, glaube ich.

Ein Team beginnt die Entwicklung einer Software. Die Software bekommt dann eine Form, wie sie das Team denken kann und die Projektkultur zulässt. Da die Architektur- oder allgemeiner Entwurfskompetenz leider, leider im Allgemeinen recht schlecht ausgebildet ist, entsteht keine gute Struktur im Sinne von Evolvierbarkeit. Am Anfang ist das allerdings noch nicht schlimm.

Über die Zeit wird das natürlich nicht besser. Der Druck im Projekt steigt tendenziell, was gewöhnlich zu einer Erosion des wie auch immer ausgeprägten Anspruchs an Strukturqualität führt.

Wo die Strukturen eines Softwaresystems nun aber nicht ausgeprägt und auf Evolvierbarkeit angelegt sind, ist es schwer, sich darin zurecht zu finden. Je schlechter sich die Struktur entwickelt und je umfangreicher solche monolithische Codebasis wird, desto schwerer wird es, neue Teammitglieder einzuarbeiten. Immer mehr Domänen-Know-How ist nötig, immer mehr Erfahrung mit der Codebasis. In einem wachsenden, eng verwobenen Ganzen schrumpfen die Inseln der Unabhängigkeit, die sich separat verstehen ließen.

Team und Code stehen mithin in einer Co-Evolution. Das Team beeinflusst die Codestruktur. Und die Codestruktur beeinflusst das Team. Das ist mir jetzt klar geworden.

Dass das Team für die Codestruktur, für die Evolvierbarkeit verantwortlich ist, liegt auf der Hand. Es gibt Ansätze, mit denen sich die Evolvierbarkeit verbessern lässt. Die einzuführen kostet natürlich etwas Mühe. Das geht nicht von heute auf morgen. Aber es ist machbar. Es winkt eine längere Lebensdauer der Software bei größerer Lukrativität.

Und was, wenn man nichts für die Evolvierbarkeit tut? Na, dann wird es halt immer zäher, an ihr etwas zu verändern.

Soweit der übliche Gedankengang. Die Motivation für die Evolvierbarkeit kommt dabei aus der Codebasis. Ob und wie viel man für Evolvierbarkeit tut, hängt vom Schmerzempfinden ab. Tun Änderungen schon weh genug, dass sich die Mühe für mehr Evolvierbarkeit lohnt? In vielen Teams empfindet es das Team so – aber das Management spürt nicht das selbe.

Jetzt ist mir aber ein weiteres Symptom einer nur noch schwer evolvierbaren Codebasis klar geworden. Und dieses Symptom könnte geeignet sein, die Schmerzschwelle des Managements zu überschreiten. Meine Beobachtung ist, dass schwer evolvierbare Softwaresysteme zu schwer evolvierbaren Teams führen.

Ein Team erzeugt (unbewusst) immer Softwarestrukturen, die seinen Anspruch an die eigene Evolvierbarkeit widerspiegeln. Ich denke, auch das ist eine Ausprägung von Conway´s Law. Monolithische Software ist daher nicht nur eine Folge mangelnden Architekturverständnisses, sondern auch mangelnden Anspruchs an die Organisation.

Monolithisch gedachte Organisation führt zu monolithischer Software. Monolithische Software erhält umgekehrt die monolithische Organisation. Denn eine andere kann den Softwaremonolithen nicht weiterentwickeln.

Flexibel gedachte Organisation führt allerdings nicht automatisch zu evolvierbarer Software. Dazu muss die Kompetenz können, evolvierbare Softwarestrukturen herstellen zu können.

Und was ist eine monolithische Organisation? Eine, die auch Konstanz, auf Starre, Rigidität ausgerichtet ist. Dazu gehört das Äußere: Wie lange sind Mitarbeiter in einem Projekt? Wie fixiert ist die Organisationsstruktur? Wie groß sind die Puffer (Geld, Zeit, Motivtion)? Dazu gehört aber auch das Innere: Wie alt ist das Know-How der Mitarbeiter? Wie alt sind Tools und Technologien? Wie homogen ist die Systemlandschaft?

Als Ergebnis der Co-Evolution von Team und Software habe ich nun aktuell in zwei Fällen gesehen, dass das Team sich nicht weiterentwickeln kann. Es ist fixiert auf einen Technologiestack. Es ist fixiert auf eine große, nicht ersetzbare, sondern in ihrer chronischen Krankheit nur noch palliativ pflegbaren Codebasis. Es ist isoliert vom Markt der Softwareentwickler. Denn in so einem Team will niemand arbeiten. Wer bei Sinnen ist, lässt sich nicht auf eine große, monolithische Codebasis eingefroren in der technologischen Zeit von vor 10 Jahren ein. Ich wüsste jedenfalls nicht, wie groß die Anreize sein müssten, damit ich mich dafür erwärmen könnte. Und die faktische Schwierigkeit, Entwickler zu finden, deutet darauf hin, dass es andere genauso sehen. Das Gehalt ist ohnehin begrenzt – Schmerzensgeld gibt es also nicht genug. Und andere Nettigkeiten vom lockerem Umgangston über Konferenzbesuche bis zur Teamparty reizen offensichtlich auch nicht genug. Die fehlenden Anreize “coole Technologien” und “da lässt sich noch richtig was im Code bewegen” können dadurch nicht kompensiert werden.

Zähigkeit im Code drückt sich in Zähigkeit in der Teamentwicklung aus. Dem Management muss man also nichts von Cyclomatischer Komplexität o.ä. erzählen. Es reicht, wenn es sieht, dass die dringend benötigten Leute nicht an Bord kommen. Wenn Codequalität keinen Anreiz darstellt, etwas in der Softwareentwicklung zu verändern, dann hilft vielleicht die Aussicht, dass wenn nicht schon heute so doch über kurz oder lang das Team stillsteht. Denn dann ist weder Wachstum noch Innovation möglich.

Vorsicht also: Es gilt nicht nur, dass ein Team den Code produziert, der ihm entspricht. Darüber hinaus gilt vielmehr, dass Code zu einem Team führt, der ihm entspricht.

Wer eine Vorstellung davon hat, wie sein Team aussehen und sich entwickeln soll über 5, 10, 20 Jahre, der tut gut daran, von ihm entsprechenden Code herstellen zu lassen.