Follow my new blog

Posts mit dem Label Softwarephilosophie werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Softwarephilosophie werden angezeigt. Alle Posts anzeigen

Mittwoch, 20. August 2014

Konstruktivistische Softwareentwicklung für mehr Wandelbarkeit

Code sei ein Mittel, um die Realität abzubilden. Zumindest scheint mir das ein wesentlicher Antrieb hinter der Objektorientierung zu sein. Mit Objekten sollte es endlich möglich sein, Dinge und Verhältnisse der Welt möglichst 1:1 in Code zu fassen.

Das ist eine platonische Sichtweise der Welt. Sie geht davon aus, dass es etwas unabhängig von Code gibt, das irgendwie ist. Und dieses So-sein kann man mehr oder weniger getreu in Code abbilden. Und je getreuer es abgebildet wird, desto besser ist das für die Software in irgendeiner Weise.

Ich glaube, dieses Weltbild sollten wir nun hinter uns lassen. Es hat uns zu der Software geführt, die wir haben. Die funktioniert, die skaliert - aber die ist nur schwer wandelbar.

Vom Platonismus sollten wir zum Konstruktivismus wechseln.

Wenn wir konstruktivistisch Software entwickeln, dann ist es nicht mehr wichtig, eine Realität getreu abzubilden.

Bei der platonischen Softwareentwicklung gibt es drei Bereiche, die in Deckung sind: die Realität (Domäne), unsere Wahrnehmung der Realität (Entwickler), Code. Als Entwickler einer Buchhaltungssoftware sehe ich eine Rechnung (Domäne) und lege dafür eine Klasse Rechnung und eine RDBMS-Tabelle Rechnungen an.

Bei der konstruktivistischen Softwareentwicklung hingegen, muss das, was ich als Entwickler in der Realität erkenne, nicht im Code auftauchen. Auch wenn ich eine Rechnung als Gegenstand in der Hand halte, führt das nicht Zwangsläufig zu einer Klasse oder einer einer Tabelle.

Leider muss ich hier von drei Bereichen sprechen, weil Software sich nicht selbst anpassen kann. Wir als Entwickler müssen das als Mittler tun. Deshalb haben wir bisher auch versucht, unsere Sicht der Welt in der Software zu manifestieren.

Aber es geht gar nicht um uns. Wenn wir für unser Überleben Rechnungen, Autos, Katzen, Götter, ein Ich in der Welt sehen wollen, dann ist das unsere Sache. Das ist unsere Konstruktion, die über das gelegt ist, was irgendwie ist.

Der Stoff aus dem die Konstruktionen sind, sind unsere sinnlichen Wahrnehmungen und a priori Grundkonzepte. Die haben wir uns nicht ausgesucht, sondern sie definieren uns. Die Evolution hat dazu geführt, dass wir als Organismen so sind, wie wir sind. Mit unserer Form und unseren Wahrnehmungen haben wir größere Stabilität in einer bestimmten Umwelt erreicht als mit einer anderen Form und anderen Wahrnehmungen.

So ist das mit Evolution. Es geht um größere Stabilität von Strukturen in einer (Um)Welt.

Und so ist es auch mit der Softwareevolution. Es geht darum, wie Software größere Stabilität erreicht in einer stetig im Wandel befindlichen Umwelt.

Wir haben es nun einige Jahrzehnte versucht, die Lebensfähigkeit durch getreuer Abbildung von durch uns wahrgenommener “dinglicher” Realität zu erhöhen. Das hat nicht so geklappt, wie gewünscht, würde ich sagen. Und nur das zählt.

Also sollten wir es anders versuchen. Befreien wir Software vom Abbildungszwang. Das einzige was zählt ist, dass Software als Ganzes ihren Zweck erfüllt (Funktionalität + Qualität) und sich zügig an eine gewandelte Umwelt anpassen lässt (Investitionssicherheit).

Die Struktur von Software muss also nicht zwangsläufig irgendetwas widerspiegeln, was wir als Menschen als Dinge in der Welt erkennen. Wenn wir ein Formular auf dem Tisch liegen haben, dann mag es noch hilfreich sein, das Formular in der Software als geDialog wiederzufinden. Ja, vielleicht ist das so. Vielleicht aber auch nicht. Machen wir uns da mal ganz locker.

Vor allem sollte uns ein Formular auf dem Tisch nicht dazu verleiten, unterhalb der Oberfläche im Code das Formular nochmal zu erschaffen. Und dann ein weiteres Mal auf der Festplatte - weder als Verbund von RDBMS-Tabelle noch als NoSql Dokument.

Damit will ich nicht sagen, dass das nicht so sein darf. Vielleicht ist es hier und da vorteilhaft, das Formular im Code wiederzufinden. Aber wir sollten das sich ergeben lassen und nicht im Sinne eines Weltbildes an den Anfang setzen.

Wenn wir schon mit dem Paradigma unserer Hauptwerkzeuge, den Programmiersprachen, Softwarestrukturen einem platonischen Weltbild unterwerfen, dann schränken wir die Freiheit der Evolution von Software ein. Wir machen es ihr schwer, in einer fluktuierenden Umwelt zu überleben, weil wir sie massiv fixieren.

“Abbildung der Realität” ist aber nicht, worum es geht. Einziger Markstein ist Zufriedenheit des Kunden - zu der gehört, dass Software quasi unsterblich ist, weil sie sich auf ewig anpassen lässt.

Natürlich haben wir wenig Erfahrung mit der Herstellung von Unsterblichen. Wer hat solche Erfahrung schon? ;-) In jedem Fall scheint mir jedoch ein schlechter Ausgangspunkt dafür, die Abbildung von objektbeladener Realität. Denn: Wenn sich diese Objekte in der Realität andauernd ändern, dann muss sich ja auch die Software andauernd ändern. Das umso häufiger und breiter, je tiefer die Objekte der Realität in der Software verankert sind.

Mir scheint, wir brauchen für evolvierbare Softwareentwicklung nicht mehr Objektorientierung, sondern das Gegenteil: Anti-Objektorientierung.

Java und C# und Ruby müssen wir deshalb nicht sofort auf den Müll werfen. Letztlich sind die Sprachen unschuldig. Auf den Müll muss das kontraproduktive platonische Weltbild. Denn das Weltbild steuert, wie wir die Sprachen einsetzen.

Mit einem neuen Weltbild können wir auch mit den überkommenen Werkzeugen durchaus neue, besser passende Strukturen schaffen. Konstruktivistische Softwareentwicklung ist mit C#, Java, Ruby, JavaScript, F# usw. möglich. Mal leichter, mal schwerer.

Hören wir also auf, das Innen der Software so zu strukturieren, wie wir meinen, dass die dingliche Realität aussieht. In Ihrem Gehirn finden Sie den Computer nicht, auf den Sie schauen. Es darin auch nicht den Stuhl, auf dem Sie sitzen. Oder den Raum, in dem Sie sich befinden. Weder die Anatomie des Gehirns noch die Signale zwischen den anatomischen Strukturen haben irgendeine Ähnlichkeit mit der Umwelt. Innen ist nicht wie außen.

Allerdings befähigt Sie der Aufbau Ihres Gehirns (plus Körper), als Ganzes in der Umwelt zu überleben. Nur das ist es, was zählt.

Genau das müssen wir für Software auch erkennen. Ein erster Schritt: EventSourcing und EventStores.

Ein EventStore löst realweltliche Strukturen auf. Sie finden sich in der Software nicht mehr so wieder, wie wir sie als Entwickler in der Domäne gezeigt bekommen. Aber das ist nur der Anfang.

Ein nächster Schritt: Bounded Contexts und Aggregate.

Bounded Contexts und Aggregate lösen die Vorstellung von dem einen Datenmodell, von der einen Datenstrukturrealität auf.

Und noch ein Schritt: Inkrementelle Architektur. Das ist für mich die grundlegende Orientierung von Softwarestruktur an Durchstichen und Nutzen statt an Technologien, Infrastruktur und überkommenen Patterns (z.B. MVC, Layers).

Inkremente lösen die Vorstellung auf, dass es um Dinge ginge bei der Softwareentwicklung. Nicht jedoch das Dokument ist das Wichtigste, sondern der Prozess. Der ändert sich zuerst, ihm folgt ein eventuelles Dokument. Aber der Prozess, die Tätigkeit, das Verhalten sind weniger greifbar, sind keine Dinge. Deshalb tut sich die platonische Softwareentwicklung mit ihnen schwer.

Weitere Schritte werden wir noch erkennen, denke ich. Wenn wir uns darauf einlassen, unser Weltbild bzw. das von Software umzubauen.

In der Psychologie hat der Konstruktivismus “gewonnen”. Ich denke, davon sollten wir lernen als Softwareentwickler.

Was außerhalb von Software existieren mag, ist eine Sache. Eine ganz andere ist es, wie Software intern organisiert ist. Ähnlichkeit muss es zum Außen nicht geben. Nur Überlebenstauglichkeit. Dafür braucht es vor allem… Wandelbarkeit.

Donnerstag, 23. Juni 2011

Why UML got it wrong - Abstraktion vs Modell

Jetzt hab ich es endlich, was mich an der UML stört. Es ist nicht mal ihre Aufgeblasenheit, sondern ihr falsches Versprechen. Die UML verspricht eine Vereinheitlichung von Modellierungsansätzen. Doch leider ist viel weniger Modellierung drin als suggeriert, wo Modellierung so fett gedruckt drauf steht.1

Dazu muss ich aber erklären, wie ich Modellierung verstehe – und was die UML stattdessen bietet.

Abstraktion

Bevor ich zu Begriff Modell komme, zunächst etwas über einen anderen Begriff, der etwas mehr Aufmerksamkeit verdient: Abstraktion. Wikipedia sagt, Abstraktion sei der “induktive[] Denkprozess des Weglassens von Einzelheiten und des Überführens auf etwas Allgemeineres oder Einfacheres”.

Abstraktion hat also mit Detaillierungsgrad zu tun. Wenn die Darstellung von etwas einmal mehr und einmal weniger Details enthält, dann ist sie einmal weniger und einmal mehr abstrakt. Das Gegenteil von abstrakt/allgemein ist also detailreich/konkret.

Einige Beispiele für abstrakt(er) vs konkret(er):

imageimage

imageimage

imageimage

image image

Darstellungen auf unterschiedlichem Abstraktionsniveau haben also mehr oder weniger Details. Dazu kommt allerdings noch eine vermeintliche Feinheit: Bei der Abstraktion bleibt man in der Domäne. Im ersten Beispiel (Karte) ist in der abstrakten wie in der konkreten Darstellung bei allem Unterschied im Detailreichtum von denselben Dingen die Rede; es geht um Straßen, Gewässer, Häuser usw. Dito beim letzten Beispiel: auch in der sehr abstrakten Schemazeichnung des Motors geht es um dieselben Dinge wie Zylinder, Kurbelwelle, Zündkerzen usw. wie beim konkreten Motor.

Bei gegebener Domäne stellen wir mit dem Abstraktionsregler also “nur” den Detailreichtum einer Darstellung ein. Manchmal ist das einfach, wie bei Karte oder Schemazeichnung. Manchmal schwieriger wie beim Artenbaum (Beispiel 2); Pflanzenarten sind Verallgemeinerungen von Merkmalsmengen. Die mussten erstmal gefunden werden. Von einer konkreten Straße auf eine bunte Linie zu abstrahieren, ist einfacher, als von tausenden Pflanzen auf ihre Arten.

Am Ende ist der Artenbaum jedoch auch “nur” eine Abstraktion. In ihm geht es um Pflanzen wie bei der Betrachtung einer einzelnen Rose.

Modell

Bisher bin ich mit dem Begriff Modell zu leichtfertig umgegangen. Als Beispiel habe ich oft eine Karte herangezogen. Das ist genau genommen aber falsch. Darum geht es mir in diesem Artikel.

Modelle sind auch detailreduzierte Darstellungen von etwas Konkretem. Doch sie sind anders als Abstraktionen, weshalb sie einen anderen Begriff verdienen. Modelle verlassen zur Vereinfachung der Darstellung die Domäne des Beschriebenen. Das “Material”, aus dem Modelldarstellungen sind, ist sozusagen ein anderes als bei Abstraktionen.

In der Abstraktion einer Stadt kommen Straßen, Gebäude, Wasserflächen vor; in einem Modell einer Stadt wäre das nicht so. In der Abstraktion eines Motors kommen Zylinder und Kurbelwelle vor, in einem Modell eines Motors nicht.2

Abstraktionen dienen der Verständlichmachung, dem Überblick. Die Vielfalt der Details wird reduziert, damit das Konkrete sozusagen in unseren Kopf passt. Wenn das reicht, dann ist es ok. Dann arbeiten wir mit Abstraktionen, um konkrete Problemlösungen zu entwerfen. Abstraktionen sind ein altes Mittel der Planung. Allemal Karten gibt es schon viele Jahrtausende.

Was aber, wenn die Abstraktion nicht reicht? Dann muss ein anderes Verallgemeinerungsmittel her, ein Modell. Natürlich beschreibt ein Modell wieder etwas Konkretes – doch mit anderen Mitteln. Hier einige Beispiele für Modelle:

imageimage

imageimage

imageimage

Dem Modell von einem Pendel ist nichts “Pendelhaftes” anzusehen, dem Zustandsautomaten ist nichts “Maschinenmäßiges” anzusehen; und selbst das neuronale Netz ist keine Abstraktion eines realen neuronalen Netzes, sondern ein Modell, weil darin keine Neuronen mehr vorkommen.

Modelle bilden das Modellierte natürlich ab, sie enthalten eine Essenz daraus. Modelle sind also an das Konkrete gebunden. Sie erfüllen darauf bezogen ja einen Zweck. Deshalb ist irgendwie natürlich das Konkrete auch im Modell vertreten. Allerdings nicht einfach durch Reduktion des Detailgrades, also eine veränderte Quantität, sondern durch eher durch Analogie, also eine andere Qualität.

Die Abstraktion Karte hat den Anspruch, die Realität zu sein – nur nicht komplett. Ein Modell hingegen hat nur den Anspruch wie ein Ausschnitt der Realität zu funktionieren oder ihn zu erklären.

Das wird deutlich, wenn ich ein Modell einer Abstraktion gegenüberstelle:

imageimage

Das Konkrete ist Deutschland mit seinem ganzen Leben und Weben. Das ist natürlich hier nicht darstellbar, also abstrahiere ich mit einer Karte. Das Organigramm für die Verwaltungsstruktur der Luftfahrt bezieht sich auf einen winzigen Ausschnitt des Konkreten – allerdings nicht mehr mit Mitteln der Abstraktion. In den Kasten “Deutscher Wetterdienst” kann man nicht hineinzoomen, da kommen nicht irgendwann Gebäude und dann der Deutsche Wetterdienst in de Blick. Es gibt nicht einmal einen konkreten Ort im Gewusel Deutschlands, an dem der Wetterdienst einzig existiert. Das Organigramm ist daher “nur” ein Modell, es erklärt eine Facette von Deutschland.

UML und die Modellierung

Mit diesen Definitionen von Abstraktion und Modell in der Hand ein Blick auf die UML 2.0. Sie definiert eine ganze Reihe von Diagrammen für die sich zu prüfen lohnt, ob sie tatsächlich etwas mit Modellierung zu tun haben.

Dazu ist natürlich erst einmal festzustellen, was denn modelliert werden soll. Was ist das Konkrete? Denn zwischen Modell und Abstraktion kann nur in Bezug auf Konkretes unterschieden werden.

Für mich als Softwareentwickler ist das Konkrete Quellcode und all die Artefakte, die ich sonst noch erzeuge (z.B. Datenbank, Datendatei, Assembly).

Wie steht es mit den UML Diagrammtypen in dieser Hinsicht?

Diagrammtyp Einsatzgebiet
Anwendungsfälle Modell
Klassen und Objekte Abstraktion
Beziehungen Abstraktion/Modell
Pakete Abstraktion
Deployment Abstraktion
Komponenten Abstraktion
Zustandsdiagramm Modell
Kommunikationsdiagramm Abstraktion
Timingdiagramm Abstraktion
Aktivitätsdiagramm Abstraktion
Interaktionsübersicht Abstraktion
Sequenzdiagramm Abstraktion

Hm… das sind nicht viele modellierungsrelevante Diagrammtypen in der UML. Sie sollte vielleicht besser UAL heißen: Unified Abstraction Language ;-)

Achtung: Das ist kein Qualitätsurteil. Abstraktion ist sehr nützlich. Die abstraktionsrelevanten Diagrammtypen haben ihren Wert. Nur sollte man sie eben nicht mit Modellierungswerkzeugen verwechseln.

Wenn das Konkrete Klassen, Methoden, Objekte, Objektreferenzen etc. sind, dann sind alle Diagramme, in denen Klassen, Methoden, Objekte, Objektreferenzen etc. vorkommen – egal in welchem Detaillierungsgrad – “nur” Abstraktionen.

Auch nett, nur eben keine Modelle. Ihnen fehlt also etwas, das wir in Modellen suchen.

Warum Modelle so wichtig sind

UML ist deshalb für mich knapp daneben, weil sowenig “echte” Modellierung drin steckt. Schön, dass wir Code damit abstrakter aus verschiedenen Blickwinkeln darstellen können – aber um Kompliziertes, nein, Komplexes entwerfen und darstellen zu können, ist mir das zuwenig.

Abstraktionen bieten mir schlicht, ähm, zuwenig Abstraktion :-) Bei Abstraktionen bin ich immer gezwungen, in der Domäne des Konkreten zu bleiben. Das bedeutet, ich muss mich ewig mit Klasse, Methoden, Objekte, Assemblies herumschlagen.

Wenn die damit sonst in der Welt zufrieden gewesen wären, hätten wir immer noch einen technischen Stand auf dem des Barock. Mit der Mathematik als universeller Modellierungssprache jedoch… damit konnten wir im wahrsten Sinn des Wortes abheben.

Und dasselbe ist uns auch schon in der Softwareentwicklung gelungen. SQL und reguläre Ausdrücke sind aus meiner Sicht Beispiele dafür, oder Prolog, neuronale Netze, Zustandsautomaten. Mathematische Ansätze und DSLs haben uns in einigen Bereichen sehr voran gebracht. Heute kann jeder einen Compiler mit ANTLR zusammenbasteln dank Zustandsautomaten und EBNF. Und auch die Abfrage komplizierter Datenbestände ist mir SQL kein Hexenwerk.

Für die meisten Softwareproblemstellungen gibt es jedoch kein allgemein anerkanntes Modellierungsverfahren. Und damit meine ich nicht einmal, dass Code soweit modelliert werden soll, dass man am Ende keine Zeile C# mehr schreiben muss.

Schade. Denn Modellierung spart viel Zeit. Was man nicht modelliert, das muss man sofort konkret umsetzen. (Naja, ein bisschen Hilfe durch eine Abstraktion kann auch noch dabei sein.) Am schwierigsten ist es aber immer, das Konkrete richtig hinzukriegen.

Zwar zählt am Ende nur das Konkrete, der funktionierende Motor, die laufende Software. Aber wenn man immer nur auf dem Konkreten rumrutscht, sind Veränderungen mühsam. Das ist, als hätte man keine Karte und bewegt sich in unbekanntem Terrain. Das geht – aber es ist mühsam.

Feldherren sitzen daher traditionell erhöht oder haben heute Satellitenunterstützung. Im konkreten Schlachtengetümmel lassen sich einfach manche Entscheidungen nur schlecht treffen.

Abstraktionen bieten, wie gesagt, mir nicht genug. Sie fesseln mich zu sehr an das Konkrete. Wenn ich eine Softwarelösung plane, will ich so lange wie möglich nicht über Details wie Klassen oder technologische Feinheiten wie WCF nachdenken. Nicht “gar nicht”, sondern nur “solange wie möglich nicht”.

UML erinnert mich einfach früher als hilfreich immer wieder an Details. Deshalb sind mir UML-Abstraktionen zu wenig; ich will echte Modelle.

Wie könnte es anders sein – der aufmerksame Leser meines Blogs wird es sich schon gedacht haben ;-) - scheint mir nun Flow-Design solche Modellierung zu ermöglichen. Denn damit kann ich Funktionalität beschreiben, ohne an Codekonkretheit denken zu müssen. In einem Flow-Design Modell tauchen keine Klassen auf. Die Übersetzung in Code ist ein separater Schritt. Darin gleicht Flow-Design SQL oder Zustandsdiagrammen.

Dass ein Flow-Design kein vollständiges Programm beschreibt, sondern nur einen Grobentwurf darstellt, ist dabei kein Nachteil, sondern eine Tugend. Modellierung soll Codierung nicht ersetzen, sondern sie erleichtern. Sprache wie C# sind zur Beschreibung mancher Lösungsaspekte gut geeignet, für andere nicht. (Wer anders denkt, sieht in 3GLs schon eine Silberkugel.) Also scheint es mir konsequent, C#, F#, VB, Java, C++ usw. zu komplementieren. Abstraktionen sind dafür aber nicht geeignet, weil sie ja die Domäne der Sprachen nicht verlassen. Also müssen Modelle her.

Die Frage ist für mich deshalb nicht, ob wir zu unseren 3GLs noch Modelle brauchen, sondern welche. Flow-Design ist mal ein Vorschlag, der für mich und viele andere vielversprechend ist.

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

PS: “Why UML got it wrong” schießt natürlich über das Ziel hinaus. Aber “Why UML is not enough” klingt nicht so knackig ;-) Für etwas mehr Aufmerksamkeit habe ich mir daher eine polemische Vereinfachung erlaubt.

Fußnoten

1 Ob die UML überhaupt eine Sprache ist (“”) oder nicht vielmehr ein Konglomerat an Sprachen, ließe sich auch diskutieren. Ebenso, ob es überhaupt sinnvoll ist, graphische Beschreibungsmittel für so unterschiedliche Aufgaben wie Spezifikation, Konstruktion und Dokumentation unter einem Dach zusammenzufassen. Aber davon vielleicht ein andermal.

2 Hier wird deutlich, dass ich offensichtlich etwas anderes mit Modell meine als ein Modellbauer. Wer das Modell eines Motors zusammenbastelt, hält natürlich Zylinder und Kurbelwelle in der Hand. An dieser Stelle würde ich einen Modellbausatz für einen Motor deshalb nicht Modell nennen, sondern Abstraktion. Wer mit Kleber und Farbe an einem Motor aus Plastik im Maßstab 1:10 bastelt, der hat einen “Abstraktionsbausatz” vor sich.

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.

Samstag, 1. Januar 2011

Kein Verständnis ohne Überprüfung – Warum Wasserfallentwicklung nicht funktionieren kann

imageGerade lese ich das Manuskript eines Buches, das demnächst im dpunkt.verlag erscheinen wird. Man hat mich nach meiner Meinung danach gefragt und ich finde es spannend. Die Autoren schauen durch die Brille der Psychologie unter fragen kritisch, inwiefern liebgewonnene oder gehypte Praktiken eigentlich auf einem wissenschaftlichen Fundament ruhen. Das finde ich gleichermaßen wichtig wie erhellend. Schön, dass in der letzten Zeit die softe Seite der Softwareentwicklung zunehmend Beachtung findet. Und der dpunkt.verlag tut sich da in Deutschland besonders hervor.

Aber mir geht es nicht um eine Buchrezension, vielmehr möchte ich Gedanken des Buches zum Anlass nehmen, mich selbst nochmal systematisch mit dem Vorgehen in der Softwareentwicklung zu beschäftigen. Was tun wir denn da eigentlich, wenn wir Anforderungen in Software gießen? Und wenn geklärt ist, was wir da tun, wie wird das am besten getan?

Meine These: Softwareentwicklung ist verlustbehaftete Kommunikation unvollständiger Daten. Deshalb ist Softwareentwicklung so besonders.

Datenübermittlung systematisch

Softwareentwicklung setzt Ideen im Kopf des Kunden um in Software, die der Kunde nutzt. Es geht also um eine Transformation von Ideen in ein Produkt. Naja, das ist noch nichts Besonderes; nichts anderes passiert in einem Restaurant, in dem ein Koch eine Idee für ein neues Gericht hat und am Ende liegt etwas auf dem Teller des Gastes. Auch da wird eine Idee in ein Produkt transformiert. Dennoch glaube ich, dass es nützlich ist, genau hinzuschauen und den Prozess der Softwareentwicklung einmal zu visualisieren.

Anfangen möchte allerdings mit simpler Datenübermittlung. Wenn einer “Daten im Kopf hat” die er einem anderen übermittelt, damit sie “in dessen Kopf sind”, dann sieht das so aus:

image

Der Sender, der mit den Daten im Kopf, kann seine Gedanken leider nicht per Telepathie dem Empfänger zukommen lassen. Also muss er sie in ein Medium verpacken (enkodieren). Dieses Medium interpretiert (dekodieren) der Empfänger; dadurch entsteht bei ihm eine eigene Vorstellung von dem, was der Sender im Kopf hat.

Wie das Bild zeigt, ist diese Datenübermittlung nicht problemlos. An mindestens zwei Punkten kann es zu Fehlern kommen:

  • Es kann dem Sender passieren, dass er seine Daten nicht 100% korrekt auf das Medium überträgt. Oder vielleicht ist der Sender schuldlos und das Medium schlicht nicht reichhaltig genug, um die Daten 1:1 zu repräsentieren. In jedem Fall kann es leicht passieren, dass das Medium am Ende nur eine verarmte/reduzierte Version der ursprünglichen Daten transportiert.
  • Und dann kann es natürlich dem Empfänger passieren, dass er die Daten im Medium inkorrekt interpretiert. Dann entsteht bei ihm eine andere Vorstellung als beim Sender. Die Daten im Kopf von Sender und Empfänger stimmen nicht überein. Allemal kann das nicht anders sein, wenn das Medium die ursprünglichen Daten nicht korrekt enthält – aus welchem Grund auch immer.

Übertragungsfehler – allemal bei menschlicher Kommunikation – sind mithin nicht zu vermeiden. Es stellt sich daher die Frage: Wie kann ein Sender eigentlich herausfinden, ob der Empfänger am Ende die beabsichtigten Daten “im Kopf hat”?

Wären Enkodieren und Decodieren ohne Probleme, das Medium reichhaltig genug, die Kommunikation also verlust und fehlerfrei… dann könnte der Sender immer sicher sein, dass beim Empfänger das vollständige Bild entsteht. Dann würde es am Ende genügen zu fragen, “Alles angekommen? Alles verstanden?” Und der Empfänger bräuchte nur mit “Ack” oder “Roger” oder “Verstanden” antworten.

Interessanterweise ist solch naive Nachfrage immer noch recht häufig zu hören. Achten Sie bei der nächsten Schulung einmal darauf oder wo immer ein Wissenstransfer stattfinden soll. Irgendwann wird da einer fragen, “Wie schaut es aus? Bis hierher alles klar?” und die kollektive Antwort der Wissensempfänger wird sein “Jo, alles klar. Es kann weiter gehen.”

Leider hat diese Antwort jedoch keinen Wert. Wissensempfänger können nicht wissen, ob sie das zu vermittelnde Wissen tatsächlich schon haben. Selbst in viel simpleren Situationen kann der Empfänger nicht einfach so wissen, ob er korrekt empfangen hat. Deshalb gibt es Prüfsummen.

image

Der Sender überträgt nicht nur seine Daten, sondern auch noch eine Prüfsumme, die sich aus den Daten errechnen lässt. Der Empfänger kann dann selbst feststellen, ob er korrekt empfangen hat, indem er mit demselben Algorithmus wie der Sender selbst die Prüfsumme berechnet und mit mitgelieferten vergleicht.

Stimmen die Prüfsummen überein, ist alles ok. Stimmen sie nicht überein, sind entweder die Daten oder die Prüfsumme inkorrekt übertragen worden. Der Empfänger muss den Sender also nur im (unwahrscheinlichen) Übertragungsfehlerfall belästigen. (Das gibt dem Sender zwar keine Sicherheit darüber, ob der Empfänger überhaupt Daten empfangen hat, aber den Fall lasse ich mal außen vor. In den Kommunikationen, um die es mir geht, ist das kein wirkliches Problem.)

Wenn für die zu übertragenden Daten eine Prüfsumme errechenbar ist, d.h. weitere Daten existieren, anhand derer der Empfänger prüfen kann, ob die eigentlichen Daten korrekt angekommen sind, dann ist Datenübertragung ohne weitere Kommunikation möglich. Dann kann der Datenstrom unidirektional vom Sender zum Empfänger fließen. Und nur im (seltenen) Fehlerfall ist ein Empfänger-Sender-Kommunikation nötig.

Solange jedoch keine Prüfsumme existiert, kann der Sender ohne eine “Spiegelung” durch den Empfänger nicht wissen, ob der die Daten korrekt empfangen hat.

image

Wenn der Empfänger während der “Spiegelung” zum Sender wird, kann es natürlich wieder zu einer verlust- und fehlerbehafteten Kommunikation kommen. Falls das Verständnis beim Empfänger inkorrekt ist, ist seine Darstellung dessen gegenüber dem ursprünglichen Sender ebenfalls notwendig inkorrekt. Darüber hinaus ist es aber wahrscheinlich, dass der Empfänger nicht einmal in der Lage ist, sein eigenes Verständnis präzise zu vermitteln. Zumindest ist das der Normalfall bei nicht trivialen Daten, also in allen menschlichen Lernsituationen.

Die Übertragung von Anforderungen aus dem Kopf eines Kunden in den Kopf eines Entwicklers kann nicht in einer Einbahnstraße geschehen. Anforderungen sind sehr komplizierte Daten, deren Enkodierung sicherlich stark verlust- und fehlerbehaftet ist. Dasselbe gilt für die Dekodierung. Das heißt, es sind viele Spiegelungen und Übermittlungswiederholungen nötig, um Anforderungen verlässlich zu übertragen. Zu glauben, es sei mit einem dicken Anforderungsdokument getan, das der Entwickler liest, versteht und in Software umsetzt, ohne großartig nachzufragen, ist naiv.

Anforderungen in Software transformieren

Am schönsten wäre es natürlich, wenn der Kunde selbst seine Software herstellen könnte. Mag sein – ist aber irreal. Soweit ist die Softwareentwicklung noch lange nicht, auch wenn Excel, Access und DSLs das manchmal suggerieren. Dennoch hier der Idealzustand im Bild:

image

Der Sender/Kunde enkodiert seine Anforderungen in eine Maschine (Software). Die transformiert dann Eingaben in Ausgaben, so wie der Kunde es sich wünscht.

Da das Encodieren auch hier natürlich fehler- und verlustbehaftet sein kann, stellt sich die Frage, wie der Maschinenbauer feststellt, ob die Maschine das tut, was sie tun soll? Ganz einfach: er muss sie mit Testeingaben füttern und schauen, ob sie daraus die zu erwartenden Ausgaben macht. Zur Definition einer Maschine gehören also nicht nur ihre Transformationsregeln (funktionale Anforderungen), sondern auch Beschreibungen zulässiger Eingaben und der daraus zu erzeugenden Ausgaben.

image

Wer eine Maschine bauen will, muss also sogar etwas mehr “im Köpfchen haben” als Regeln; er muss sehr konkret wissen, welche Eingaben erlaubt sind und in welche Ausgaben die durch eine korrekte Implementierung der Regeln in der Maschine transformiert werden sollen:

image

Damit stellt sich der reale Softwareentwicklungsprozess als Kette von Datenübermittlungen dar: der Kunde übermittel Anforderungen an einen Stellvertreter, der die Anforderungen dem Team vermittelt, das daraus eine Maschine baut, die der Kunde bzw. sein Stellvertreter prüfen müssen. Hier eine etwas verkürzte Darstellung der minimalen Entwicklungssequenz:

image

Eigentlich ganz einfach, oder? Es müssen die Anforderungen nur drei Mal verlust- und fehlerfrei enkodiert werden und zwei Mal verlust- und fehlerfrei dekodiert werden.

Entwicklung vs. Produktion

Selbst diese vereinfachte Darstellung der Softwareentwicklung sollte nun eines klar machen: Softwareentwicklung ist eben Entwicklung und nicht Produktion. Produktion ist nämlich, wenn eine existierende Maschine (oder Maschinerie) wiederholt Eingaben und Ausgaben transformiert. Die Eingaben mögen in einer gewissen, vergleichsweise geringen Bandbreite variieren und damit auch die Ausgaben. Aber letztlich läuft die Maschine(rie) ohne große Änderungen durch.

image

Das ist ja Sinn und Zweck einer Maschine(rie): Ermüdungsfrei immer wieder dasselbe tun. Ganz unkreativ – dafür verlust- und fehlerfrei.

Wenn eine Software erstmal entwickelt ist, dann ist sie selbstverständlich eine solche Maschine. Dann kann der Kunde mit ihr Ausgaben aus Eingaben produzieren. Immer und immer wieder.

Der Weg zur Software hin ist allerdings, der ist keine Produktion. Weder Kunde, noch Stellvertreter, noch Team sind Maschine(rie)n. Von ihnen kann bei aller möglicherweise vorhandenen Erfahrung nicht erwartet werden, dass sie verlust- und fehlerfrei Eingaben in Ausgaben transformieren. Enkodieren und Decodieren sind essenziell schwierig, sehr schwierig. Aus Anforderungen eine Software zu machen, ist mithin ein aufwändiger Kommunikations und Realisierungsprozess. Ansprüche, wie man sie an die Produktion stellt (z.B. gute Schätzbarkeit in puncto Geld- und Zeitaufwand), sind hier fehl am Platze. Softwareentwicklung ist vielmehr kreative Einzelstückherstellung, beginnt also immer wieder bei quasi Null.

Entwicklung als Lernprozess

Hier ein typischer Vermittlungsprozess ganz allgemeiner Art:

image

Die zu vermittelnden Daten sind kompliziert; es handelt sich um Wissen, also Fakten und Zusammenhänge. Enkodieren und dekodieren sind deshalb notwendig verlust- und fehlerbehaftet. Also ist eine Spiegelung des Empfangenen durch den Empfänger gegenüber dem Sender nötig. Und wenn der Empfänger “sich dumm anstellt” oder der Sender “sich nicht ausdrücken kann” oder schlicht die Natur der Daten auch bei kommunikationsfähigen und intelligenden Sendern und Empfängern keine Abkürzung erlaubt, dann ist eine “Datenübertragung” nur im Dialog möglich. Sender und Empfänger brauchen mehrere Iterationen, damit beim Empfänger die Daten in ausreichend hoher Korrektheit und Vollständigkeit ankommen.

Das nennt man dann Lernen.

Da es für nicht trivialen Lernstoff keine Prüfsummen gibt, braucht es einen Dialog, um Wissen vom Wissenden zum Unwissenden zu übertragen. Es geht zwar auch im Monolog, indem der Wissende sein Wissen einmalig enkodiert, z.B. in ein Buch oder ein Video, und der Unwissende dieses Medium wiederholt dekodiert und dann versucht, mit seinem Verständigs Eingaben in Ausgaben zu transformieren. Doch solches Autodidaktentum ist mindestens langwierig. Aber vor allem besteht keine Gewähr, dass der Unwissende je das Wissen des Wissenden erlangt. Denn bei aller Mühe, die sich der Wissende beim encodieren gibt, kann es zu Verlusten und Fehlern kommen. Wie aber soll dann der Unwissende aus einem löchrigen und/oder fehlerhaften Medium lückenloses und fehlerfreies Wissen dekodieren?

Wer an effektiver und effizier Wissensweitergabe interessiert ist, der stellt sich besser auf einen Dialog mit dem Wissensempfänger ein.

Jetzt konkreter für die Softwareentwicklung. Wie nun offensichtlich sein sollte, ist Softwareentwicklung Lernen. Der Stellvertreter lernt vom Kunden, der Softwareentwickler lernt vom Stellvertreter des Kunden, die Software “lernt” vom Entwickler. Mindestens drei Dialoge müssen also geführt werden, denn – darin sind sich wohl alle einige – Anforderungen sind keine trivialen Daten.

image

An den drei Dialogen gehts nichts vorbei. Die Frage ist allerdings, wie sie geführt werden sollten? Sequenziell…

image

…oder parallel/verschränkt:

image

Die Antwort sollte auf der Hand liegen: Sequenziell kommuniziert liegt eine erste Version der angeforderten Maschine nach 12 Kommunikationsschritten vor; mit verschränkten Dialogen steht eine erste Version jedoch schon nach 8 Kommunikationsschritten bereit.

Als mehrstufige Wissensvermittlung sollte Softwareentwicklung auf keiner Stufe glauben, bei endgültigem Verständnis angekommen zu sein. Deshalb ist ein Vorgehen, das erst eine Stufe abschließt, um dann die nächste zu beginnen, kontraproduktiv. Missverständnisse kann es in allen Kommunikationsphasen geben. Vielmehr ist ein zügiges Fortschreiten von der Idee zu ihrer Manifestation in einer Maschine anzustreben, um dem Ideal der Selbstentwicklung durch den Kunden (s.o.) möglichst nahe zu kommen.

Der Lernprozess ist nämlich noch unvollständig dargestellt. Die Herstellung der Maschine Software ist nicht schon abgeschlossen, wenn der Entwickler meint, den Dialog mit ihr beenden zu können. Softwareentwicklung ist vielmehr – da geht sie über das übliche Lernen hinaus – ein rekursiv gekoppelter Prozess.

image

Lernen ist bei allem Dialog nur als Einbahnstraße gedacht: Wissen wird vom Wissenden zum Unwissenden übertragen. Was der dann damit macht, ist dem Wissenden recht egal. Der Lehrer soll nur sicherstellen, dass die Übertragung verlust- und fehlerfrei ist.

Das ist anders bei der Softwareentwicklung. Da will der Wissende/Kunde hinterher mit dem Ergebnis der Übertragung, dem manifestierten Wissen, den Maschine gewordenen Anforderungen etwas anfangen. Deshalb kann der Kunde sich nicht darauf verlassen, dass die oben gezeigte Dialogkaskade – ob sequenziell oder verschränkt – bei genügend Mühe verlust- und fehlerfrei ist. Nein, der Kunde muss das Endergebnis auch noch selbst prüfen. Auch er muss mit der Maschine einen direkten Dialog führen.

image

Und dieser notwendige Dialog des Kunden mit der Software führt dann zu einer nächsten Lernkaskade. Beim üblichen Lernen steht der Lehrer nur am Anfang, seine Aufgabe ist das Senden (und die Überprüfung, ob der Empfang ausreichend ist). Bei der Softwareentwicklung steht der Lehrer/Kunde jedoch am Anfang und am Ende. Er ist gleichzeitig Sender und ultimativer Empfänger.

Das ist aber nicht insofern etwas Besonderes, als dass die Kommunikation grundsätzlich so schwierig ist vom Sender bis zum Produkt/zur Maschine. Das ist auch in anderen Situationen so. Wahrhaft besonders ist die Situation, weil die zu übermittelnden Daten so kompliziert sind.

Man mag es kaum glauben, aber es ist nicht zu leugnen:

  • Kunden kennen nicht einmal die funktionalen Regeln der Maschine vollständig, die sie in Auftrag geben.
  • Kunden kennen die Eingabemenge der Maschine nicht vollständig.
  • Kunden kennen die Ausgabemenge der Maschine nicht vollständig.

image

Das bedeutet für die Kette von Lernprozessen, aus denen die Softwareentwicklung besteht: Keine Stufe kann je sicher sein, dass sie das, was “eigentlich” gewollt ist, verstanden hat. Selbst nach einem Dialog mit seinem Sender hat der Empfänger kein wirklich, wirklich verlässliches Wissen. Kein Empfänger kann es haben, weil der ursprüngliche Sender, der Kunde, es nicht einmal hat.

Das liegt nicht an der Dummheit des Kunden, sondern an der Komplexität der Materie. Softwaremaschinen sind zum einen komplizierter als die meisten Hardwaremaschinene, zum anderen ist ihnen aber auch etwas anderes enkodiert. Softwaremaschinen sind geronnene Geschäftsprozesse und keine Materialtransformatoren. Und da Geschäftsprozesse auch ohne Computer viel schwerer greifbar sind als die Produktion von Wurst, Schuhen oder auch Häusern – schon weil Geschäftsprozesse die enthalten, also umfassender sind –, ist das Wissen des Kunden über sich notwendig lückenhaft.

Es geht also nicht ohne dass der Kunde selbst in Dialog tritt mit der Softwaremaschine, um erstens festzustellen, ob die Wissensvermittlung und Manifestation geklappt hat, und zweitens, um seine eigenen Vorstellungen in der Maschine reflektiert zu sehen. Erst durch den Dialog mit der Maschine kann der Kunde sich selbst und seine Wünsche verstehen. Software ist ein Spiegel; in ihm sieht der Kunde ein gnadenloses Abbild seines Selbstverständnisses bzw. seines Domänenverständnisses.

image

Softwareentwicklung arbeitet an der Grenze des explizit für den Kunden wissbaren. Die Wissensübertragung ist schwierig, weil Enkodieren und Dekodieren über mehrere Studen immer schwierig ist, aber insbesondere das ursprüngliche Wissen unvollständig ist. Deshalb muss die Softwareentwicklung dem Kunden schnell und häufig den Spiegel des aktuellen Softwarestandes vorhalten. Nur so kann der Kunde über sein eigenes vermeintliches Wissen reflektieren.

Prüfsummen für die Softwareentwicklung

Lehrer mögen es, wenn Schüler ihre Lernerfolge selbst kontrollieren können. Dann haben sie weniger Arbeit. Weniger Interaktionen im Dialog sind nötig. Und überhaupt mögen Sender Rückfragen nicht. Deshalb liegen Datenpaketen Prüfsummen bei.

Um in der Softwareentwicklung die notwendigen Dialoge zumindest so kurz wie möglich zu halten, wäre es also nützlich, auch hier Prüfsummen zu haben. Was sind aber Prüfsummen für die funktionalen Regelwerke, die Kunden in Software gegossen haben wollen?

Eingabe-Ausgabe-Paare sind solche Prüfsummen.

Die vornehmste Aufgabe jedes Empfängers von Anforderungen in der Kommunikationskette der Softwareentwicklung ist es daher, beispielhafte Ein- und Ausgaben zu sammeln. Sie sind wichtiger als das Regelwerk der Maschine, da dies ohnehin sehr wahrscheinlich Lückenhaft ist oder im Extremfall gar nicht vorliegt. Die letzte Wahrheit liegt daher immer bei gegebenen Eingaben mit zugehörigen Ausgaben, d.h. Akzeptanztestdaten. Ihnen muss die Aufmerksamkeit bei der Anforderungserhebung zuvörderst gelten.

Liegen Akzeptanztestdaten vor – je mehr desto besser –, kann ein Empfänger selbstständig prüfen, ob sein Verständnis des Maschinenregelwerks korrekt ist. Das senkt die Notwendigkeit für Interaktionen mit dem Sender.

Eine Maschinenbeschreibung sollte daher nicht nur ein Tripel sein – {Funktionale Regeln R, Eingabemengenbeschreibung E, Ausgabemengenbeschreibung A} –, sondern mindestens ein Quadrupel: {R, E, A, ea} – mit ea als Menge von Paaren von Elementen aus E mit zugehörigem Element aus A.

image

Spätestens der Entwickler sollte keine Anforderung entgegennehmen ohne Akzeptanztestdaten. Das spart Rückfragen bei vorgelagerten Sendern und somit Zeit wie Nerven.

Und auf ein Kunde sollte keine Entwicklung beauftragen, ohne für jede Funktionalität Akzeptanztestdaten zu spezifizieren. Solange die Entwicklung nicht kurz und knapp demonstrieren kann, dass die korrekt transformiert werden, muss dem Kunden die Maschine nicht vorgeführt werden.

Wie die korrekte Transformation demonstriert wird, damit der Kunde frühzeitig in “einen Spiegel blicken kann”, ist eine andere Frage. Jedes Mittel ist dabei recht; auf ein vollständige Software muss nicht gewartet werden. Ein spezieller Prüfstand kann auch ein probates Mittel sein – und sollte vom Kunden nicht abgelehnt werden mit dem Argument, da sei ja noch nicht alles fertig. Bei den Dialogen in der Kommunikationskaskade der Softwareentwicklung geht es um zügige Spiegelung aller möglichen Aspekte der Maschinendefinition im Kopf des Kunden. Komplette Benutzerschnittstellen sind dafür nicht unbedingt nötig.

Zusammenfassung

Softwareentwicklung ist keine Produktion im üblichen Sinn. Sie ist ein Lernprozess, unvollständiges und gar fehlerhaftes Wissen aus dem Kopf des Kunden in eine dennoch korrekte Maschine transferieren soll.

Da der Nürnberger Trichter bisher nicht erfunden wurde, ist Lernen ein mühsames Geschäft. Es bedeutet Dialog um Übertragungsverluste und –fehler bei der Wissensvermittlung zu kompensieren. Das gilt umso mehr, wenn das Lernen über mehrere Stationen erfolgen muss. Zentral für die Softwareentwicklung ist also die Kommunikations-, die Dialogkompetenz.

Eine besondere Herausforderung stellt dabei die mangelnde Qualität des expliziten Wissens im Kundenkopf dar. Spätestens sie zwingt den Prozess in eine ständige Rückkopplungsschleife mit dem Kunden. Je schneller der “anfassen kann”, was aus seinem Wissen gemacht wurde, desto eher kann er beurteilen, ob das, was er meinte zu wissen auch wirklich korrekt war. Unumgänglich ist also eine fortlaufende Prüfung von in Software manifestiertem Verständnis durch den Kunden.

Um sich selbst für die Prüfung zu entlasten, kann und soll der Kunde jedoch möglichst viele Akzeptanztestdaten beibringen. Das Entwicklerteam belästigt ihn mit einer neuen Version der Softwaremaschine nur, wenn es aufgrund dieser Testdaten sicher ist, die Anforderungen korrekt verstanden und umgesetzt zu haben. Daran sollte dem Kunden sehr gelegen sein, so dass er sich auch mit speziellen Prüfständen zufrieden gibt und nicht darauf besteht, Anforderungen nur durch die endgültige Benutzerschnittstelle zu prüfen.

Prüfstände haben den Vorteil, dass sie viel einfacher zu realisieren sind als eine Benutzerschnittstelle und auch für ansonsten “versteckte” Subfunktionalität aufgebaut werden können. Der Kunde kann sich dadurch viel zügiger “einen Spiegel vorhalten”, um sein Verständnis der Domäne zu überprüfen. Dass Kunden solchen “Blick in den Spiegel” scheuen mögen, steht auf einem anderen Blatt und vergrößert die Herausforderung Softwareentwicklung. Softwareentwicklung ist wie ein Therapeut, der mit seinen Fragen nicht an der Oberfläche bleibt, sondern sich auch ins Unterbewusste gräbt. Und wer mag das schon?

Verständnis und Einfühlungsvermögen gehören mithin auch zu den wünschenswerten Kompetenzen eines Softwareentwicklungsteams. Wer therapeutisch arbeitet, sollte sich nicht wie ein Elephant im Porzellanladen benehmen.

Und zuguterletzt: Was für Therapeuten angemessen ist, kann für Softwareteams nicht so falsch sein. Zur Softwareentwicklung gehört also auch die Reflexion, gar Supervision. Sonst ist nicht nur keine Nachhaltigkeit der unvermeidlichen eigenen Veränderung gewährleistet – die ist über Effizienz und Effektivität hinaus aber sehr wichtig für langfristigen Erfolg –, nein, Teams laufen sonst auch Gefahr, sich durch ihre Klienten nachteilig beeinflussen zu lassen. Ohne Reflexion/Supervision können auch “Softwaretherapeutenteams” in die Falle Gegenübertragung oder Neurose tappen. Aber davon ein andermal mehr…

An dieser Stelle sollte nur soviel klar geworden sein: Wasserfall oder nicht ist keine Frage von Weltanschauung, sondern eine Frage der Qualität des expliziten Wissens eines Kunden und der Kommunikationskompetenz aller Beteiligten im Softwareherstellungsprozess.

Da Menschen eine Menge wissen, aber oft sich selbst nicht kennen und dasselbe daher für Unternehmen, die aus Menschen bestehen, anzunehmen ist, sollte die Qualität des expliziten Wissens nicht allzu hoch eingestuft werden. Dasselbe gilt für die Kommunikationsfähigkeit vom Benutzer beim Kunden über einen ProductOwner bis zum Entwickler. Durchschnittlichkeit zu erwarten, scheint das Maximum. Und deshalb sind Wasserfall oder andere Vorgehensmodelle, die eher unidirektional sind und/oder viele Sender-Empfänger-Stufen bis zur Software haben, ungeeignet für die Softwareentwicklung.

Samstag, 11. Dezember 2010

Maschinen bauen, aber Software verschärfen

Softwareentwicklung kann man nicht schätzen? Die Frage und meine verneinende Antwort haben einige Diskussion ausgelöst. Das verstehe ich gut. Nicht anders hatte ich es erwartet. Glaubenssätze, auch wenn sie wieder und wieder zu Schmerzen führen, werden ungern aufgegeben. Davon weiß die Psychotherapie ein Lied zu singen.

Und um einen Glaubenssatz handelt es sich, denn die ganze Branche ist seit 50 Jahren unfähig, Schätzungen mit einer Fehlermarge von vielleicht 5-10% verlässlich abzugeben. Oder selbst wenn das möglich wäre, ist sie zumindest unfähig, diese Kunst systematisch in der Ausbildung zu vermitteln. Oder habe ich etwas übersehen?

Hier will ich allerdings dieses Fass nicht nochmal aufmachen. Vielmehr geht es mir um eine Prämisse dieser und anderer Diskussion um Software. Ich glaube nämlich, dass so manche Diskussion in die falsche Richtung geht und so manche Argumentation zu falschen Schlüssen kommt, weil der Ausgangspunkt falsch ist.

Ich glaube, dass wir in vielerlei Hinsicht nicht weiterkommen, wenn wir uns nicht nochmal fragen, was denn “die Natur” von Software ist. Wie ist sie eigentlich so ganz fundamental?

Frust mit Maschinen

Das vorherrschende Bild scheint mir das einer Maschine. Software wird als Maschine angesehen, die man vor allem produzieren muss. Der Entwurf der Maschine ist für den Laien vor allem die Anforderungserhebung. Danach müssen diese Anforderungen nur noch produziert, d.h. in Code übersetzt werden. Eine Tätigkeit, die dem Bau eines Hauses oder der Fertigung eines Motors entspricht – so meint man weithin.

image

Deshalb wird auch immer wieder nach Schätzungen gefragt. Produktionsaufwände lassen sich nämlich sehr gut schätzen.

Und deshalb wird auch immer wieder sehnsüchtig nur eines erwartet: die Ablieferung der kompletten Software. Die herrschende Vorstellung ist, mit Software kann man erst etwas anfangen, wenn sie komplett realisiert ist. (Dem widersprechen auch nicht die üblichen, Monate oder gar Jahre auseinander liegenden Meilensteine in vielen Projekten.)

image

Und aus der Vorstellung einer Maschine oder eines Gebäudes resultiert ebenfalls, dass Software bottom-up zu produzieren ist: erst das Chassis/Fundament, dann das, was darauf aufsetzt usw. Das Schichtenmodell ist dafür eine weit verbreitete Blaupause. Immer wieder bekomme ich als Berater deshalb auch Anfragen, ob ich nicht vor Aufnahme der Entwicklung einer Anwendung mal einen Blick auf das Anwendungsframework werfen könne. Das zu realisieren war ja nötig, damit man die Anwendung überhaupt beginnen kann. Ein Framework ist damit dem Stahlrahmen eines Hochhauses gleichgesetzt. Ohne den kann man nicht anfangen, die Wände hochzuziehen.

Drei Probleme, dreimal eine Maschinenanalogie als Ursache:

  • Problem 1 – Planabweichung: Schätzungen liegen immer wieder weit neben der Realität.
  • Problem 2 – Zielabweichung: Der Kunde ist eigentlich nur mit dem Ganzen zufrieden und wird daher erst spät mit der Umsetzung seiner Anforderungen konfrontiert. Das führt zu großen Differenzen zwischen Wunsch und Realität.
  • Problem 3 – Mangelnde Kommunikation: Budget wird in Frameworks und Infrastruktur verbrannt, die man für grundlegend und unverzichtbar hält, ohne dass der Kunde in der Zeit Nutzen erhält.

Weil die allgemeine Vorstellung ist, Software würde produziert, mache im Wesentlichen nur als Ganzes Sinn und müsse auch aus Schichten von Querschnitten aufgebaut werden, frustrieren wir immer wieder uns und unsere Kunden.

Das behaupte ich mal, denn ich glaube, mit einer anderen Vorstellung von Software, verschwinden diese Probleme. Software wird nicht gebaut. Oder wenn, dann ist das eine kurze Sache, die man einem Buildscript überträgt. Softwareproduktion ist quasi kostenlos. Aber Softwareentwicklung, d.h. die Planung dessen, was gebaut werden soll, die kostet kaum schätzbaren Aufwand.

Spaß mit Bildern

Wie könnte eine weniger frustrierende Vorstellung von Software aussehen? Ich glaube, mit einem einer 3D CGI-Grafik kommen wir weiter.

image

Die Umsetzung der Gesamtanforderungen können wir dann als komplett gerendertes 3D-Modell ansehen. Jedes Detail ist brilliant gerechnet.

Doch das ist erst ganz am Ende der Fall. Vorher existiert auch schon das Gesamtbild, aber in gröberer Form: Drahtmodell, Drahtmodell mit wenigen Flächen, grob gerendertes Modell mit Flächen, feiner gerendertes Modell usw.

image

Der Trick bei Software ist, dass sie nicht wie eine Maschine bottom-up entwickelt werden muss. Wir können sie langsam scharf werden lassen, wie können sie immer feiner rendern, wie können ihr immer mehr Details hinzufügen. Aber vom ersten Moment an kann sie als Ganzes, d.h. als benutzbares Werkzeug existieren.

image

Software ist insofern eher fraktal. Dazu ein Zitat über den hier abgebildeten Farn:

After a few dozen repetitions or ITERATIONS the shape we would recognize as a Perfect Fern appears from the abstract world […]. How and Why can this be?

Ja, wie kann das sein? Es liegt daran, dass Software eben soft ist. Sie unterliegt nicht der Physik. Für das Feature “Ruhe Schlafstätte” muss ein Haus ein Schlafzimmer haben, dafür muss es ein Dach und Wände haben, dafür muss es ein Fundament haben. Wände können einfach nicht schweben ;-)

Bei Software ist das anders. Für die Funktionalität “Zahlungseingänge buchen” muss die Fakturasoftware kein komplettes Data Access Layer haben und kein vollständiges UI und keine umfassende Security und schon gar kein lückenloses Datenmodell – in Bezug auf den Gesamtzweck.

Software kann Feature-by-Feature schrittweise ausgeprägt werden in Längsschnitten. Und sie kann bei jedem Schritt überall eine Kleinigkeit hinzufügen. Etwas mehr UI-Detail, etwas mehr Funktionalität bei Data Access, ein wenig mehr Leistung in der Validation usw. usf.

Minimale Funktionalität im Backend kann sozusagen schon ein riesiges Frontend tragen, weil es keine physikalischen Gesetze gibt. Unvollständiges kann Vollständigem dienen. Lückenhaftes kann jederzeit nachträglich verspachtelt werden. Grobes kann jederzeit verfeinert werden.

Software können wir schrittweise wie den obigen Farn vollständiger, lebensechter, anforderungsnäher “rechnen”. Im ersten dünnen Längsschnitt – Feature Slice – ist ihre grundsätzliche Form schon zu erkennen.

image

Mit dem kann der Kunde dann noch nicht soviel anfangen. Zugegeben. Doch er bekommt schon einen Eindruck. Und wir als Entwickler auch. Das ist wie mit dem modernen Film. Da verlässt sich ein Regisseur wie Stephen Spielberg auf die pre-visualization:

Damit meine ich jetzt aber keinen Prototypen, sondern eben einen lauffähigen Längsschnitt, eine Teilfunktionalität. Sie stellt die Anwendung “durch alle Schichten” schon dar – nur eben noch unvollständig.

Bei einem Auto oder einem Mixer oder einem Drucker wäre das nicht zu machen. Wie auch?

Oder anders: Bei Maschinen findet diese schrittweise “Verschärfung” über Modelle statt. Die Sukzession der Modelle bietet von Anfang an Funktionalität – aber nicht all das, was ein Kunde von heute erwartet. Ein Ford Model-T konnte Personen motorgetrieben befördern, hatte aber keine Sicherheitsgurte und schon gar kein ABS.  

Über die Zeit “verschärfen” sich Maschinen also auch. Ein Modell einer Maschine jedoch muss eben in ganz-oder-gar-nicht Manier entwickelt und gebaut werden. Das ist bei Software anders. Das macht Software fundamental anders als Maschinen.

Bilder mit Konsequenzen

Die “Bildhaftigkeit” von Software hat nun Konsequenzen, die wir nicht ignorieren sollten. Sie helfen die obigen Probleme lösen:

  • Konsequenz 1 – “Planlosigkeit”: Software wird eben nicht produziert, sondern entwickelt. Damit ist sie der Möglichkeit der maschinenbaulichen Dreifaltigkeit Fixpreis-Fixscope-Fixzeit beraubt. Entwicklung ist inhaltlich nicht zeitlich planbar so wie Produktion. So ist das mit kreativen Prozessen. Wer Software in Auftrag gibt, muss das verstehen, um nicht unglücklich daran zu werden.
  • Konsequenz 2 – Verschärfung: Software bietet nicht erst Nutzen, wenn sie 100% geliefert ist. Im Gegenteil! Wer sie erst sieht, wenn sie “voll gerendert” ist, der erlebt sehr wahrscheinlich sein blaues Wunder. Software muss wegen ihrer Kompliziertheit und Komplexität schrittweise verschärft werden – und sie kann es. Das ist das Schöne. Warum sollte man sie dann auf etwas Maschinenhaftes reduzieren? Nehmen wir doch Software endlich ernst und entwickeln sie in dünnen Längsschnitten. Agile Vorgehensmodelle haben das auch schon vorgeschlagen – aber aus meiner Sicht nehmen sie das noch nicht ernst genug. Und die Objektorientierung steht und da im Weg. Sie kommt aus dem Maschinenbauzeitalter. Für “development as rendering” ist OOP nicht wirklich geeignet. Andere Ansätze müssen her. EBC scheint mir da ein Weg zu mehr Kongruenz der Modellierung mit der Natur der Software.
  • Konsequenz 3 – Längsschnitte: Man höre endlich auf, in Schichten zu denken und zu entwickeln. Das ist nicht nötig; Software braucht kein Fundament oder Rahmenwerk. Das Geld des Kunden sollte nicht auf Wochen oder Monate versenkt werden in Programmierung, die ihm nichts bringt. Nutzen muss vom ersten Tag an her. Längsschnitte statt Querschnitte tun not. Aber dafür braucht es eine andere Entwicklungskultur. Denken und Teamorganisation müssen sich ändern, um Längsschnitte zu sehen und hochperformant zu realisieren.

In Summe glaube ich, dass wir noch gar nicht abschätzen können, wie anders und besser Softwareentwicklung sein kann, wenn wir endlich das überkommene Denken über Software aufgeben. Maschinen als Bild und von-Neumann-Denke waren lange erfolgreich – werden aber zunehmend ein Klotz am Entwicklerbein. Sie stehen uns im Wege dabei, dem Kunden schnell Nutzen zu liefern für sein Geld. Und sie stehen uns im Wege bei der Ausreizung moderner Prozessoren.

Verabschieden wir uns davon. Schreiben wir endlich schärfer und schärfer werdende Software. Jeden Tag.