Follow my new blog

Posts mit dem Label Gesetze der Softwareentwicklung werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Gesetze der Softwareentwicklung werden angezeigt. Alle Posts anzeigen

Sonntag, 10. Juli 2011

Begrenzte Qualität – aber zackig!

Uncle Bob hat “crap code” den Kampf angesagt: “Craftsmanship over Crap” – zünftige Handwerksleistung soll für mehr Qualität in der Softwareentwicklung sorgen. Dagegen hat Nicolai Josuttis vor einigen Jahren den Gedanken geäußert, wir sollten endlich lernen, uns mit “crap code” abzufinden; er sei nicht zu vermeiden:

“So, when I say "Welcome Crappy Code", my point is not to force crappy code. My point is simply to face the reality. But, yes, you can consider my position as a view of resignation.”

Natürlich habe ich mich gegen eine solche Resignation gewendet. Nicht umsonst habe ich die Clean Code Developer Initiative mitgegründet. Ja, wir können “crappy code” aus der Welt schaffen! Wir müssen uns nur bemühen. CCD macht´s möglich.

Nun sind seitdem zwei Jahre vergangen und ich sehe die Welt ein wenig anders.

Es gibt viel Motivation in der Entwicklergemeinde, nicht länger “crap code” zu entwickeln. Die Zahl der Mitglieder in der CCD XING-Gruppe wächst ungebrochen. Das ist toll.

Ich glaube auch weiterhin daran, dass wir schlechte Qualität nicht fatalistisch hinnehmen sollten. Teams müssen besseren Code schreiben, wenn sie im Geschäft bleiben wollen. Und wir haben auch grundsätzlich die Kenntnisse, wie das geht, besseren Code zu schreiben.

Aber… ja, ich sehe da ein Aber. Der geballten Faust in der Tasche der Entwicklerschaft steht eine ökonomische und organisatorische und kognitive Macht gegenüber, die nicht zu vernachlässigen ist.

Softwarequalität (also innere Softwarequalität vor allem im Sinne von Korrektheit und Evolvierbarkeit) ist nur ein Wert unter vielen. So wie Ehrlichkeit auch nur ein Wert unter vielen ist.

Wo es aber ein Wertesystem gibt, also viele, auch noch konkurrierende Werte, da müssen Werte ausbalanciert werden. Wie in der Produktion muss das Optimum beim Ganzen eingestellt werden und nicht beim Teil.

Schon das bedeutet, dass wir nie zu optimaler innerer Qualität kommen werden. Sie wäre ein lokales Optimum, unter dem das Ganze leiden würde.

Dazu kommt, dass wir nicht einmal optimale Qualität herstellen könnten, wenn wir wollten. Wir verhalten uns in Bezug auf innere Softwarequalität nicht rational. Denn rational wäre, unsere Begrenzung durch Kosten bei ihrer Herstellung zu akzeptieren und danach abzuwägen.

Wir können nicht einmal rational entscheiden, weil neben ökonomischen und organisatorischen Gründen auch kognitive Gründe unsere Entscheidungen beschränken. Ich behaupte, dass wir nicht einmal wissen können, was die optimale Qualität wäre. Kein noch so langes Nachdenken und Diskutieren darüber würde uns zu einer eindeutigen Lösung bringen. Der Grund dafür liegt in der Volatilität der Anforderungen. In einer kleinen Serie zu den Gesetzen der Softwareentwicklung hatte ich dazu schon einmal etwas gesagt.

Es geht also nicht. Verabschieden wir uns von der Vorstellung hohe oder gar optimale innere Qualität herzustellen. Wir bewegen uns nicht im Bereich der Rationalität, sondern müssen unter Bedingungen Beschränkter Rationalität (Bounded Rationality) arbeiten.

Unser Ziel kann eingebunden in ein Netz von Werten nur eine genügend gute innere Softwarequalität sein. Wir müssen uns mit “good enough” bescheiden. Auf allen Ebenen: bei der Architektur, bei der Modellierung, bei der Implementierung. Immer kriegen wir nur “good enough” hin. Das sehe ich als Annäherung an Nicolais Standpunkt.

Also: Mehr Qualität, ja! Aber den Anspruch an die Qualität begrenzen.

Das bedeutet zum Beispiel, wir können uns das ganze Gerede über Eleganz sparen. Eleganz ist etwas für Leute mit viel Zeit. Eleganz ist Verfeinerung. Und damit gewinnt man keine Schlacht.

Ebenso können wir uns längliche Diskussionen über alle Prinzipien von Clean Code Developer sparen. Sollte die Methode auf dieser Klasse oder jener definiert sein, damit das SRP eingehalten wird? Solche Fragen müssen ohne Zweifel erörtert werden – doch am besten in einer Timebox. Wenn darüber nach 10 Minuten keine Einigung mit Argumenten erzielt werden kann, dann sollte der Code so bleiben, wie er ist. Die Zukunft wird zeigen, wer in der Diskussion Recht hatte.

Die Prinzipien und Praktiken von Clean Code Developer behalten ihren Wert – aber nun sehe ich noch eine Herausforderung, die darüber hinausgeht:

Wie kommen wir möglichst schnell zu einer “good enough” Softwarequalität? Eine Liste mit Werten und Prinzipien auf Kärtchen ist nicht genug. Gedruckt sind die nämlich geduldig. Und auch die Mahnung, man solle sich täglich um sie bemühen, ist geduldig. Im Tagesgeschäft ist das schwer. Und das nächste Refactoring ist immer weit. Und TDD ist auch nicht einfach.

Nein, ich glaube, wir müssen anders vorgehen, um schnell zu “good enough” zu kommen. Wir müssen das Schreiben der Software so verändern, dass quasi automatisch und ohne lange Diskussion “good enough” rauskommt. Alles, was nicht sofort beim Schreiben passiert, passiert nämlich tendenziell gar nicht.

Das hat TDD im Grunde schon begriffen – nur ist die Refactoring-Phase sehr unspezifisch.

Also: Wie können wir zügig begrenzte Qualität in unserer begrenzten Rationalität herstellen? Wie setzen wir uns sinnvoll Grenzen in der Entwicklung, um schlicht nicht mehr soviel falsch machen zu können?

Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat…

Sonntag, 20. März 2011

Flow-Orientation für´s Unternehmen - Teil 1, Theory of Constraints

Bei der Softwareentwicklung geht es immer um Geld. Das mag unschön klingen, ist aber nicht zu ändern. Jeder, der sein Geld mit der Entwicklung von Software verdient, muss mit seiner Software dazu beitragen, dass dieses Geld auch reinkommt. Alles, was wir in der Softwareentwicklung tun, muss sich deshalb die Frage gefallen lassen, ob es dem Ziel des Geldverdienens nützt.

Oberstes Ziel eines for-profit Unternehmens ist es, Geld heute zu verdienen und in der Zukunft.

Wenn ich an der Softwareentwicklung etwas verändern will durch Einführung der Methode ABC oder der Technologie XYZ, dann muss ich mir die Frage gefallen lassen, “Bringt das denn etwas?” D.h. ich muss zeigen, dass ABC oder XYZ einen positiven Einfluss auf das Geld hat.

Wie ich nicht nur mit den Prinzipien und Praktien der Clean Code Developer Initiative feststellen durfte, ist das nicht einfach. Entwickler finden die CCD-Bausteine meist wertvoll – das Management hingegen ist nicht so leicht zu überzeugen. “Was bringt es denn, so zu entwickeln?”

Tja… was bringt es denn? Meine persönliche Überzeugung mag noch so groß sein, ein positives Gefühl reicht nicht, um grünes Licht vom Management zu bekommen. Wer verkaufen will, der muss das also im Rahmen der Logik des Käufers tun. Ich muss mir also Gedanken darüber machen, wie es dazu kommt, dass Geld mit Software verdient wird. Erst wenn dich das verstehe, habe ich einen Ansatzpunkt für ein vernünftiges Gespräch mit einem Entscheider.

Leider habe ich kein BWL studiert. Ich bin kein MBA, kein Buchhalter, kein Controller. Meine Annäherung an die “Gelddenke” erfolgt daher aus anderer Richtung. Weil ich bei der Softwareentwicklung gerade den Hammer Flow-Orientation gefunden habe, schaue ich einfach mal, ob das Thema Geld auch ein Nagel ist, den ich damit einschlagen kann – ohne mir auf die Finger zu hauen ;-) Erstmal ein Werkzeug nehmen, das ich schon habe, bevor ich mich nach anderen umschauen, die ich noch lernen muss.

Flüssig Geld verdienen

Es geht also ums Geld verdienen. Wie hängt denn das mit Software zusammen? Ich sage mal ganz naiv, das sieht grundsätzlich so aus:

image

Softwareentwicklung stellt Software in Form von Releases her – und der Vertrieb verkauft die als “Packerl”.

Geld mit Software wird also in einem einem Fluss, einem Prozess verdient. Der hat mindestens und aus großer Flughöhe zwei Schritte. Und solange ich nicht den zweiten Schritt in den Blick nehme, kann ich eigentlich mit keinem Entscheider sprechen.

Das Problem jedes Technikers, der mit einem Manager spricht, wird an diesem Diagramm deutlich. Techniker und Manager haben unterschiedliche Perspektiven:

image

Und ich würde sogar noch weitergehen und sagen, die beiden haben nicht einmal dieselbe Vorstellung vom Gesamtsystem:

image

Und so können sie nicht zueinander kommen. Oder nur schwer. Ist doch eigentlich verständlich, oder?

Wenn ich also in einer Organisation etwas verändern will, dann tue ich gut daran…

  1. …alle Stakeholder an einen Tisch zu bringen; für CCD wären das zum Beispiel mindestens Entwickler und Entscheider.
  2. …sicherzustellen, dass die Stakeholder ein gemeinsames Bild von der Organisation haben.
  3. …dass das gemeinsame Bild in irgendeiner Form an das Ziel gekoppelt ist.

Mit einer flow-orientierten Sicht, einem möglichst vollständigen Bild des Prozesses, bei dem am Ende Geld verdient wird, scheint mir das am einfachsten möglich. Ein Flow macht klar, welche Schritte zu tun sind hin auf das Geld und wie die Zusammenhänge sind.

Geld wird mit Software verdient durch die Kooperation von Rollen. Vertrieb allein nützt nichts, Entwicklung allein nützt auch nichts. Kooperation ist nötig. Und ein Verständnis dafür, wie Produktion in solchen Prozesse auf ein Ziel hin optimiert werden kann.

Produktionsprozesse optimieren

Bevor ich meine Sicht des Softwareproduktionsprozesses konkretisiere ein kurzer Ausflug ins Allgemeine; meine Erklärung basiert dabei auf der Theory of Constraints von Eliyahu M. Goldratts, so wie ich sie derzeit verstehe.

Wie kriege ich also eigentlich aus einem Prozess das meiste raus? Wie kann ich mit einem Prozess am meisten Geld verdienen?

Hier ein allgemeiner Prozess, der in 3 Schritten etwas herstellt und an den Kunden bringt:

image

Erst wenn das “Packerl” über den Tisch zum Kunden gewandert ist, fließt Geld. “Material” [1] in Form eines Produktes fließt aus dem Prozess in den Markt und Geld fließt zurück in den Prozess bzw. die Organisation, in der er fließt. Das Geld des Marktes hält den Prozess am Leben.

Es ist ein Transformationskreislauf. “Material” wird zu Geld wird zu “Material” wird zu Geld usw. usf.

Jetzt zum Grundsatz #1 solcher Geldverdienprozesse: Geld ist erst verdient, wenn der Kunde gekauft hat.

Hört sich trivial an, wird in Diskussionen aber schnell vergessen. Der Börsenspekulant weiß: Nicht realisierte Gewinne sind keine Gewinne. Soll heißten: Von günstigen Gelegenheiten zu reden, bringt nichts. Wer sie nicht wahrnimmt, hat nichts davon. Erfolgreich ist nur, wer sie nutzt.

Auf den Geldverdienprozess angewandt bedeutet das: Ein “Packerl” nützt nichts, solange es nicht verkauft ist. Jedes “Packerl” auf Lager ist gebundenes Kapital. Dito jedes X und Y: Zwischenprodukte sind gebundenes Kapital. Geld, das in den Prozess geflossen ist, wurde umgewandelt in “Material”. Damit ist das Geld weg – und solange kein “Packerl” verkauft wurde, kommt dafür kein Geld zurück.

Es heißt also verkaufen, verkaufen, verkaufen. Dann kommt mehr Geld rein. Und das bedeutet, produzieren, produzieren, produzieren. Oder?

Grundsatz #2: Man kann nur soviel verkaufen, wie der Markt bereit ist abzunehmen.

Am Markt herrscht für “Packerl” ein bestimmter Bedarf. Alle Hersteller von “Packerln” können zusammen nur soviel verkaufen, bis die Nachfrage befriedigt ist. Wer mehr produziert als nachgefragt (oder absetzbar) ist, der hat überproduziert und damit Geld in unverkauftem/unverkäuflichem “Material” gebunden.

Eine der vornehmsten Aufgaben jedes Managers muss es daher sein, die Produktion so zu steuern, dass keine Überproduktion entsteht. Das bedeutet, die Produktionsleistung wird vom Markt bestimmt. Der Markt zieht sozusagen an der Produktion. Oder der Markt kann als ein Unterdruck angesehen werden, der “Packerl” ansaugt.

Die Steuerung eines Produktionsschritts n geht mithin vom ihm folgenden Schritt n+1 aus! Wenn der Markt pro Monat 10 “Packerl” braucht, dann sollten auch nur 10 produziert werden. Werden mehr produziert, ist das Geldverschwendung, weil ab dem 11. “Packerl” nicht mehr verkauft werden kann, aber Geld in der Herstellung geflossen ist. Geld wird also verbrannt. Werden weniger produziert, ist das Geldverschenkung, weil existierende Gelegenheiten zum Geldverdienen nicht genutzt werden.

image

Natürlich ist es eine rechte Kunst, den Bedarf abzuschätzen und die Produktion darauf einzustellen. Das ist nicht leicht. Aber darum geht es mir hier nicht. Diese Kunst zu lernen lohnt nämlich erst, wenn man die Implikationen verstanden hat.

Grundsatz #3: Downstream Bedarf bestimmt upstream Produktion in allen Teilen eines Prozesses.

Dass man gegenüber dem Markt nicht überproduzieren sollte, leuchtet schnell ein. Aber es ist wichtig zu verstehen, dass das nicht nur für das Verhältnis Produktion:Markt gilt, sondern für jede Folge von Prozessschritten.

Ein Folgeschritt n+1 hat pro Zeiteinheit immer eine bestimmte Kapazität, mit der er “Material” vom vorhergehenden Schritt n aufnehmen kann. Beim Markt heißt diese Kapazität Bedarf. Es gibt sie jedoch nicht nur dort, sondern bei jedem Prozessschritt.

Das bedeutet, dass sich die Produktionsleistung eines Prozesses auf jeder Ebene und in jedem Schritt immer am Bedarf ausrichtet, am Bedarf des Marktes bzw. an der Kapazität von Folgeschritten. Oder anders und radikal ausgedrückt:

Produktionsschritte produzieren nicht soviel sie können pro Zeiteinheit, sondern nur soviel wie vom Folgeschritt abgenommen werden kann.

Das widerspricht weithin gepflegter Denkungsart. Die fordert nämlich, dass jeder Schritt immer möglichst nahe an seiner Kapazitätsgrenze läuft. Eine stillstehende Maschine, ein Zeitung lesender Entwickler… das darf nicht sein! Jedes und jeder sollte möglichst immer mit “Materialverarbeitung” bzw. “Materialproduktion” beschäftigt sein.

Aber das ist falsch, weil es zu Kapitalbindung in Zwischenerzeugnissen führt. Wenn Produktionsschritt n mehr in einer Zeiteinheit herstellt als Schritt n+1 weiterverarbeiten kann, dann entsteht eine Halde vor n+1.

n ist dann zwar fein raus, weil dort die Kapazität ausgeschöpft wird. n+1 ist ebenfalls fein raus, weil auch dort an der Kapazitätsgrenze gefahren wird. Der lokale, enge Blick auf die Prozessschritt und ihre Kapazitätsauslastung sieht also ein gutes Ergebnis.

Aber was ist mit der Halde zwischen n und n+1? Nicht nur in Anschaffung und Betrieb von n und n+1 steckt Geld; Geld steckt auch in der Überproduktion, die zwischen Schritten auf Halde liegt.

image

Consumer, d.h. downstream Produktionsschritte steuern also den Output von Producern, d.h. upstream Produktionsschritten.

Wenn denn alle Produktionsschritte die gleiche Kapazität hätten, dann würde sich die Produktion ganz einfach in allen Teilen nach dem Markt richten. Der Markt würde mit seinem Bedarf M ziehen/ansaugen und der letzte Produktionsschritt m würde mit seiner Kapazität Km darauf eingestellt. Das würde dann zur Folge haben, dass sich m-1 in seinem Ausstoß Am-1 (und also auch früher oder später in seiner Kapazität Km-1) nach m richtete. Das würde dann zur Folge haben, dass sich m-2 nach m-1 richtet usw. usf.

Am Ende würde schönste Eintracht herrschen, weil M=Ki und Ai=Ki+1 gälten. Alles wäre im Gleichgewicht, nein, alles wäre gleich: Bedarf, Kapazitäten und Ausstöße.

Aber so ist die Welt leider nicht. Kapazitäten lassen sich nicht gleichmachen. Und der Markt bewegt sich dauernd. Das bedeutet, es gibt Kapazitätsunterschiede bei den Schritten im Geldverdienprozess.

Daraus folgt nach Grundsatz #3, dass nicht alle Produktionsschritte ihre Kapazität ausschöpfen sollten. Sonst läuft das Gesamtsystem Gefahr, Geld durch Überproduktion zu verschwenden. Es gilt vielmehr:

Grundsatz #4: Der Ausstoß eines Prozesses richtet sich nach dem Prozessschritt mit der geringsten Kapazität. Dieser Schritt ist der Engpass des Prozesses.

Wieviel “Packerl” kann der folgende Prozess pro Zeiteinheit an den Markt bringen?

image

Der Markt hat einen Bedarf M=4, die Kapazitäten sind Ka=3, Kb=2, Kc=4. An C soll es also nicht liegen, wenn der Markt nicht befriedigt wird; C kann mit der Nachfragte mithalten.

Aber C kann pro Zeiteinheit nur 2 Y von Produktionsschritt B bekommen. Egal wie C sich auch anstrengen mag, es bekommt nicht genügend Input, um seine Kapazität auszuspielen. B hungerte C gewissermaßen aus.

B auf der anderen Seite könnte von A 3 X pro Zeiteinheit bekommen – also 1 X mehr als es verarbeiten kann. B würde von A überschwemmt, vor B entstünde eine Halde mit gebundenem Kapital.

Es hilft also nichts: Der Bedarf kann sein, wie er will, es können nicht mehr “Packerl” hergestellt werden, als die, zu denen Produktionsschritt B pro Zeiteinheit seinen Beitrag leisten kann. Und das sind 2 X, die am Ende zu 2 “Packerl” werden.

Der Ausstoß eines Prozesses richtet sich nach der Kapazität des Engpasses: A=Ke. Auf den Engpass folgende Produktionsschritte werden unterfordert, vor ihm liegende müssen gedrosselt werden. Andere Produktionseinschritte als den Engpass oberhalb dessen Kapazität zu fahren, bringt einfach nichts. Das würde nur geschäftig aussehen, aber nicht zum Betriebsergebnis beitragen. Im Gegenteil: Vorgelagerte Schritte würden eine Kapitalbindung durch Haldenbildung erzeugen. Das wäre Geldverschwendung.

Das bedeutet im Umkehrschluss:

Grundsatz #5: Investionen sind nur lohnend, wenn sie sich auf die Beseitigung des Engpasses konzentrieren.

Solange der Markt mehr Bedarf hat, als die Produktion hergibt, liegt der Engpass im Prozess. Wer den Ausstoß vergrößern will, um mehr vom Bedarfskuchen zu bekommen, der muss sich auf die Erweiterung des Engapsses konzentrieren. Investionen in A oder C im vorigen Diagramm lohnen nicht. Ihre Kapazität, die ja ohnehin schon größer ist als die von B, zu erhöhen, wäre Geldverschwendung.

Leider gehen viele Unternehmen jedoch diesen Weg. Sie haben einen engen Blick, der jeden Produktionsschritt nur für sich sieht. Und sie denken, mehr Kapazität und Kapazitätsauslastung wären per se wünschenswert. Leider gilt für abhängige Prozessschritte jedoch nicht, dass optimale Einzelsschritte zu einem optimalen Gesamtergebnis führen.

Abteilungsdenken, Budgetdenken und Managementhierarchien leisten solchem Denken allerdings Vorschub. Und so ist es kein Wunder, dass viele Unternehmen Geld in unnützen Optimierungen versenken. Es wird zwar getan und gemacht… aber es bringt nichts, weil es nicht am Engpass ansetzt.

Am Engpass anzusetzen ist natürlich auch schwer, wenn man keine rechte Vorstellung vom Produktionsprozess hat. Wer keinen Fluss zeichnet, auf dem seine Produkte von der Idee bis zum “Packerl” beim Kunden schippern, der kann sich nicht fragen, wo ein Flaschenhals sitzt. Der reagiert vielmehr immer nur erratisch auf Symptome die sie hier oder dort zeigen. Ein gezielter, systematischer Eingriff zur Optimierung des Gesamtsystems ist nicht möglich. Denn das Gesamtsystem liegt ja im Dunkeln.

Wer das Gesamtsystem jedoch kennt, der kann systematisch vorgehen:

  1. Engpass identifizieren
  2. Engpass ausreizen (Grundsatz #5)
  3. Nicht-Engpass Prozessschritte am Engpass ausrichten (Grundsatz #3)
  4. Enpass erweitern (Grundsatz #5)
  5. Zurück zu 1.

Nach Identifikation von B als Engpass oben, müsste zuerst sichergestellt werden, dass B auch wirklich seiner Kapazität gemäß pro Zeiteinheit Ausstoß produziert; es ist zu erreichen: Ab=Kb.

Dann müsste Aa=Kb eingestellt werden. An C kann man nicht viel tun, ohne die Kapazität zu verringern.

Und schließlich wäre zu prüfen, wie die Kapazität von B erhöht werden könnte. Würden mehr Personal oder mehr Maschinen helfen? Oder eine Maßnahme, um die Produktivität der vorhandenen Ressourcen zu erhöhen?

Zwischenstand

Wenn ich der Softwareentwicklung ein Angebot machen will, dann funktioniert das letztlich nur, wenn Entwickler, Entscheider und ich ein gemeinsames Verständnis vom Gesamtsystem haben, in dem die Softwareentwicklung eine Rolle spielt. Die Frage ist zu klären, wie die Softwareentwicklung zum Geldverdienen beiträgt.

Wie aber sieht so ein Gesamtsystem aus? Ich finde eine Beschreibung mit Flüssen genauso einfach wie hilfreich. Und die Theory of Constraints (TOC) zeigt mir, wie solche Produktionsflüsse auf das Ziel “Geld verdienen heute und in der Zukunft” hin optimiert werden können.

Hier konnte ich die TOC nur anreißen. Es ging mir darum, eine Grundlage für weitere Erklärungen zu legen, die sich konkreter auf die Softwareproduktion beziehen. Ausgelassen habe ich z.B. die Themen Puffer und Maßzahlen. Aber wer mehr erfahren will, der kann viel darüber lesen. Ich finde z.B. diese Beschreibung online von Yougman sehr gut lesbar. Leider gibt es dazu kein handliches eBook, so dass die 500+ Seiten im Browser gelesen werden müssen. Wer es lieber kürzer und in Prosa haben möchte – um es z.B. auch dem Chef zu schenken –, der tut sich etwas Gutes mit dem Roman “Das Ziel” vom Erfinder persönlich.

Im nächsten Teil zur Flow-Orientation für´s Unternehmen baue ich auf der TOC auf und beschreibe meine Vorstellung eines grundsätzlichen Softwareproduktionsprozesses. Und danach unterhalten wir uns über Engpässe…

 

[1] “Material” schreibe ich in Anführungsstrichen, weil ich damit nicht nur Handfestes wie Stahl oder Holz meine, sondern auch Wissen und Software. “Material” ist etwas, aus dem man etwas herstellen kann, wofür Kunden bereit sind, Geld zu bezahlen.

Sonntag, 27. Februar 2011

Beziehungsratgeber für Softwareentwickler

Komplexität vergällt uns das Programmieren. Unwartbarkeit ist ein Komplexitätsproblem. Aber was macht die Komplexität in Software aus?

Für mich ist etwas komplex, wenn ich es durch Nachdenken nicht verstehen kann.

Solange etwas kompliziert ist, hilft Nachdenken. Beispiel: Der Boyer-Moore Algorithmus zur Zeichenkettensuche ist kompliziert (zumindest für mich). Ich verstehe ihn nicht sogleich, aber wenn ich die Beschreibung ein paar Mal lese und darüber nachdenke, dann blicke ich durch und kann ihn implementieren.

Insofern sollte Software eigentlich gar nicht komplex sein. Programme sind doch nur Ansammlungen von Algorithmen, die jeder für sich höchstens kompliziert sind.

Das wäre wohl korrekt, wenn da nicht Beziehungen bestünden. Die einzelnen “nur” komplizierten Teile einer Software sind miteinander verbunden. Sie stehen in Abhängigkeitsverhältnissen. Und das macht ihre Summe mehr als kompliziert, nämlich komplex.

imageDenn wo viele, gar unüberschaubar viele Beziehungen bestehen, da ist unklar, wie sich Veränderungen an einem Teil über die Beziehungen auf andere Teile auswirken. Mit unendlich viel Zeit, wäre das zwar herauszubekommen – doch wer hat die schon. Nachdenken würde also zwar grundsätzlich helfen, nur nicht in der verfügbaren Zeit. Somit ist Software nicht nur eine komplizierte Summe aus mehr oder weniger komplizierten Teilen, sondern ein komplexes Ganzes.

Also: Beziehungen zwischen Funktionseinheiten machen Software komplex. Je mehr es sind, je undurchsichtiger sie sind, desto komplexer das Ganze. Das bedeutet: Softwareentwicklung muss sehr bewusst mit Beziehungen zwischen Funktionseinheiten umgehen, um die Komplexität nicht ungewollt wachsen zu lassen.

Hier deshalb ein paar Beziehungsratschläge:

Ratschlag #1: Beziehungen vermeiden

Wenn Beziehungen aus Kompliziertem Komplexes machen, dann scheint es angeraten, auf sie zu verzichten, wo es nur geht. Denn ohne Beziehung keine (ungewollte) Ausbreitung von Änderungsnotwendigkeiten. Wenn Funktionseinheit A und B keine Beziehung haben, dann kann eine Veränderung an A keinen Effekt auf B haben.

image

Wo hingegen eine Beziehung besteht, da können sich Änderungen ausbreiten:

image

Hier ein Beispiel: Methode A() ruft Methode B() auf, um Daten zu beschaffen; identifiziert werden die Daten durch einen Schlüssel. A() ist also abhängig von B():

void A()
{
  var daten = B(“123”);
  …
}

void B(string schlüssel) {…}

Wenn nun B() seine Signatur ändert und statt einer Zeichenkette nur noch ganze Zahlen als Schlüssel akzeptiert, dann hat das Auswirkungen auf A().

Das ist eine recht simple Abhängigkeit und der Compiler erkennt, wo sie nicht erfüllt ist. Was aber, wenn die Abhängigkeit nicht syntaktischer Natur ist?

Nehmen wir an, B() liefert eine Zeichenkette der Form “a=1;b=2” zurück, aus der A() relevante Werte extrahieren soll. Dann zerlegt A() diese Zeichenkette sicherlich zunächst beim Trennzeichen “;”:

void A()
{
  var daten = B(“123”);
  foreach(string zuweisung in daten.Split(‘;’))
  {…}
}

Wenn jetzt B() die Wertzuweisungen in der Zeichenkette aber nicht mehr durch “;” trennt, sondern durch “,” oder beide Trennzeichen zulässt, dann hat das ebenfalls Auswirkungen auf A() – allerdings kann der Compiler nicht helfen, diese zu aufzustöbern. Es liegt eine tückische semantische oder logische Abhängigkeit vor.

Fazit: Sobald Beziehungen ins Spiel kommen, wird es bei der Softwareentwicklung ekelig. Vermeiden Sie daher Beziehungen oder minimieren Sie sie soweit es eben geht. Vorsicht vor allem vor logischen Beziehungen!

Ratschlag #2: Beziehungen syntaktisch machen

Nicht zu vermeidende Beziehungen sollten, wenn es irgend geht, syntaktisch sein und nicht logisch. Logische Abhängigkeiten machen das Ganze vor allem komplex, weil Werkzeuge nicht helfen, sie zu überblicken. Der Mensch muss sie “im Kopf haben”. Und wenn er das nicht hat, dann merk er erst zur Laufzeit, ob durch Änderungen an einer Funktionseinheit etwas bei anderen “verrutscht ist”.

Bezogen auf das obige Beispiel bedeutet das, die Rückgabe von Name-Wert-Paaren in Form eines String aus B() sollte ersetzt werden z.B. durch ein Dictionary. Das Wissen um Trennzeichen ist dann konzentriert auf B(); sollten sie sich ändern, hat das keine Auswirkungen mehr auf abhängige Funktionseinheiten wie A().

Strenge Typisierung ist also der erste Schritt zu einem anti-komplexen Beziehungsmanagement. Ein spezifischer Typ ist besser als ein allgemeiner. string reduziert logische Abhängigkeiten gegenüber object. Und HashTable reduziert sie noch weiter. Und Dictionary<string,int> reduziert sich noch weiter.

Abstrakte Datentypen (ADT) und Kapselung sind der nächste Schritt. Wieder in Bezug auf das Beispiel oben: Ein Dictionary<> ist zwar spezifischer als ein string, doch es lässt zum Beispiel zu, dass Werte überschrieben werden können. Vielleicht soll das jedoch nicht erlaubt sein für das, was zu Beginn als Zeichenkette kodiert war. Solange ein Dictionary<> “durch die Gegend geschoben wird”, existiert also noch eine subtile logische Abhängigkeit: die Funktionseinheiten, die mit den Daten umgehen, müssen wissen, dass sie Werte im Dictionary<> nicht überschreiben dürfen. Eine formale Prüfung, ob sie das auch beherzigen, gibt es aber nicht. Erst zur Laufzeit wird das sichtbar.

Besser ist es da, statt eines Standard-Dictionary einen spezielleren Datentyp zu implementieren, z.B. NonOverridableDictionary<TKey, TValue>. Die Beziehung zwischen A() und den Daten wird damit weiter syntaktisch formalisiert.

Fazit: Beziehungen, deren Gültigkeit der Compiler überprüfen kann, sind anderen vorzuziehen.

Ratschlag #3: Süchtige vereinfachen

Der Drache der Komplexität ist noch nicht erschlagen, wenn die Beziehungen so gering wie nötig und so syntaktisch wie möglich sind. Auch dann kann es noch Funktionseinheiten geben, die von vielen anderen abhängig sind. Es sind die unvermeidbar “Süchtigen”; sie brauchen viele andere Funktionseinheiten zu ihrem Glück.

image

Wenn Sie auf solch ein Beziehungsmuster stoßen, dann… ja, dann hilft nur eines: Halten Sie die süchtige Funktionseinheit so einfach wie möglich in Bezug auf die Funktionalität bzw. Domäne ihrer “Suchtmittel”.

Der Grund ist einfach: Wenn eine Funktionseinheit von vielen anderen abhängt, dann ist die Wahrscheinlichkeit sehr hoch, dass sich an einer Funktionseinheiten etwas ändert, die relevant für die Süchtige ist. “Irgendwas ist immer” könnte man sagen.

Solche Änderungen können sich aber nur fortsetzen entlang der Beziehungen (vom Unabhängigken zum Abhängigen), wenn der Abhängige kompliziert ist, d.h. große Angriffsfläche bietet. Denn Kompliziertheit geht gewöhnlich einher mit vielen logischen Abhängigkeiten.

Da logische Abhängigkeiten aber so schwer zu sehen sind, lautet der Rat, eher auf die Kompliziertheit der süchtigen Funktionseinheit zu achten, d.h. auf deren Umfang und den internen Aufbau. Der Umfang sollte klein, der interne Aufbau simpel sein.

Wenn sich nun an den komplizierten Funktionseinheiten etwas ändert, dann ist die Wahrscheinlichkeit, dass das eine Auswirkung auf das einfache Abhängige hat, gering.

image

Typisches Beispiel für ein solches Abhängigkeitsmuster sind DI Container. Bei ihnen werden viele Typen registriert, ihre Aufgabe ist sozusagen, maximal abhängig zu sein. Aber das macht nichts, weil DI Container sehr einfach sind in Bezug auf das, wozu sie Beziehungen haben. Sie wissen von den Domänen der Funktionseinheiten, die bei ihnen registriert werden, nichts.

Fazit: Je einfacher eine Funktionseinheit, desto größer kann die Zahl ihrer Abhängigkeiten sein.

Ratschlag #4: Promiskuitive vereinfachen

Das Gegenteil von süchtigen Funktionseinheiten, d.h. solchen, die von vielen anderen abhängen, sind solche, zu denen viele andere Abhängigkeitsbeziehungen haben. Es sind Promiskuitive mit großem Netzwerk. Fallen an ihnen Änderungen an, dann haben die potenziell ein großes Ausbreitungsgebiet.

Was können Sie dagegen tun? Machen Sie prosmikuitive Funktionseinheiten so simpel wie möglich in Bezug auf die Funktionalität der von ihnen abhängenden.

image

Das bedeutet, Änderungen setzen sich nicht so leicht entlang der Beziehungen fort. Vor allem aber: Es fallen Änderungen an der promiskuitiven Funktionseinheit gar nicht so häufig an. Denn was einfach ist, das sollte sich nicht so oft ändern.

Typisches Anti-Pattern für Promiskuitive sind “fette” Domänendatenmodelle. Von ihnen hängen viele andere Funktionseinheiten ab – aber die Domänendatenklassen selbst sind funktionsreich (d.h. kompliziert) und haben auch noch eine große Oberfläche durch ihre Properties.

Fazit: Je weiter bekannt eine Funktionseinheit, desto simpler sollte sie sein.

Ratschlag #5: Funktionseinheiten einzäunen

Funktionseinheiten sind nicht nur direkt, sondern auch indirekt gekoppelt. Änderungen an einer können sich deshalb u.U. weitreichend fortsetzen. Veränderungen an einem Teil führen dann zu Veränderungen an einem anderen, die wiederum zu Veränderungen an einem dritten Teil führen usw. Der Ausbreitungsprozess ist rekursiv entlang von Abhängigkeitsbäumen.

image

Um solchen Ausbreitungswellen Einhalt zu gebieten, ist es ratsam, Beziehungsnetzwerke in “Kästchen” zu isolieren. Selbst wenn die bisherigen Ratschläge beherzigt sind, sollten Beziehungen nicht beliebig verlaufen können.

image

Vielmehr sollte Kohäsives, d.h. Funktionseinheiten die enger in Beziehung stehen, von anderem deutlich getrennt werden. Das enger Zusammengehörige aus dem vorigen Bild ist im nächsten zu diesem Zweck “umzäunt” und die Beziehungen zwischen den “Nachbarn” verlaufen über eine “Kontaktperson”, einen Vermittler.

image

Änderungen hinter der Fassade des Vermittlers haben dann eine geringere Chance auf Abhängige durchzuschlagen.

image

Die sich durch die Trennung ergebenden Zusammenfassungen können dann als Funktionseinheiten auf höherer Abstraktionsebene angesehen werden. Auf sie sind dort dann natürlich dieselben Ratschläge zur Komplexitätsreduktion durch bewusste Beziehungspflege anzuwenden.

image

Fazit: Bewusst eingezogene Mauern zwischen Gruppen von Funktionseinheiten, die die Beziehungsaufnahme einschränken, reduzieren die Komplexität.

Zwischenstopp

Softwareentwicklung ist Beziehungsmanagement. Denn Software ist immer ein Geflecht aus Funktionseinheiten, die von einander abhängig sind. Fragt sich nur, wie. Ohne sorgfältige Planung von Beziehungen und der gegenseitigen Kompliziertheit der Bezogenen, läuft Software in eine Komplexitätsfalle.

Unabhängig von Plattform und Programmiersprache gibt es jedoch Ratschläge, denen Sie folgen können, um nicht Opfer wuchernder Beziehungen zu werden. Wo Beziehungen nötig sind, lässt sich ihr Beitrag zur Komplexität minimieren.

Bewegen Sie die Ratschläge #1 bis #5 im Herzen und schauen Sie dann auf Ihren Code oder die Empfehlungen der Literatur zu ihrer Plattform. Sie werden erkennen, dass hinter Technologiediskussionen und neuen Rezepten für bessere Software oft auch nur der Wunsch steht, Komplexität durch Beziehungsmanagement in den Griff zu bekommen. Exemplarisch seien im Folgenden einige bekannte Prinzipien aus diesem Blickwinkel betrachtet, die sich zum größten Teil auch als Bausteine bei der Clean Code Developer Initiative wiederfinden.

Beziehungsmäßige Prinzipien

Ratschlag #1 ist so fundamental, dass er im Grunde die Mutter aller Softwareprinzipien darstellt. Er findet sich im alten Prinzip Lose Kopplung wieder. Lose Kopplung bedeutet, Beziehungen zu vermeiden bzw. zu minimieren.

Ratschlag #2 ist immer wieder im Gespräch, wenn es um die Typisierung von Programmiersprachen geht. Typsicherheit, Typinferenz, dynamische Typen, Variants… das alles sind Aspekte expliziter syntaktischer Beziehungen.

Während lange Zeit galt, dass strenge Typsierung fundamental für die Reduktion der Fehlerzahl in Software sei, hat sich das Bild in den letzten Jahren jedoch verändert. Testautomatisierung wird von einigen Diskutanten als Alternative angeführt. Wo Tests automatisiert und schnell nach einem Compilerlauf ausgeführt werden können, ist Feedback zur Beziehungskonformität fast ohne Verzug auch zur Laufzeit zu erreichen. Das relativiert Ratschlag #2 etwas, dennoch gilt weiterhin: Einschränkungen der Beziehungsmöglichkeiten sind immer noch der Entdeckung von Beziehungsfehlern vorzuziehen. Gut, wenn Sie schnell erkennen können, wo etwas falsch läuft; noch besser, wenn Sie manche Fehler erst gar nicht machen können. Das Thema Abstrakter Datentyp (ADT) ist mit Testautomatisierung allemal nicht vom Tisch.

Womit wir bei den Prinzipien Information Hiding und Law of Demeter wären. Beide plädieren für eine Verringerung der Oberfläche von Funktionseinheiten, so dass weniger Beziehungen zu ihnen aufgebaut werden können. Auf höherer Abstraktionsebene tut das auch die Komponentenorientierung. Es sind Prinzipien und Praktiken im Sinne von Ratschlag #5.

Das gilt auch für das Single Responsibility Principle, das allerdings auch Ratschlag #3 und #4 zum Thema hat. Denn der Fokus auf nur eine Verantwortlichkeit pro Funktionseinheit reduziert deren Kompliziertheit. Dasselbe will natürlich auch Keep it simple, stupid (KISS).

Konkreter im Sinne von Ratschlag #3 ist dann Single Level of Abstraction (SLA), dessen Anwendung dazu führt, Kompliziertheit aus einer Funktionseinheit hinauswandert in neu geschaffene auf niedrigerem Abstraktionsniveau. SLA macht aus normalen Funktionseinheiten Süchtige - die allerdings simpler sind als vorher.

Das Open Close Principle (OCP) wendet sich auf der anderen Seite eher an promiskuitive Funktionseinheiten im Sinne von Ratschlag #4. Seine Anwendung macht sie weniger änderungsanfällig.

Das Inversion of Control Prinzip (IoC) schließlich adressiert abhängige Funktionseinheiten jeder Art. Es will ihre Beziehungen lockern durch Austausch statischer gegen dynamische Abhängigkeiten. Das reduziert zwar nicht die Zahl der Abhängigkeiten, macht sie aber flexibler, so dass Textautomatisierung besser ansetzen kann.

Fazit

Das Thema Beziehungsmanagement ist für die Softwareentwicklung also alt. Viele Prinzipien, die heute weithin anerkannt sind, um korrekten und evolvierbaren Code zu schreiben, drehen sich um Beziehungen – ohne das immer explizit zu machen. Damit ist die Zentralität des Beziehungsthemas leider leicht verschleiert. Statt eines Namens hat es viele. Das verhindert eine Diskussion darüber, wie es an der Wurzel gepackt werden kann.

Denn Prinzipien sagen zwar, was getan werden kann, um das fundamentale Beziehungsproblem zu mildern, doch sie drängen sich nicht auf. Jeder Entwickler hat die Freiheit, sie anzuwenden oder auch nicht. Im Zweifelsfall entscheidet er sich – natürlich nur für den Moment und immer aus guten Gründen – deshalb dagegen. Und so wächst die Komplexität in Software weiter, obwohl wir eigentlich genau wissen, was ihre Ursache ist und was wir dagegen tun können.

Prinzipien geben allerhöchstens Handreichungen, definieren aber keine Methode und kein Rahmenwerk. Prinzipien wie auch die hier gegebenen Ratschläge allein sind daher schwache Waffen gegen die Komplexität.

Ich glaube, wir müssen stärkere Waffen in Anschlag bringen. Wenn der Spruch gilt, “Sag mir, wie du mich misst, und ich sage dir, wie ich mich verhalten werde”, dann müssen wir mehr am Start haben als fluffige Prinzipien. Die sind nämlich keine genügend starken Wände, an denen man sich die Stirn einhauen kann, bis man von selbst auf dem Pfad der Tugend bleibt. Wenn Beziehungsmanagement so zentral für die Softwareentwicklung ist, dann müssen wir Sprachen oder allemal Denkwerkzeuge haben, die das Beziehungsmanagement in den Mittelpunkt stellen. Beziehungen müssen sich weitgehend von allein und intuitiv den Ratschlägen entsprechend bilden. Sonst ist nicht zu hoffen, dass die wenigen Ratschläge oder die vielen Prinzipien zur Reduktion von Komplexität führen.

Freitag, 10. Dezember 2010

Gesunde Anämie

Immer wieder gibt es Uneinigkeit darüber, wie zustandsbehaftete Domänenklassen mit Funktionalität ausgestattet werden sollen. Ist es eine Tugend, ein Kundenobjekt nach seiner Bonität befragen zu können? Die vorherrschende Meinung sieht das wohl so. Und wo sie zustandsbehaftete Domänenklassen auf die Datenhaltung reduziert sieht, spricht sie von einem anämischen Domänenmodell. Gestern bei einem TDD-Training und auch heute beim Architecture Open Space gab es dazu wieder einige Diskussion.

Aber warum wird denn überhaupt dazu soviel noch diskutiert? Ist es denn nicht klar, was richtig ist? Hat die Objektorientierung nicht schon seit Jahrzehnten eine gute Antwort gefunden? Anscheinend nicht. Es gibt immer noch oder schon wieder zwei Lager. Die einen bemühen sich, Domänenmodelle reich zu machen; da bekommt der Kunde seine Bonitätsprüfungsfunktion. Die anderen sehen das als kontraproduktiv an und argumentieren, Geschäftsregeln und Datenstrukturen seien auseinander zu halten. Ich gehörer letzterer Fraktion an, wie ich in früheren Blogartikeln schon klar gemacht habe.

Leider kann ich jedoch mit meinen Argumentationsversuchen nicht immer so leicht punkten, wie ich es mir wünsche. Deshalb bin ich immer auf der Suche nach neuen Gesichtspunkten, die meine Position stützen und erklären helfen – oder, ja, von mir aus auch einfach begreifbar widerlegen.

Heute nun habe ich wieder einen solchen Gesichtspunkt entdeckt. Und hätte der Architecture Open Space nicht auch sonst schon wegen des Community Erlebnisses Spaß gemacht, dann wäre er deshalb lohnenswert gewesen. Hier meine Erkenntnis:

Anämische Domänenmodelle sind eine Tugend, weil sie entkoppeln.

Ha! Wer hätte das gedacht? Wieder ist die Antwort auf hartnäckige Fragen “Entkopplung”. Abhängigkeiten, also Kopplung, ist einfach eines der Grundübel der Softwareentwicklung.

Meine Argumentation:

Abhängigkeiten jeder Art behindern die Evolvierbarkeit. Sie sind deshalb zu vermeiden oder zumindest zu minimieren.

Wenn eine Funktionseinheit A von vielen anderen Funktionseinheiten abhängig ist – U1, U2, U3, … –, d.h. eine hohe efferente Kopplung hat, dann ist das Risiko groß, dass A sich ändern muss, weil irgendwo bei U1, U2, U3 usw. eine Änderung vorgenommen wurde.

image

Wenn andererseits eine Funktionseinheit U von vielen abhängigen Funktionseinheiten A1, A2, A3 usw. gebraucht wird, d.h. eine hohe afferente Kopplung hat, dann ist das Risiko groß, dass eine Änderung an U sich auf alle A* auswirkt.

image

Wir können es also drehen und wenden, wie wir wollen, eine große Anzahl von Abhängigkeiten koppelt eng, weil die Abhängigkeitslinien Wege für die Ausbreitung von “Veränderungsschockwellen” sind.

Nun ist es natürlich unzweifelhaft, dass Abhängigkeiten nötig sind. Softwaresysteme wären keine Systeme, wenn sie nur Haufen unverbundener Funktionseinheiten wären. Also ist die Frage, wie können wir den potenziellen Schaden von Abhängigkeiten in Grenzen halten?

  1. Die Kopplung sollte so gering wie möglich und so stark wie nötig sein. Bewusstheit beim Aufbau von Abhängigkeiten ist also gefragt.
  2. Je größer die Kopplung, desto einfacher sollten die gekoppelten Funktionseinheiten sein. Kopplung und Kompliziertheit sollten umgekehrt proportional sein.
    Einfachheit lässt sich herstellen durch:
    • fokussieren der Verantwortlichkeit (Single Responsibility Principle)
    • verbergen von Details (Encapsulation)

Die zweite Regel verdient besondere Aufmerksamkeit. Schauen wir uns an, welche “Verhältnisse” sich ergeben, jenachdem ob die gekoppelten Funktionseinheiten einfach oder kompliziert sind:

Efferente Kopplung U* einfach U* kompliziert
A einfach überschaubar überschaubar
A kompliziert kompliziert komplex

Eine hohe efferente Kopplung ist unkritisch, wenn die abhängige Funktionseinheit einfach ist. Dann können sogar die unabhängiggen Funktionseinheiten kompliziert sein, denn falls Änderungen an ihnen “durchschlagen”, muss nur Einfaches angepasst werden - wenn überhaupt; was zu tun ist, ist dann überschaubar.

Ist die abhängige Funktionseinheit jedoch kompliziert, dann werden die Verhältnisse komplex. Denn dann ist nur schwer abschätzbar, was überhaupt als Anpassung zu tun ist, falls die Unabhängigen sich ändern.

Etwas schlimmer sieht es sogar noch bei der afferenten Kopplung aus:

Afferente Kopplung U einfach U kompliziert
A* einfach überschaubar komplex
A* kompliziert kompliziert komplex

Hier sind die Verhältnisse immer komplex, wenn die unabhängige Funktionseinheit kompliziert ist. Änderungen wirken sich ja potenziell auf viele andere Funktionseinheiten aus. Das ist schwer abzuschätzen.

Jetzt die Übersetzung auf die Modellierung:

  1. Event-based Components (EBC) packen das Thema Kopplung/Abhängigkeiten bei den Hörnern, indem sie Funktionseinheiten im Sinne der Funktionalität statisch und dynamisch unabhängig voneinander machen. Logische Kopplung existiert zwar weiterhin, aber zumindest müssen Bauteile sich nicht mehr untereinander kennen. Das ist ein guter Schritt voran bei der Entkopplung. Unmittelbar ist das daran zu erkennen, dass Sie zum automatisierten Testen von EBC-Bauteilen keine Mock-Frameworks brauchen.
  2. EBC entschärfen die efferente Kopplung, indem sie Abhängigkeiten auf die Verdrahtung beschränken. Nur Platinen sind abhängig von anderen Funktionseinheiten. Das mögen dann auch viele sein – doch das macht nichts, weil Platinen denkbar einfach sind. Ihr einziger Zweck ist die Verdrahtung mit trivialem Code. Die Verhältnisse sind überschaubar, auch wenn die verdrahteten Funktionseinheiten kompliziert sind (s. rechte obere Tabellenzelle bei der efferenten Kopplung).
  3. EBC geben Domänendatenmodellen einen klaren Platz in der Modellierung: als Typen für die Daten, die zwischen EBC-Funktionseinheiten fließen.

    image
    Das bedeutet jedoch, dass viele Funktionseinheiten von ihnen abhängig sind. Im Bild müssen A, B, C und D den Domänenobjektmodelltyp d kennen. Die afferente Kopplung von d ist also hoch.
    Das bedeutet – und das ist meine heutige Erkenntnis auf dem Architecture Open Space –, dass d nicht kompliziert sein darf.

Domänendatenobjekte haben per definitionem eine hohe afferente Kopplung. Das ist für mich der wesentliche Grund, warum sie so einfach wie möglich gehalten werden sollten. Prinzip schlägt Objektorientierung, möchte ich sagen. Es ist mir also egal, ob ein Domänenobjektmodell als anämisch angesehen wird oder nicht. Objektorientierung ist ein Tool. Mit diesem Tool sollte ich nicht gegen fundamentale Prinzipien – hier: Abhängigkeiten erzeugen Komplexität; Entkopplung reduziert Komplexität – verstoßen, auch wenn eine bestimmte Benutzung des Tools noch so toolgemäß aussehen mag.

Wo hohe Abhängigkeiten von zwischen Objekten bestehen, da muss auf die Kompliziertheit der Beteiligten sehr genau geachtet werden. Mit EBC sind wir da auf einem sehr guten Weg, würde ich sagen. Aber nun weiß ich auch, warum es nicht schlimm ist, wenn EBC irgendwie gegen die heilige Kuh “reichhaltiges Domänenobjektmodell” verstößt, weil Domänenobjektmodelle, die auf Drähten fließen, immer irgendwie blutleer wirken. Auch das ist nämlich eine Tugend, weil ihre Datentypen so weitreichend gekannt werden. Anämie kann also auch mal gesund sein, wie hier zu sehen ist :-)

Objektorientiertes Nachspiel

Mir ist jetzt auch klarer, wo Chancen und Grenzen der Objektorientierung liegen. Gegenüber der prozeduralen Programmierung bietet Objektorientierung Kapselung.

Wo früher ein Record/struct nur Daten gehalten hat und die Funktionalität, die auf diesen Daten auch nur das Einfachste tun sollte, davon getrennt war, hat Objektorientierung Daten und Funktionalität zusammengezogen und unter einer “kleineren” Oberfläche verborgen. Die afferente Kopplung sinkt damit, weil U (s. zweite Abbildung oben) einfacher wird. Das ist gut so. Danke, Objektorientierung! Da liegt die Chance für uns mit der Objektorientierung.

Die Grenze der Objektorientierung ist jedoch erreicht, wenn ihre Möglichkeit zur Zusammenfassung von Daten und Funktionalität darüber hinaus gedehnt werden. Die Objektorientierung hat angefangen mit dem Begriff des ADT (Abstrakter Datentyp), d.h. einem “Objektmodell”, das Zustand hat und auf sich auch operieren kann. Beispiele dafür sind Stack, Baum oder Priority Queue usw. Gern auch ein “anämisches Objektmodell” mit Funktionen, die nicht über sich selbst hinausgreifen und keinen Zweck jenseits des “Zustandsorganisation” haben, der Konsistenzhaltung.

Wenn nun Objektorientierung über den ADT hinausgeht und ein reichhaltiges Domänenobjektmodell kreiert, dann mag die Kapselung immer noch ordentlich sein, auch wenn sie schwieriger wird. Vor allem aber wird der “Single Purpose” auf die Probe gestellt und die Kompliziertheit steigt schnell an.

Und genau das ist es, wo dann die Werte der Objektorientierung durch höhere Werte überstimmt werden. Wenn Objektorientierung die Entkopplung gefährdet, dann hat sie ihre Grenze erreicht. Das sehe ich beim reichhaltigen Domänenobjektmodellen. Sie überspannen den objektorientierten Bogen. Sie erzeugen – im allerbesten Willen – ungünstig hohe afferente Kopplung.

Objektorientierung ist nur ein Tool. Wer professionell Software entwickeln will, muss ihre Werte abwägen gegen andere. EBC tut das mit den bisherigen Übersetzungsvorschlägen für Modelle, würde ich sagen. Objektorientierung wird genutzt – solange der hohe Wert der Evolvierbarkeit repräsentiert durch das Prinzip “lose Kopplung, hohe Kohäsion” nicht kompromittiert wird.

Donnerstag, 30. Juli 2009

Mehr Termindruck braucht die Softwareentwicklung – aber den richtigen [OOP 2009]

heise Developer berichtet gerade über eine Studie in der Java-Welt – das 2. Java-Trendbarometer der expeso GmbH –, die die Verhältnisse bei der Projektabwicklung als nicht sehr rosig darstellt. Das ist mir als .NET-Entwickler ein kleiner Trost, denn ich dachte, in der Java-Welt sei das Gras grüner bzw. die Projektwelt rosig-erleuchteter. Nicht nur hüben, sondern auch drüben gibt es also einiges zu tun.

Die Begründung für die verbesserungswürdigen Zustände:

“Die Qualität leide[t] unter der häufig sehr stark termingetriebenen Entwicklung.”

Wer hätte das gedacht? Ja, alle denken und wissen das. Und viele ringen mit den Händen und fragen, “Ja, was sollen wir denn machen?”

Dabei ist die Lösung ganz, ganz einfach:

Softwareprojekte brauchen noch mehr Termindruck!

Wer hätte das gedacht? Wenige. Aber es ist so. Wir brauchen nicht weniger Termine, sondern mehr. Das ist der Kerngedanke der Agilität. Viele und ganz regelmäßige Termine sind nötig, damit es mit einem Softwareprojekt vorangeht. Die liegen dann 2 oder 3 oder vielleicht auch 4 Wochen auseinander. Und die Zeiträume zwischen den Terminen heißen Iteration oder Sprint.

image Diese Termine sind allerdings keine Meilensteine! Das (!) ist der entscheidende Unterschied zu den heutigen Terminen, die soviel Druck machen. Denn Meilensteine definieren eine Menge von Arbeit, die geschafft sein muss; bei Meilensteinen geht es um Inhalte. Iterationsendtermine drehen sich hingegen nur um verlässliche Zeitangaben.

Iterationen takten die Auslieferung. Sie garantieren, dass zu festgelegten und sehr häufigen Terminen etwas geliefert wird, das Feedback vom Kunden bzw.  seinem Vertreter braucht. Kommt solches Feedback nicht, dann wächst die Gefahr, dass Geld und Motivation schlicht verbrannt werden. Deshalb sind es auch immer mehr Iterationsendtermine als Meilensteintermine in einem Projekt. Softwareprojekte profitieren davon, wenn Entwickler und Kunden quasi ständig kurz vor einem Abgabetermin stehen. Das nächste Release – in welcher Größe auch immer – ist immer nur maximal 2-3 Wochen in der Zukunft. Es ist sozusagen immer “Meilensteinendphase” – allerdings ohne den Scope-Druck der Meilensteine.

Denn da liegt das wahre Übel bei der Projektabwicklung: Termindruck ist nur ein Symptom von Scope-Druck, d.h. vom Anspruch eine bestimmte Menge an Inhalten zu schaffen.

Und woher kommt diese Vorstellung, dass sich zu einem Termin eine vordefinierte Menge an Inhalten, an Features produzieren ließe? Die resultiert aus einem Grundmissverständis her. Es stammt aus einer Zeit, da Softwareentwicklung im Grunde nur Hardwaremanipulation war. Sie ist ein Relikt aus der Elektrotechnik und dem Maschinenbau, die beide ausgedehnte Produktionsphasen kennen.

Mit solchen Produktionsphasen wird Softwareentwicklung verwechselt. Für ein Auto oder einen Schaltkreis oder ein Gebäude können wir schon lange recht gut angeben, wann die Produktion zu welchem Prozentsatz abgeschlossen ist. Bauphasen lassen sich planen – vorbehaltlich auftretender Störungen z.B. durch Probleme mit Lieferanten oder am Produktionsort.

Doch Softwareentwicklung ist keine Produktion! Softwareentwicklung ist Entwicklung, Entwurf, Design, Kreativität, Problemlösung. Niemand sage also, er hätte es nicht gewusst. Im Deutschen wie im Englischen steckt die Natur der Sache schon im Begriff: Softwareentwicklung, software development. Nomen est omen.

image Jack W. Reeves hat das schon vor bald 20 Jahren deutlich herausgearbeitet. Er sieht “Code as Design”. Seine unmissverständlichen Worte sind hier zu lesen, und hier gibt es noch ein Wiki zum Thema. Dennoch scheint die Fehlwahrnehmung unausrottbar. Denn nicht anders ist zu erklären, dass immer noch Meilensteine als erreichbar gelten.

Wer hätte jedoch von Meilensteinen bei kreativen Aufgaben je gehört? Die Entwicklung (!) des iPod hat sicherlich nicht in festgelegten Meilensteinen stattgefunden. Die Entwicklung (!) des Smart hat sicher nicht in Meilensteinen stattgefunden. Nichts anderes ist aber auch die Entwicklung (!) einer Warenwirtschaft oder eines Dokumentenverwaltungssystems.

Entwicklung ist ein kreativer Prozess, der zwar ein grundsätzliches Ziel hat - aber bei dem man erstens nicht weiß, wie ganz haargenau das Ziel eigentlich aussieht, und zweitens wann es denn erreicht sein wird. Das ist ein fundamentales Gesetz der Softwareentwicklung. Sicherlich muss ein Entwicklungsprozess Fortschritte machen; das Gefühl einer stetigen Annäherung an das ungenaue Ziel muss da sein. Aber ansonsten ist nichts fix. Ab einer gewissen Reife der Entwicklung kann quasi immer jemand sagen, “Es ist genug! Ich bin zufrieden. Lassen wir es dabei. Wir gehen in Produktion.”

Entwicklung lässt sich nicht in Meilensteinpakete aus Inhalt+Termin verpacken. Entwicklung lässt sich höchstens zeitlich takten, um sicherzustellen, dass sie fortschreitet. Das ist alles. Das ist die Natur der Sache der Softwareentwicklung.

image Und deshalb braucht Softwareentwicklung mehr Termindruck, quasi den ständig drohenden Termin. Interessanterweise droht der dann aber gar nicht mehr, wenn ihm keine Inhaltslast mehr anhaftet. Der ständig in 2-3 Wochen liegende nächste kleine Abgabetermin kann vielmehr als eine produktive Rhythmisierung der Arbeit empfunden werden.

Nur eine kleine Voraussetzung ist dafür nötig: Vertrauen. Vertrauen in die Teammitglieder, dass sie bis zum nächsten Termin ihr Bestes geben, soviel wie mit vernünftigem Zeitaufwand eben möglich ist auch inhaltlich umzusetzen. Aber das ist ein anderes Thema.

Sonntag, 17. Februar 2008

Warum Veränderung es schwer hat in der Softwareentwicklung [OOP 2008]

Grad lese ich "The Art of Change" von Loebbert und muss innehalten, weil mir aufgeht, was oft schiefgeht. In den Gesprächen auf der VSone und anschließenden Architekturworkshop habe ich immer wieder Aussagen gehört wie: "Ich würde gerne anwenden, was Sie sagen, aber in unserem Unternehmen geht das nicht. Der Chef/der Projektleiter/... sagt immer, dass das früher ja auch nicht nötig war."

Auf die genaue Begründung, die dem Chef/Projektleiter usw. in den Mund gelegt wird, kommt es nicht an. Gemeinsam ist allen, dass den Führungspersonen keine Einsicht in die Notwendigkeit eine Veränderung in den Mund gelegt wird.

Angesichts meiner Lektüre habe ich mich nun gefragt, was es so schwierig macht, Veränderungen in der Softwareentwicklung anzuregen und durchzuführen. Meine heutige Erkenntnis: Es ist so schwierig, weil es Missverständnisse gibt und Wahrnehmungsorgane fehlen.

Ordnungen der Veränderung

Missverständnisse beim Gespräch über Veränderungen rühren von einer undifferenzierten Betrachtung her. Die an der Softwareentwicklung beteiligten glauben nämlich oft, dass Veränderungen nicht nötig seien, weil sie ja ohnehin schon die ganze Zeit in einer Projektsituation leben, die von Veränderungen nur so strotzt. Warum also noch mehr verändern, wenn die ständigen Veränderungen heute schon an den Rand des Machbaren gehen?

Undifferenziert ist diese Sichtweise, weil sie alle Veränderungen in einen Topf schmeißt. Neue Funktionalität, neue Technologien, neue Teamorganisation: das alles ist einerlei.

Um notwendige Veränderungen plausibler zu machen, scheint mir daher zunächst wichtig, genauer hinzuschauen. Ich teile Veränderungen daher mal in 5 Ordnungen ein:

0. Ordnung: Veränderungen im Sinne eines Kunden sind für mich Veränderungen 0. Ordnung. Ein neues Feature, eine Fehlerkorrektur, höhere Performance... wird Software modifiziert, um funktionale und nicht funktionale Anforderungen zu implementieren, sind das Veränderungen 0. Ordnung. An ihnen ist aber nicht nur der Kunde interessiert. Sie sind es auch, auf die Führungskräfte besonders ihren Blick richten.

1. Ordnung: Bei Veränderungen 1. Ordnung werden Materialien verändert. Holz wird zu einem Schrank verarbeitet, APIs und Daten zu einer Software. Wenn einer ein Werkzeug in die Hand nimmt (z.B. Visual Studio), damit Material bearbeitet (z.B. den WinForms API) und zu einem Produkt zusammenfügt, dann ist das eine Veränderung 0. Ordnung. Das, was zu tun ist, ist klar. Es muss nur einfach getan werden. Dabei verändern sich die Rohstoffe hin zum Produkt im Sinne der Kundenanforderungen. Das ist die tägliche Arbeit der Softwareentwickler. Veränderungen 1. Ordnung werden vorgenommen, um Veränderungen 0. Ordnung zu bewirken.

2. Ordnung: Veränderungen 1. Ordnung finden in einem Rahmen statt. Das ist die Grundstruktur oder auch Architektur einer Software. Wenn sich dieser Rahmen ändert, dann ist das eine Veränderung 2. Ordnung. Sie ändert nichts an der Rohstoffveränderung/-verarbeitung, nichts an den Features einer Software, sondern nur die strukturellen Zusammenhänge, in denen die Veränderungen 0. und 1. Ordnung stattfinden. Ziel von Veränderungen 2. Ordnung ist das technische Fundament, auf dem Software ruht.

3. Ordnung: Was hergestellt wird, ist eingebettet in einen Herstellungsprozess. Veränderungen an ihm sind Veränderungen 3. Ordnung. Hierzu zähle ich z.B. die Einführung von Unit Tests oder Continuous Integration oder den Umstieg von VSS auf TFS. Werden diese und andere Konzepte bzw. Werkzeuge eingeführt, die die Art ändern, in der Code produziert wird, dann geht es um Veränderungen 3. Ordnung.

4. Ordnung: Schließlich kann sich auch noch der Rahmen ändern, in dem der Herstellungsprozess abläuft. Wenn Teamorganisation und Vorgehensmodell verändert werden sollen, dann sind das Veränderungen 4. Ordnung. Früher ad hoc Tests, heute Unit Tests - das ist eine Veränderung 3. Ordnung. Aber früher ad hoc Programmierung, heute eXtreme Programming, das überhaupt den Ausschlag für Unit Tests gegeben hat - das ist eine Veränderung 4. Ordnung. Ändern sich die Rollen, ändern sich die Vorgehensschritte (z.B. durch Einführung einer QS oder eines Change Management oder von time boxes Releases), dann sind das Veränderungen 4. Ordnung.

5. Ordnung und höher: Veränderungen höherer Ordnung betreffen nicht mehr unmittelbar das Softwareentwicklungsteam, sondern seine Umwelt. Dazu gehören zum Beispiel Firmenzusammenschlüsse oder die Einführung von Teleworking.

Mit diesen Ordnungen von Veränderungen in der Hand, können Sie in Zukunft sehr einfach die Empfehlungen einordnen, die Sie lesen oder hören. Sie lesen einen Artikel über Scrum - und Sie wissen sofort, dass es um Veränderungen 4. Ordnung geht. Sie lesen einen Artikel, der den Einsatz von BizTalk Server empfiehlt - und Sie wissen, dass die Umsetzung der Empfehlung eine Veränderung 2. Ordnung bedeutete. Den üblichen Artikeln in Fachzeitschriften nach dem Motto "Mehr Performance durch xyz" oder "Best Practices für den Einsatz von abc" geht es um Veränderungen 1. Ordnung.

Ordnungsbeziehungen

Die Ordnungen der Veränderung bilden eine Hierarchie, die ich einmal als Pyramide darstelle:

image

Höhere Ordnungen sind darin das Fundament für Veränderungen niederer Ordnung. Veränderungen der Ordnung n führen zu Strukturen, die Voraussetzung für Veränderungen der Ordnung n-1 sind. Oder anders ausgedrückt: Veränderungen der Ordnung n-1 sind eingebettet bzw. basieren auf vorherigen Veränderungen der Ordnung n:

  • Veränderungen an funktionalen und nicht funktionalen Features einer Software setzen Veränderungen an den "Softwarerohstoffen" voraus.
  • Veränderungen an und mit "Softwarerohstoffen" sind immer eingebettet in einen Architekturrahmen und insofern auch abhängig von Veränderungen an ihm.
  • Veränderungen am Architekturrahmen setzen voraus, dass sie auch getestet, produziert und deployt werden können. Sie basieren also auf einem angemessen veränderten Produktionsprozess.
  • Veränderungen am Produktionsprozess sind nur möglich, wenn sich auch das Vorgehensmodell um ihn, also die Teamorganisation verändert.

Um Veränderungen n-ter Ordnung durchzuführen, sind also ggf. vorher Veränderungen (n+1)-ter Ordnung vorzunehmen.

Den Ordnungen der Veränderungen entsprechen natürlich Ebenen von Systemen (bestehend aus Strukturelementen und Beziehungen) und Prozessen. Diese Systeme und Prozesse sind es, die verändert werden. Insofern beschreibt die obige Pyramide auch eine Hierarchie von Systemen/Prozessen, die aufeinander aufsetzen:

  • Das System der kundenrelevanten Features basiert auf einem System von Codeeinheiten.
  • Das System der Codeeinheiten basiert auf bzw. manifestiert ein Modellsystem, die Architektur.
  • Die Architektur ist eingebettet in ein Produktionssystem und QS-Prozesse.
  • Produktion und QS sind Ergebnis der Zusammenarbeit innerhalb des sozialen Systems Projektteam.

Keine Veränderung ohne Druck

Veränderungen sind kein Selbstzweck. Niemand verändert sich - allemal kein Unternehmen -, ohne einen Druck wahrzunehmen, der eine Veränderung zumindest nahelegt, um in Zukunft weniger Druck zu verspüren.

Woher kommt der Druck, um Veränderungen verschiedener Ordnung anzustoßen? Vor allem kommt Druck natürlich von außen. Der Kunde äußert Anforderungen, die in Veränderungen 0. Ordnung resultieren, die auf Veränderungen 1. Ordnung basieren. Ebenso sind es vor allem die expliziten Kundenanforderungen, die Veränderungen der 2. Ordnung anstoßen. Aber auch neue Optionen der Technologieanbieter können sie ermuntern.

Doch schon für Veränderungen 2. Ordnung sind die Impule von außen vergleichsweise schwach. Ganz zu schweigen von Veränderungen höherer Ordnung. Kein Kunde wünscht sich eine bestimmte Produktionsweise oder ein konkretes Vorgehensmodell.

Bei gegebenen Systemen und Prozessen ab Ebene 2 stellt sich daher die Frage: Woher kommt Veränderungsdruck? Warum sollten Veränderungen 2., 3. oder 4. Ordnung überhaupt durchgeführt werden?

Und genau da liegt das Hauptproblem vieler Softwareprojekte, scheint mir. Ein mangelnder Druck von außen ist der Grund für die oben erwähnte Aussage von Entwicklern. Ein Blick auf die Pyramide zeigt auch: Für den Kunden - und somit für Führungskräfte - unmittelbar relevant ist nur die Ebene 0. Nur (oder vor allem) Veränderungen 0. Ordnung werden von ihnen wahrgenommen.

Wahrnehmungsorgane für Veränderungsdruck

Ich denke, es mangelt an Wahrnehmungsorganen für Veränderungsdruck. Das hauptsächlich ausgeprägte ist das Ohr für den Kunden. Wenn er sich räuspert, dann wird die Veränderungsmaschinerie angeworfen. Dann sind Veränderungen 0. Ordnung gefragt - egal wie.

Aber wo Veränderungsdruck von außen kommen kann, kann er natürlich auch von innen kommen. Veränderungen n-ter Ordnung können "von innen" Druck auf andere Ebene ausüben, d.h. Veränderungen (n-1)-ter oder (n+1)-ter Ordnung motivieren. Doch diese Form von Druck muss man natürlich wahrnehmen können.

Veränderungen erzeugen Unterschiede. Manche dieser Unterschiede können dann einfach so groß sein, dass sie einen Unterschied auf anderer Ebene nach sich ziehen sollten. Quantitative Unterschiede sind dafür gute Kandidaten. Ein Beispiel aus der Verkehrswelt: Solange es nur wenige Autos gab, regelte der Verkehr sich quasi von selbst. Nur durch eine größere Quantität der Autos war es dann jedoch ab einem bestimmten Punkt nötig, explizite Regelungen einzuführen. Verkehrspolizisten und Ampeln waren die Folge. Veränderungen n-ter Ordnung (Zahl der Autos) zogen also Veränderungen (n+m)-ter Ordnung (Regelungskonzept) nach sich.

Warum sollte Software davon ausgenommen sein? Wenn bei einer Softwaregröße g zu einem Zeitpunkt tg Systeme und Prozesse auf allen Ebenen passend waren, dann heißt das doch ganz selbstverständlich nicht, dass bei einer Softwaregröße h>g zum Zeitpunkt th>tg dieselben Systeme und Prozesse immer noch passend sind.

Dasselbe gilt für weniger konkrete Umwelteinflüsse als funktionale Kundenanforderungen. Zeitrahmen oder Budget sind Umwelteinflüsse, denen ein Softwaresystem auf allen Ebenen angepasst sein sollte. Ändern sich Zeitrahmn und Budget, so kann das eigentlich nur durch Veränderungen unterschiedlicher Ordnung kompensiert werden.

Vom Entwickler bis zum Chef sollten daher alle Beteiligten bei jeder Veränderung in der Umwelt fragen, Veränderungen welcher Ordnungen dadurch für das Projektteam bzw. Softwaresystem angezeigt sind. Wahrnehmungsorgane für Veränderungsdruck auszubilden ist also gar nicht so schwierig. Eigentlich genügt es zunächst, Unterschiede nicht nur in der Umwelt, sondern auch "im System" überhaupt ersteinmal wahrzunehmen. Auch und vor allem, wenn sie nicht antizipiert oder selbst verursacht sind.

Ist es schwieriger geworden, neue Features einzubauen? Nimmt die Menge an Code zu, von der nicht mehr ganz klar ist, wozu sie dient? Steigt der Aufwand, um Performancelecks zu finden? Meldet der Support Fehler, die eigentlich schon länger behoben sein sollten?

Das sind Fragen, die, wenn mit Ja beantwortet, Unterschiede aufzeigen, die Veränderungsdruck darstellen, auch wenn er nicht unmittelbar von einem Kunden ausgeht.

image

Kunden (und ihre Stellvertreter wie Verkauf oder Chef) üben Veränderungsdruck auf Software aus. Aber je älter und größer eine Software wird, desto mehr Druck übt sie auf sich selbst aus. Das ist dann Druck von innen.

Solcher Druck ausgehend von Ebene n kann dann nahelegen, Veränderungen (n+1)-ter oder (n-1)-ter Ordnung durchzuführen. Manchmal ist seine Reichweite aber auch größer. Ändert sich das Vorgehensmodell (4. Ordnung), hat das wahrscheinlich Auswirkungen auf die Produktion (3. Ordnung) oder gar auf die Architektur (2. Ordnung). Umgekehrt können neue architektonische Konzepte (2. Ordnung) wie Contract-first Design Veränderungen am Produktionsprozess (3. Ordnung) nach sich ziehen.

Für unterschätzt halte ich in jedem Fall den Veränderungsdruck von innen, der durch "Massezuwachs" (oder Komplexitätszuwachs) über die Lebenszeit eines Projektes entsteht. Und der Druck, den immer knappere Zeitvorgaben und volatile Kundenwünsche ausüben, schlägt sich noch nicht in angemessenen Änderungen der Ordnung 2-4 nieder.

Die Übersetzung von Druckwahrnehmung (wie bewusst oder unbewusst auch immer) in Veränderungen unterschiedlicher Ordnung geht also oft schief.

Freitag, 14. Dezember 2007

OOP 2008: Gesetze der Softwareentwicklung III

Abgesehen mal von der Frage, was eine gute, nein, eine optimale SOA ist, lohnt auch die Frage, wie denn ein Unternehmen überhaupt zu dieser SOA kommen kann? Ich glaube, zu ihrer Beantwortung können einige "Gesetze der Softwareentwicklung" beitragen.

Gesetz 1 sagt, dass optimale Strukturen, also auch optimale serviceorientierte Architekturen überhaupt nur für bekannte Anforderungen entworfen werden können. Das mögen Sie als trivial ansehen, dennoch ist es hilfreich, diese Basislinie zu haben. Und keine Sorge, Gesetz 2 macht die Sache schon spannender: Selbst wenn es für gegebene Anforderungen eine oder wenige gleichwertige optimale Strukturen geben sollte, dann lassen sich die nicht aus ihnen ableiten. Unser Verständnis bekannter Anforderungen ist einfach zu begrenzt. Ja, ich meine, dass wir selbst vorliegende Anforderungen allermeistens nur unzureichend verstehen; von den (noch) unbekannten Anforderungen, d.h. davon, dass Anforderungen immer im Fluss sind, also niemals vollständig bekannt, rede ich gar nicht erst.

Das glauben Sie nicht? Sie meinen, wasimmer Sie aus einem Kunden mit der Hebammenkunst des Requirements Engineering herausgekitzelt haben, verstehen Sie auch in seinen Implikationen und können deshalb eine optimale Struktur dafür entwerfen. Gut, dann unterliegt das Ergebnis der Implementation dieser optimalen Struktur immer noch

Gesetz 3: Optimale Strukturen werden nie verlustfrei implementiert

Sie kennen das Spiel: Am Anfang stehen gute Vorsätze, aber was aus ihnen wird, ist eine andere Sache. Wie Clausewitz schon sagte: Kein Plan überlebt den Kontakt mit dem Feind. Eine optimale Struktur ist aber ein Plan, ein Vorsatz für die Realisierung von Software. Und der Feind... der ist das Tagesgeschäft. Am Anfang einer Iteration/eines Release sind Motivation und Zuversicht groß. Am Ende, wenn es auf die Auslieferung zugeht, ist die Motivation hoffentlich immer noch groß, aber die Zuversicht hat Dämpfer bekommen. Meist werden Sie am Ende einer Timebox nicht die geplanten Features ausliefern. Sie werden (hoffentlich) immer noch Wert für den Kunden produziert haben, aber nicht den, den Sie anfänglich geplant hatten. Das betrifft nicht nur die Zahl der Features, sonder vor allem die interne Struktur ihrer Implementation.

Der Grund dafür ist einfach: Es existiert ein Wertekonflikt. Auf der einen Seite der Wert "Funktionalität für den Kunden" und auf der anderen Seite "Qualität der Implementationsstruktur". Und welcher dieser Werte wird, wenn die Zeit knapp ist, höher geschätzt? Sie wissen es aus eigener Erfahrung nur allzu genau. "Funktionalität für den Kunden" ist im Zweifelsfall immer der Gewinner. Wenn die Uhr merklich tickt, dann verfallen wir nicht nur in physischen Gefahrensituationen auf Verhaltensweisen, die sich seit der Steinzeit eingeschliffen haben, sondern auch wenns im Virtuellen brenzlig wird. Copy&Paste als Reflexhandlung, hier noch schnell etwas dazustricken, damit es läuft, dort mit "Ja, ich weiß, das ist jetzt nicht optimal und nicht so, wie wir´s geplant hatten, aber es funktioniert halt erstmal." entschuldigen. So funktioniert Softwareentwicklung in immer engen Zeitplänen auf der ganzen Welt. Nicht schön, aber is´ halt so.

Der Kunde ist König. Der Kunde sieht keine internen Strukturen. Also wird poliert, was der Kunde sieht und wünscht. Ob die langfristig optimalen Strukturen dabei erhalten bleiben, ist im Zweifelsfall egal. Zweifelsfälle, d.h. Entscheidungszwänge am Ende von Zeitplänen, sind allerdings die Regel. Also ist auch Suboptimalität die unvermeidliche Regel. Wider jeden besseren Willens.

In der Suboptimalität einrichten

Was bedeutet das für eine SOA? SOA, also Architektur im Großen, muss sich wie jede andere Softwarestruktur in der Suboptimalität und damit im Vorläufigen einrichten. Oder postmoderner ausgedrückt: Wer auf den SOA-Zug springt mit der Hoffnung, endlich der Wahrheit ein Stück näher zu kommen, der hechelt (wieder) einer Illusion hinterher. Es gibt sie nicht, die Wahrheit der Softwarearchitektur, die eine oder andere für das Unternehmen oder auch nur eine Applikation optimale Architektur.

Es gibt immer nur temporär angemessene Strukturen, die noch nicht einmal wie geplant implementiert werden können. Insofern ist jeder real existierender Code immer suboptimal in der einen oder anderen Hinsicht. Immer. Unausweichlich. Vor allem, solange man an ihm noch zu einem anderen Zweck als der Optimierung seiner Struktur in Bezug auf währenddessen konstante und durch langwierige Implementation inzwischen wohlbekannte Anforderungen hält.

Im Kern jeder SOA, d.h. der Manifestation der SOA-Prinzipien oder überhaupt irgendwelcher Architekturprinzipien muss daher Anpassungsfreundlichkeit stehen. Design for Change ist nie wichtiger gewesen. Design for Change ist quasi die Metaebene der Agilitätsgrundsätze.

Das bedeutet aber natürlich nicht, dass Sie keinen Plan haben sollten. Das bedeutet auch nicht, dass es keine guten oder weniger guten Architekturen für bekannte Anforderungen gibt. Es bedeutet nur, dass Sie kein Gefühl von Angekommen entwickeln sollten. Architektur ist die Kunst der Strukturierung und Ordnung des ewig Vorläufigen.

Egal, welche Technologie Sie heute einsetzen, egal, welcher Schule Sie folgen... das alles wird veralten und Ihre Implementationen sind zu erneuern.

Und was bedeutet das für schon existierende Strukturen? Dazu mehr bei Gesetz 4 der Softwareentwicklung.

Mittwoch, 17. Oktober 2007

Gesetze der Softwareentwicklung II

Das erste Gesetz der Softwareentwicklung ist scheinbar trivial und doch sehr wichtig. Nach ihm können wir nur eine "gute Software", d.h. eine Software mit wartbaren Strukturen für Anforderungen entwickeln, die wir kennen. Trivial ist das, weil es sich nicht nur selbstverständlich anhört, sondern im Grunde auch ist. Wichtig ist die explizite Formulierung dieses Gesetzes aber dennoch, weil die gängige Praxis in vielen Projekten im nicht Rechnung trägt. Denn da die Softwareentwicklung quasi schon sprichwörtlich nie alle Anforderungen kennt, sind die Softwarestrukturen immer suboptimal. Suboptimale Strukturen machen die Pflege von Software schwer und schwerer, also - so sollte man denken - investieren Projekte immer wieder Zeit, um die suboptimalen Strukturen zu verbessern. Sie würden dann quasi innehalten und für die implementierten Anforderungen die Strukturen verbesseren (Refaktorisierung), bevor weitere Anforderungen erfüllt werden. Dieses Innehalten kommt jedoch nur selten, sehr selten vor. Es scheint gegenüber dem Kunden (oder dem Chef als Vertreter der Kundschaft) keinen Wert zu haben. Deshalb wird dafür nicht systematisch Zeit eingeplant. Das Resultat: Die Struktur der Software verfällt über die Zeit. Die Entropie in ihr nimmt ständig zu, die Unordnung wächst (siehe auch Gesetz 4 der Softwareentwicklung).

Aber lassen wir diese Entwicklung von Qualität für einen Moment außer acht. Nehmen wir Gesetz 1 mal einfach nur als trivial hin. Nehmen wir auch einfach mal an, wir würden alle Anforderungen für eine Software kennen. Schaffen wir es dann wirklich, eine optimale Struktur für unsere Software zu planen? Nein.

Gesetz 1 definiert die entscheidende Voraussetzung für optimale Softwarestruktur, d.h. langlebige Software. Gesetz 1 sagt, ohne die Kenntnis aller Anforderungen kommen wir zu keiner langfristig guten Struktur. Anforderungskenntnis ist aber nur eine notwendige Voraussetzung, keine hinreichende. Denn leider gilt nicht nur Gesetz 1, sondern auch...

Gesetz 2: Optimale Strukturen lassen sich nicht aus Anforderungen ableiten

Selbst also, wenn wir die Anforderungen an Funktionalität, Performance, Wartbarkeit usw. kennen, dann können wir immer noch keine optimale Struktur für die Implementation ermitteln. Dafür gibt es zwei Gründe:

a. Auch wenn wir meinen, die Anforderungen zu kennen, kennen wir sie nicht wirklich. Wir kennen sie nicht wirklich, weil entweder der Kunde sie (noch) nicht wirklich kennt; er weiß also selbst nicht, was er will bzw.  braucht. Oder der Kunde hat die ihm tatsächlich bekannten Anforderungen - natürlich unwissentlich und ungewollt - nur unvollständig mitgeteilt. Oder es gab schlicht Übermittlungsfehler, die Anforderungen sind bei uns nicht verlustfrei angekommen; wir meinen sie zu verstehen, missverstehen sie aber in Wirklichkeit. Auf die eine oder andere Weise kann es also leicht passieren, dass wir zurück bei Gesetz 1 sind: Wir kennen die wahren Anforderungen nicht, sondern nur eine Untermenge. Damit lässt sich kein optimaler Start machen.

b. Selbst wenn wir nun aber die Kundenanforderungen wirklich, wirklich kennen sollten - was selten genug der Fall ist -, dann gibt es immer noch ein Problem bei ihrer Umsetzung: unsere Tools und Technologien. Unsere Branche ist wie kaum eine andere geprägt von ständigen Neuerungen. Materalien (APIs) und Werkzeuge (IDE, Sprachen usw.) sind in ständig wachsendem Fluss. Wir haben immer weniger Zeit, sie wirklich solide zu erlernen. Das bedeutet aber, dass wir immer wieder auf unangenehme Überraschungen bei ihrem Einsatz stoßen. Sie funktionieren nicht wie angenommen. Strukturen lassen sich aber nur für Bekannte Einsatzformen von Materialien und Werkzeugen planen. Wenn wir unseren Werkzeugkasten aber immer weniger beherrschen, er ständig wächst, dann können wir für ihn und mit ihm keine optimalen Strukturen entwerfen. Beispiel: Wer meint, der neue O/R Mapper funktioniert so und so und darauf aufbauend eine Architektur plant und dann stellt sich bei der Implementation heraus, der O/R Mapper funktioniert leider nicht so... der verlässt sehr schnell die scheinbar optimale Struktur und begibt sich in den Wald der ad hoc Anpassungen. Die widersprechen aber selbstredend jeder optimalen Struktur.

Wenn Gesetz 1 trivial erscheint, dann sollte Gesetz 2 zumindest bescheiden machen. Wir sollten nie annehmen, überhaupt die triviale Anforderung von Gesetz 1 erfüllen zu können. Es gibt genügend Gründe, warum wir entgegen jeder Annahme eben doch nicht alle Anforderungen kennen. Und selbst wenn... dann beherrschen wir unsere Materialien und Werkzeuge, die sich quasi immer im Vorläufigen bewegen, nicht wirklich. Wenn wir ein Problem nicht schon 2-3 Mal mit durchaus unterschiedlichen Technologien gelöst haben, oder wenn wir neue Probleme nicht schon 2-3 Mal mit derselben Technologie gelöst haben, dann können wir überhaupt keine optimalen Strukturen planen.

Optimale Strukturen sind nur dort möglich, wo wir sichere Kenntnis haben. Wer die Problemdomäne aber nicht 100% kennt oder wer seine Werkzeuge nicht 100% kennt, der kann nur suboptimale Strukturen entwerfen. Teams, die Auftragsentwicklungen machen, kennen die immer wechselnden Problemdomänen nicht, da ist Suboptimalität gar nicht zu vermeiden. Andere Teams machen jahrelang dasselbe; sie kennen die Problemdomäne aus dem Effeff. Sie leiden dann zumindest darunter, dass sie bei wachsenden Anforderungen keine wirkliche Konstanz in den Implementierungstechnologien haben.

Gesetz 1 und Gesetz 2 machen also deutlich, dass optimale Strukturen kaum geplant werden können. Kommunikationsschwierigkeiten und ewige technologische Unerfahrenheit stehen dem allemal im Wege.

Aber was, wenn wir mal träumen? Was, wenn wir mal ganz naiv annehmen, dass wir die Anforderungen doch wirklich, wirklich kennen und auch noch die Meister unserer Technologien sind? Dann tritt Gesetz 3 in Kraft.

Dienstag, 25. September 2007

Gesetze der Softwareentwicklung I

Ja, ja, ich weiß, bei der Softwareentwicklung ist immer alles immer wieder anders. "Es kommt darauf an!" ist die Antwort auf fast jede Frage nach Einsatz einer Technologie oder eines Konzeptes. Grundsätzlich stimmt das natürlich auch. Meistens. Oft. Wie auch sonst so im Leben.

Aber es lässt sich nicht leugnen, dass wir uns alle nach Gewissheiten und Regeln sehnen. Irgendwo muss es doch mal feste Standpunkte geben, oder? Absolute Ansatzpunkte für Hebel, mit denen wir die Welt aus den Angeln heben können.

Und tatsächlich, die gibt es. Bei mehreren Beratungsterminen in der letzten Zeit sind sie mir klar geworden. Heute während eines online Unterrichts zur Einführung in den .NET Framework (ja, damit experimentiere ich; wer Interesse an einem solchen ".NET Basics Personal Training" hat, melde sich gern bei mir per Email) ist mir dann noch ein weiterer "Baustein" eingefallen, der mich nun auch veranlasst, mal darüber zu schreiben.

Also bin ich mal so kühn und behaupte, dies seien fünf grundlegende Gesetze der Softwareentwicklung:

  1. Strukturen können immer nur optimal für bekannte Anforderungen sein.
  2. Optimale Strukturen lassen sich nicht aus Anforderungen ableiten.
  3. Optimale Strukturen werden nie verlustfrei implementiert.
  4. Strukturen, die für bekannte Anforderungen aus schon existierenden weiterentwickelt werden, sind nie optimal in Bezug auf die gesamte Anforderungsmenge.
  5. Anforderungen werden durch implementierte Strukturen beeinflusst.

Der Begriff "Struktur" steht in den Gesetzen sowohl für die grobe wie die feine Organisation. Er bezieht sich also sowohl auf Auswahl und Anordnung von Anweisungen, Methoden, Klassen, Komponenten, Prozessen, Dienste usw. Gemeint sind auch nicht nur Bausteine auf unterschiedlichen Abstraktionsebenen, sondern ebenfalls ihre Anordnung, d.h. wie sie in Beziehung zueinander gesetzt sind.

"Optimale Strukturen" sind dann Strukturen, die nicht nur die funktionalen und nicht-funktionalen Anforderungen erfüllen, sondern auch etwas Luft lassen für neue Anforderungen bzw. unvermeidliche Fehlerbehebungen. Optimale Strukturen sind verständlich und wartungsfreundlich, flexibel und testbar. Bei ihrem Anblick sollen Sie ausrufen "Toll! Einleuchtend! Ideal!". Optimale Strukturen haben insofern auch immer etwas von einem Kunstwerk. Sie sollen auch "schön" sein. Aber nur "auch", denn ihr Hauptzweck ist ja die Erfüllung von knallharten Anforderungen.

Aber ich möchte mich auch nicht an der Optimalität festklammern. Statt "optimal" können Sie auch "gut" oder "sehr gut" oder ein noch weniger wertendes "angemessen" lesen.

Was bedeuten nun aber suboptimale Strukturen? Ist es schlimm, wenn eine Software nur eine suboptimale Struktur hat? Suboptimal bedeutet ja nicht, dass die Software nicht funktioniert. Sie erfüllt die "platten", gut greifbaren funktionalen und nicht-funktionalen Anforderungen des Kunden. Aber: Suboptimale Strukturen machen es schwerer als nötig, Änderungen einzuarbeiten. Seien es Fehlerbehebungen, Änderungen an Features oder ganz neue Features. Änderungsfreundlichkeit aber ist - so finde ich - ein hohes Gut - auf das viele Projekte leider de facto zuwenig Wert legen. Direkt nach dem Wert von Änderungsfreundlichkeit gefragt, finden Projektteams sie zwar womöglich wichtig. Aber wenn man dann in den Code schaut, spiegelt sich diese Wertschätzung nicht wider. Sie ist - sorry to say - oft ein Lippenbekenntnis, dass man - soviel zur Entlastung der Softwareentwickler - gern in die Tat umsetzen würde, aber nicht zu können glaubt, weil "eine höhere Macht" (Kunde, Chef) das nicht zulässt.

Insofern mögen die folgenden "Gesetzeserklärungen" oder "Erklärungen der Gesetze" auch helfen, "höhere Mächte" eine andere Sicht auf Software zu vermitteln.

Gesetz 1: Optimale Strukturen für bekannte Anforderungen

Dass optimale Strukturen nur für bekannte Anforderungen ermittelt werden können, ist eigentlich kaum der Erwähnung wert. Interessant ist hingegen der Umkehrschluss: Zu unvollständigen oder schwammigen Anforderungen lassen sich keine optimalen Strukturen finden. Das bedeutet nämlich für die Softwareentwicklung, deren Projekte notorisch unterspezifiziert sind, dass ihre Strukturen quasi notwendig immer suboptimal sind.

Das Wasserfall-Vorgehensmodell hat versucht, sich dagegen zu stemmen. Es basierte auf der Annahme, Anforderungen ließen sich vollständig ermitteln. Wie die Agilitätsbewegung inzwischen ja aber hinlänglich klar gemacht haben sollte, ist das allermeistens eine Illusion. Sie können die Anforderungen an eine Software nie vollständig erheben. Anforderungen sind ständig in einem mehr oder weniger schnellen Fluss. Jedes Release Ihrer Software implementiert also nur eine vorläufige Menge von Anforderungen, einen Schnappschuss.

Dazu kommt noch, dass Projekte so groß sein können, dass die schiere Menge der Anforderungen Sie zwingt, sie in mehreren Schritten zu implementieren, die jeweils nur eine Untermenge abdecken. Selbst also bei grundsätzlich bekannten Anforderungen können Sie nicht immer all diese Anforderungen auch zur Strukturierung heranziehen. Es würde schlicht zu lange dauern, bis der Kunde ein erstes Ergebnis erhält.

Das bedeutet: Da zu einem bestimmten Zeitpunkt entweder nicht alle Anforderungen bekannt sind oder nur eine Untermenge von Anforderungen berücksichtigt werden kann, hat Software immer eine suboptimale Struktur auf das "große Ganze" bezogen:

  • Wenn nicht alle Anforderungen bekannt sind, dann muss die Implementierung der bekannten Anforderungen suboptimal in Bezug auf die erst später bekannte gesamte Anforderungsmenge sein. Das gilt auch, wenn die Struktur für die implementierten Anforderungen optimal ist! Siehe dazu auch Gesetz 4.
  • Wenn Sie nur eine Untermenge bekannter Anforderungen implementieren, dann mag die Struktur in Bezug auf die Untermenge optimal sein - aber nicht gleichzeitig für alle Anforderungen.

Ohne weiteren Aufwand hat Software also immer eine suboptimale Struktur. Sie ist damit weniger als möglich und nötig änderungsfreundlich. Und das wird nicht besser, wenn Sie weitere Anforderungen implementieren. Zumindest nicht, wenn Sie nicht explizit etwas dagegen tun.

Gesetz 1 beschreibt etwas Unvermeidliches: Suboptimalität. Die Qualität von Software ist immer geringer als sie sein könnte, weil wir nie alle Anforderungen im Blick haben können. Wer Software entwickelt, sollte sich also vom Gedanken der Perfektion verabschieden. Die mag sich vielleicht noch für einen Sortieralgorithmus erreichen, aber nicht mehr für eine ganze Anwendung.

Allerdings ist Gesetz 1 keine Entschuldigung für schlechte Qualität! Es macht nur klar, das wir uns nach hoher Qualität immer strecken müssen. Es gibt dabei kein Ankommen. Zwischen dem, was ist, und dem, was am besten wäre, ist immer eine Lücke. Hoffentlich wird die immer kleiner im Verlauf eines Projektes, doch schließen können Sie sie nicht.

Insgesamt hohe Qualität oder vor allem Änderungsfreundlichkeit als Grundlage für ein langes Softwareleben ergibt sich damit nicht einfach so, sondern erfordert bewussten Energieeinsatz. Akzeptieren Sie Suboptimalität, aber stemmen Sie sich gleichzeitig dagegen.

Mehr dazu im nächsten Posting.