Follow my new blog

Dienstag, 6. August 2013

30 day challenge – Dem Neuen eine Chance

Lohnt sich eine Veränderung? Funktioniert ein Ansatz wirklich? Das können wir oft nicht durch bloßes Nachdenken herausfinden. Attraktivität und Plausibilität sind nett, gar notwendig - aber hinreichend sind sie nicht, damit wir Aufwand für etwas Neues treiben. Deshalb bleibt so manches eine interessante Idee und der Rest einfach beim Alten.

Das ist schade, oder? Denn damit verschenken wir ja Chancen, dass “es” besser wird. Schneller, leichter, angenehmer… das kriegen wir nur, wenn wir uns auf Neues einlassen.

Solchen Verbesserungswünschen steht aber natürlich der Wunsch nach Sicherheit gegenüber. Wir wollen ja durch Neues nichts verlieren, das wir schon haben. Sonst wird es nicht besser, sondern nur anders - und wir haben Aufwand getrieben. Das wäre auch schade, oder?

Verbesserung und Erhalt des Ist-Zustands stehen sich gegenüber. Aber nicht nur im Patt. Denn der Erhalt gewinnt, weil sich durch Inaktivität ja nichts verändert.

Angesichts der Ungewissheit, ob die Implementierung einer Idee wirklich zu Verbesserungen führen würde, und der Verständlichkeit des Erhaltungswunsches, stellt sich die Frage, wie denn aber doch dem Verbesserungspotenzial eine Chance gegeben werden könnte.

Ich denke, da helfen Experimente. Genau wie in der Wissenschaft geht es ja darum, eine Hypothese zu überprüfen. Mehr ist eine Idee, ein Veränderungsvorschlag, ein neuer Ansatz ja zunächst nicht. Selbst nicht, wenn die Idee woanders schon tausendfach ihr Versprechen eingelöst hat.

Nachdenken kann nicht helfen und muss nicht helfen. Ob Agilität, reactive programming, NoSql, Flow-Design, Continuous Build, TDD as if you meant it usw. usf. etwas für Sie sind, stellen Sie nur fest, indem Sie es ausprobieren.

Ausprobieren klingt für mich allerdings ein bisschen unverbindlich. Deshalb sage ich Experiment. Der Unterschied liegt in der Ernsthaftigkeit. Beim Ausprobieren tun Sie nicht mehr, als dass Sie an einer Sache in eng begrenztem und geschütztem Umfeld herumspielen. Das ist nicht schlecht und steht immer am Anfang. Doch der Erkenntnisgewinn ist begrenzt.

Bei einem Experiment hingegen, geht´s ans Eingemachte. Das findet für mich in vivo statt. Da wird nicht ausprobiert, sondern real eingesetzt. Man nimmt die Idee ernst und implementiert sie. Allerdings nur für einen überschaubaren Zeitraum.

Insofern fordert man sich selbst heraus. Schafft man es, sich auf die Idee einzulassen?

Und die Idee wird auch wirklich herausgefordert. Schafft sie es, ihre Versprechen einzuhalten?

Ideen kommen oft “absolut” daher, mit großem Anspruch und der Suggestion, es ginge nur ganz oder gar nicht. Davon sollten wir uns aber nicht beeindrucken lassen. Jedenfalls heute nicht mehr. Ideen stehen in einem Wettbewerb. Und da gilt: Die bessere mögen gewinnen. Dafür jedoch müssen wir ihnen eine Chance geben. Aber nicht nur nebenbei, von ernsthaft.

Matt Cutts hat dafür ein leidenschaftliches Plädoyer bei TED gehalten:

Damit geht der Druck raus aus den Entscheidungen für oder gegen Ideen. Wir müssen nicht einmalig den Kurs auf Gedeih und Verderb ändern. Nein, wir können es für einen begrenzten Zeitraum tun. “Nur” 30 Tage reichen für vieles aus.

30 Tage TDD, 30 Tage daily standups, 30 Tage ein Entwicklertagebuch, 30 Tage 10 Seiten in einem Fachbuch lesen, 30 Tage im Team ein Code Review, 30 Tage eine neue Codierungsrichtlinie… und währenddessen, aber vor allem anschließend reflektieren. Wie fühlt es sich an, eine Idee ernsthaft 30 Tage lang zu implementieren?

Bei www.my30dc.com gibt es dafür sogar ein Tool. Wenn da das Geek-Herz nicht höher schlägt ;-)

Ich habe da auch gerade eine “30 day challenge” laufen. Mein Experiment ist, nur während 8 Stunden pro Tag zu essen. Das soll sich positiv auf Wohlgefühl und Gewicht auswirken. Ob das stimmt? Keine Ahnung. Ich werde es nach 30 Tagen wissen. Durch Nachdenken könnte ich das nicht herausbekommen.

Also: Lassen Sie sich von Ideen nicht ins Bockshorn jagen. Fühlen Sie sich nicht belastet durch Zweifel und endgültigen umfassenden Adoptionsaufwand.

Wenn sie Ihnen halbwegs interessant erscheinen, machen Sie “nur” ein Experiment. Möglichst unverzüglich, solange die Motivationsenergie noch hoch ist. Das geht persönlich oder im Team. Nutzen Sie die “30 day challenge” Plattform oder schreiben Sie ein bisschen Experimentiertagebuch ein einem Notizbuch. Egal. Tun und reflektieren sind das Wichtigste. Taten statt Warten.

Ich habe auch noch eines zum Thema Programmierung gestartet. Im Verlauf von 30 Tagen will ich einen Web-Dienst realisieren. Sie können meinen Fortschritt im github Repository verfolgen. Dort sammle ich nicht nur Code, sondern auch Tagebucheinträge.

Donnerstag, 1. August 2013

Was ist eine Entität?

Im Zuge meiner Beschäftigung mit CQRS und Event Sourcing habe ich mir wieder die Frage gestellt, was denn eigentlich eine DDD Entität sei?

Nun hab ich endlich für mich eine ganz einfache Definition gefunden: Eine Entität ist ein Dictionary<string,string> mit unwandelbarer Id [1]. Eine Entität ist ein Sammlung von Schlüssel-Wert-Paar als Zeichenfolgen mit unwandelbarer Id [1].

Jup. Das isses. Mehr nicht [2].

Den Inhalt einer Entität kann man wandeln, die Id aber nicht. Und der Inhalt einer Entität ist strukturiert. Gerade die Annahme, dass Keys Pfade sein können [2], macht mich zufrieden. Nicht nur “Vorname” oder “Mindestgebot” oder “Lagerplatz” sind also Keys, sondern ebenfalls “Wohnsitz/PLZ” oder “Telefonnummern/3” oder “Positionen/2/Menge” [3].

So ein Dictionary ist ein Dokument, d.h. es ist selbstgenügsam (self-contained). Alles, was dazugehört, steht drin. Was nicht drin steht, gehört zu einer anderen Entität. So einfach ist das.

Was Sie als Daten bei einem Key hinterlegen, ist Ihre Sache. Das kann so umfangreich und strukturiert sein, wie es will. Per definitionem ist das aber keine Entität, weil es eben ein Wert in einer Entität ist. Werte als string [4] und nicht als object zu definieren ist mir deshalb sehr wichtig. Dadurch ist ausgeschlossen, aus einer Entität Objekte zu referenzieren.

Entitäten dürfen allerdings Ids anderer enthalten. Solche Entitätsreferenzen sind ok, weil sie sich auf das wahrhaft Unwandelbare beziehen [5]. Beispiel:

var frau = new Dictionary<string,string>{{$$id, "10"}, {"Vorname", "Eva"}, ...};
var mann = new Dictionary<string,string>{{$$id, "0815"}, {"Vorname", "Adam"}, ...};

frau["partner"] = mann["$$id"];
mann["partner"] = frau["$$id"];

Mit der Definition “Eine Entität ist ein Dictionary<string,string>” ist es nun ganz einfach herauszufinden, ob ein zusammengesetzter Wert eine Entität ist oder nicht. Fragen Sie sich einfach, ob Sie den Wert einem oder mehreren Keys in einer anderen Entität zuweisen würden. Beispiel:

Ist der zusammengesetzte Wert

Person("Vorname": "Peter", "GebDat": "1967-05-12", "Geschlecht": "m")

eine Entität? In manchen Zusammenhängen mag das so sein:

var person = new Dictionary<string,string>{{"$$id", "7654}, {"Vorname", "Peter"}, ...};
var einwohner = new Dictionary<string,string>{{"$$id", "0192"}, {"Person", "7654"}};

In anderen wieder nicht. Dann ist er Teil einer umfassenden Entität, z.B.

var einwohner = new Dictionary<string,string>{...};
einwohner["Person"] = "{\"Vorname\": \"Peter\",
                        \"GebDat\": \"1967-05-12\",
                        \"Geschlecht\": \"m\"}";

oder

einwohner["Person/Vorname"] = "Peter";
einwohner["Person/GebDat"] = "1967-05-12";
einwohner["Person/Geschlecht"] = "m";

Ich denke, das macht die Entscheidung einfacher. Doch auch wenn die mal daneben geht, ist es nicht so schlimm. Entitäten in dieser Form kann man auch leicht refaktorisieren. “Inline entity” und “extract entity” scheinen mir genauso probat wie “inline method” und “extract method”. Also keine Angst vor BDUF!

Außerdem lassen sich Entitäten dieser Form sehr leicht verteilt über Ereignisse denken, z.B.

Zugezogen("$$id": "9384", "Nachname": "Müller", "Straße": "Isestr. 2", ...)
Umgezogen("$$id": "9384", "Straße": "Richterstr. 17", "PLZ": "22085", ...)
Geheiratet("$$id": "9384", "Ehepartner": "0815", "Nachname": "Schmied", ...)
Geheiratet("$$id": "0815", "Ehepartner": "9384", ...)

In jedem Ereignis wird die Entität angegeben und danach nur eine Liste von Keys mit neuen Werten. Der aktuelle Stand einer Entität ergibt sich dann aus dem Summe ihrer Ereignisse.

Endnoten

[1] Ob die Id ein Eintrag mit speziellem Key in so einem Dictionary ist oder separat gehalten wird, ist mir erstmal egal.

[2] Noch einfacher könnte man natürlich sagen, eine Entität sei ein strukturierter Text, z.B. ein Json- oder XML-Dokument. Wird letztlich bei NoSql auch so gemacht. Aber das ist mir grad zu wenig griffig, wenn ich an den Umgang damit im Code denke. Mit einem Dictionary fühle ich mich etwas wohler.

Ein geschachteltes Dokument wie

<Person id="1234">
  <Vorname>Peter</Vorname>
  <Anschrift>
    <PLZ>22085</PLZ>
  </Anschrift>
</Person>

sieht mir auch komplizierter aus als eine Liste von Key-Value-Paaren:

$$id:1234
Vorname:Peter
Anschrift/PLZ:22085

Das eine kann in das andere übersetzt werden. Das reicht mir.

[3] Wem da Metadaten fehlen, der kann sie sich gern dazudenken, z.B. “Vorname:string” oder “Positionen/2/Menge:int”. Am Ende ist jedenfalls jeder Wert auf eine Zeichenkette abbildbar.

Wie solche Pfade aussehen, ist hier ebenfalls nicht wichtig. Mit der Syntax, die ich hier benutze, sind Sie ja aber vertraut.

[4] Binärdarstellungen sind nur eine Optimierung. Mit textuellen Repräsentationen von Daten kommen wir weiter. Sie sind ausreichend, bis ein konkreter Use Case etwas anderes fordert.

[5] Allerdings hadere ich hier noch mit mir. Manchmal denke ich, dass Entitäten gar keine Referenzen enthalten sollten. Wenn sie zu anderen in Beziehung stehen, dann nur innerhalb eines Kontextes - und der wird dann von einem Aggregat aufgespannt.

Ein Aggregat ist dann eine Entität, deren Zweck es ist, andere zu verbinden. Es ist ein Beziehungsraum.

So ganz traue ich mich aber noch nicht, das konsequent zu denken. Ich habe es zwar schon einmal in der dotnetpro so beschrieben - doch im Moment bin ich wieder ein wenig im Zweifel… Hm… warum eigentlich? Dazu mach ich mir am besten ein andermal weitere Gedanken.

Dienstag, 30. Juli 2013

Was der Softwarekunde will

Wer Software kauft, kauft ein Werkzeug. Das tut er, weil er die Effizienz des Anwenders steigern will.

Alles, was Software tut, kann auch ohne Software getan werden. Wir brauchen Software also nicht für ihre funktionalen Eigenschaften. Stoffe wurden ohne Software gewoben, Logarithmen wurden ohne Software berechnet, Auktionen ohne Software durchgeführt, Rechnungen ohne Software geschrieben usw. usf.

Mit genügend Zeit, Platz, Geld kann man sozusagen Software simulieren.

Das bedeutet: Software wird nicht wegen ihrer Funktionalität geschrieben, es geht also nicht um Effektivität. Software wird vielmehr wegen ihrer nicht-funktionalen Eigenschaften geschrieben. Dafür wird Software gemacht. Ausschließlich.

Nicht-funktionale Anforderungen sind allerdings nicht alle gleich. Es gibt primäre und sekundäre.

Primär sind die nicht-funktionalen Anforderungen, die Software etwas besser tun lassen, als die Alternative. Meist ist die langsamer oder umständlicher oder hat weniger Reichweite. Dann geht es bei Software um Performance, Usability oder Skalierbarkeit.

Sekundär sind die nicht-funktionalen Anforderungen, die Software auch noch erfüllen muss, die aber nichts mehr mit der Alternative zu tun haben. Wenn eine Software schneller als ein Mensch rechnen soll, dann ist Sicherheit ein sekundärer Aspekt. Wenn Software es leichter machen soll, Überblick über Zahlen zu behalten, dann ist Portierbarkeit ein sekundärer Aspekt.

Noch genauer wird Software also wegen ihrer primären nicht-funktionalen Eigenschaften geschrieben. Welche das in Ihrem Projekt/Produkt sind, können Sie ja mal überlegen.

Diese Feststellung hat schon eine Auswirkung auf die Praxis. Aus ihr ergibt sich, dass vordringlich und im Zweifelsfall Aufwand in primäre nicht-funktionale Anforderungen investiert werden sollte.

Nicht-funktionale Eigenschaften gibt es nicht ohne funktionale. Sie sind die Adverbien zu diesen Verben: schnell rechnen, übersichtlich darstellen, verlässlich speichern. Und das sogar im Komparativ, da es ja um eine Steigerung gegenüber der Alternative geht, also: schneller rechnen, übersichtlicher darstellen, verlässlicher speichern.

Nicht-funktionale Eigenschaften und funktionale sind auf Augenhöhe. Kunden suchen Effizienz und Effektivität. Nicht die Pyramide ist daher das erste Bild, das in den Sinn kommen sollte:

image

Es geht vielmehr um einen Kreis für das Ganze, dessen Teile gleichberechtigt sind:

image

Soweit das recht Offensichtliche. Ich glaube aber, dass sich damit nicht das komplette Kundenverhalten erklären lässt. Der Kunde will mehr.

Haltbarkeit ist Wandelbarkeit

Wer in Werkzeuge investiert, der erhofft sich, dass mehr herauskommt, als reingesteckt wurde. Der Nutzen soll größer sein als die Investition. Die Wahrscheinlichkeit dafür steigt mit der Nutzungsdauer.

Dieser Gedanke ist sogar so wichtig, dass er in der Steuergesetzgebung Niederschlag gefunden hat. Abschreibungsdauern richten sich nach üblichen Nutzungsdauern.

Neben den funktionalen und primären nicht-funktionalen Eigenschaften hat der Käufer einer Software also immer auch ihre Haltbarkeit im Hinterkopf. Die Investition soll sich auszahlen.

Eine Batterie soll Wochen oder Monate nutzen, ein Auto Jahre, ein Flugzeug Jahrzehnte, ein Sakralbau Jahrhunderte.

Wer Geld ausgibt, der hat immer eine Wunschvorstellung von der Haltbarkeit des Erwerbs. Er wird sich also mit dem Verkäufer darüber explizit unterhalten bzw. anderweitig etwas über die Haltbarkeit des “Objektes seiner Begierde” in Erfahrung bringen.

Zum obigen Kreis der Anforderungen kommt deshalb ein “Tortenstück” hinzu:

image

Es wäre ja töricht anzunehmen, dass Käufer von Software nicht auf Haltbarkeit achten würden. Für sie ist Software ein Werkzeug wie ein Bohrer oder ein Bagger.

Nun ist es aber so, dass Software zwar ein Werkzeug ist, aber ein immaterielles. Dadurch verschiebt sich die Bedeutung von Haltbarkeit.

Haltbarkeit in der materiellen Welt hat etwas mit Unveränderlichkeit, Erhalt, Konstanz zu tun. Ein Messer, Fenster, Auto ist umso haltbarer, je weniger es sich über die Zeit und mit Gebrauch verändert. Weniger Verschleiß bedeutet längere Haltbarkeit und damit höhere Investitionssicherheit.

Jedenfalls war und ist das gewöhnlich so. Denn es gibt auch materielle Werkzeuge, bei denen die Haltbarkeit weniger mit Verschleiß zu tun hat. Mir fällt da z.B. ein Handy ein. Dessen materielle Haltbarkeit ist weit höher als seine immaterielle. Allemal, wenn wir von geplanter Obszoleszenz absehen. Ich könnte immer noch mit meinem ersten Handy aus dem Jahr 1998 telefonieren.

Aber ich will das nicht. Es hat nämlich nicht Schritt gehalten mit der Entwicklung. Ich habe heute andere Ansprüche an ein Handy.

Unveränderlichkeit ist von Wert, wenn sich die Einsatzwelt eines Werkzeugs über die Zeit seiner materiellen Haltbarkeit nur geringfügig ändert. Deshalb tun wir bei Messern, Bohrern, Autos etwas gegen Verschleiß.

Dadurch verschiebt sich ebenfalls die Bedeutung von Haltbarkeit von Software. Denn die Einsatzwelt von Software ist in den meisten Fällen starken Veränderungen unterworfen. Das ist der Fall, weil Software in vielen Fällen weniger Werkzeug in einem Prozess, sondern vielmehr selbst die Definition eines Prozesses ist. Je größer ein Softwaresystem, desto eher ist das der Fall.

Die Haltbarkeitsdauer, d.h. die Zeit, in der ihr Nutzen höher als die Kosten ist, ist bei Software deshalb eine Funktion ihrer Anpassungsfähigkeit. Haltbar ist Software, die sich über lange Zeit leicht Veränderungen in der Einsatzwelt nachführen lässt.

Bei Software gibt es also keine Wartung wie bei materiellen Werkzeugen. Es geht eben nicht um den Erhalt von Eigenschaften. Von Softwarewartung zu sprechen und Wartungsverträge anzubieten, ist aus meiner Sicht grob kontraproduktiv. Denn dadurch wird ein völlig falsches Bild von Software transportiert. Es werden auch die falschen Signale an die Softwareproduktion gesandt.

Kunden erwarten Haltbarkeit. Denn ohne Haltbarkeit keine Investitionssicherheit. Doch diese Haltbarkeit hat eben nichts mit Erhalt heutiger Eigenschaften zu tun, sondern mit der Fähigkeit zum Wandel von Eigenschaften. Das müssen wir Kunden deutlich machen. Wir müssen ihnen explizit Wandelbarkeit [1] verkaufen. Ohne Wandelbarkeit keine Haltbarkeit. Wir müssen Wandelbarkeit auf Augenhöhe mit Funktionalität und primären nicht-funktionalen Eigenschaften sehen:

image

Wandelbarkeit ist nicht nice to have, sondern zentral. Wandelbarkeit ist keine Eigenschaft, die sich später hineinrefaktorisieren lässt. Wandelbarkeit ist auch wichtiger als sekundäre nicht-funktionale Eigenschaften.

Wenn Software ein nutzbringendes Werkzeug sein und lange bleiben soll, dann muss sie auf das Dreibein Effizienz, Wandelbarkeit und Effektivität gestellt werden. Das ist genau das, was der Kunde will - ob er das so klar ausspricht oder nicht.

Endnoten

[1] Mit Wandelbarkeit meine ich natürlich nicht, Software zwanghaft mit Plug-Ins auszurüsten. Was ich unter Wandelbarkeit verstehe, geht tiefer. Sie ist auf allen Abstraktionsebenen ins Softwaresystem gewoben. Sie hat mit grundlegenden Prinzipien zu tun.

Mittwoch, 24. Juli 2013

Historische Interaktionen

Die erste Frage bei der Überführung von Anforderungen in Code sollte den Interaktionen gelten: Welche Interaktionen braucht ein Softwaresystem? Das bedeutet, welche technischen Ereignisse [1] sind domänenrelevant und müssen verarbeitet werden?

Im einfachsten Fall bedeutet das, hinter jedem Menüpunkt und jeder Schaltfläche steht eine Interaktion. Löst der Anwender sie aus, soll im Softwaresystem etwas passieren.

Für mich beginnt die Softwareentwicklung also mit einer Diskussion über die Schnittstellen des Softwaresystems zur Umwelt. Dort finden Interaktionen mit Benutzern statt - seien das Menschen, andere Software oder Maschinen. Von dort treibe ich den Softwareentwurf nach innen weiter: outside-in design. Denn alle Strukturen im Softwaresystem müssen einem Bedarf in der Umwelt dienen, der über eine Schnittstelle vermittelt wird.

Wie der Entwurf nach Fund einer Interaktion dann bis zum Code fortschreiten kann, will ich hier nicht thematisieren. Von mir aus kann man das streng mit Test-Driven Design versuchen.

Entscheidend ist vielmehr, dass Interaktionen einen idealen Ansatzpunkt für jegliches weiteres Vorgehen bieten. Jede Interaktion kann nämlich als Funktion codiert werden [2].

Beispiel Benutzeranmeldung: Die Benutzeranmeldung erfolgt über einen Dialog, in dem der Anwender seinen Benutzernamen und ein Passwort einträgt. Dann drückt er die Schaltfläche “Anmelden”, um die Authentifizierung und Autorisierung anzustoßen.

Die Schaltfläche steht für die Interaktion “Anmelden”. Und die Interaktion kann als Funktion bool Anmelden(string benutzername, string passwort) realisiert werden.

Alternativ könnte die Interaktion durch einen Menüpunkt oder einen Tastatur-Shortcut ausgelöst werden. Das technische Ereignis ist letztlich unwichtig. Wichtig ist, was daraufhin passiert, das Verhalten des Softwaresystems. Und das kann komplett in einer Funktion gekapselt werden. Immer.

Soweit der Blick mit unbewaffnetem Auge.

Interaktionen unterscheiden

Jetzt aber die Lupe zur Hand genommen. Da zeigen sich nämlich Unterschiede in den Interaktionen bzw. Interaktionsfunktionen.

image

Reinheitsgrad: Ich denke, es lohnt sich die Unterscheidung zwischen reinen und unreinen Interaktionen. Reine Interaktionen arbeiten nur auf Daten, die ihnen übergeben werden. Beispiel: Wenn ich in einem Dialog einen Text eingeben kann, der auf Knopfdruck umgekehrt wird, damit ich sehen kann, ob ein Palindrom vorliegt, dann steht dahinter eine reine Interaktionsfunktion. Der oben skizzierte Anmeldedialog hingegen stößt eine unreine Interaktionsfunktion an. Mit Benutzername und Passwort allein kann nicht authentifiziert werden; dazu ist mindestens noch ein Verzeichnis von Benutzern nötig.

Verhaftungsgrad: Unreine Interaktionen zerfallen für mich darüber hinaus noch in solche, die zeitlich verhaftet sind - und solche, die quasi nur im Moment leben.

Zeitlich verhaftete Interaktionen benötigen für ihre Arbeit ein Gedächtnis. Ihnen ist die Vergangenheit wichtig - zumindest in Form eines akkumulierten Zustands. Man könnte auch sagen, sie seien historisch determiniert. Denn ihr Verhalten hängt nicht nur vom aktuellen Input ab, sondern von vorherigem Verhalten des Softwaresystems.

Der Anmeldedialog ist dafür ein Beispiel. Ob er einen Benutzer erfolgreich authentifiziert oder nicht, hängt davon ab, welche Veränderungen vorher am Benutzerverzeichnis vorgenommen worden sind.

Anders ist das bei Interaktionen, die zwar auch nicht nur auf ihrem Input arbeiten, sondern weitere Ressourcen brauchen. Doch da ist deren Geschichte nicht wichtig. Sie wird nicht in die Verarbeitung mit einbezogen.

Das ist zum Beispiel der Fall, wenn eine Interaktion etwas druckt oder Input mit der aktuellen Systemzeit vergleicht.

Zugriffsart: Unreine Interaktionen, die nur im Moment leben, können auf ihre Ressourcen lesend oder schreibend zugreifen. Sie interessieren sich nicht für die Vergangenheit, also dürfen sie Zustand auch einfach verändern. An der Ressource ist anschließend nicht abzulesen, in welchem Zustand sie vorher war.

Aber wie ist das bei unreinen verhafteten Interaktionen?

Ich glaube, hier sollten wir umdenken. Bisher ist dort auch lesender und schreibender Zugriff erlaubt - ohne dass vorherige Zustände erhalten blieben.

Aus Effizienzgründen war das bisher so. Wir konnten nicht anders oder haben das zumindest geglaubt. Doch es hat immer schon der Natur dieser Interaktionen widersprochen.

Das haben wir schmerzvoll erfahren, wenn wir Mühe hatten undo/redo zu implementieren oder Daten mit Geltungsdauern zu verwalten oder historische Daten zu pflegen oder wir uns mit der Versionierung von Datenstrukturen herumschlagen mussten.

Wenn wir nun aber mal von eingefahrenen Mustern Abstand nehmen, dann können wir mit solchen Interaktionen natürlicher umgehen. Das bedeutet für mich, dass bei “historischen Interaktionen” nur lesender und anfügender Zugriff erlaubt sind. Es gibt keinen schreibenden im Sinne von verändernden Zugriff. Denn der würde ja die Geschichte umschreiben.

Die Geschichte, das ist die Liste der Veränderungen, die zum aktuellen Stand der Daten geführt hat. Ja, ich glaube, die sollte unangetastet bleiben. Wir können davon nur profitieren.

Fazit

Nicht jede Interaktion ist zeitlich verhaftet. Wir tun also gut daran, genauer hinzuschauen. Die Unterscheidung zwischen Interaktionsfunktionen mit und ohne Seiteneffekt finde ich jedoch zu einfach. Denn Seiteneffekte können sehr unterschiedlich sein.

Die Ausgabe auf einem Drucker ist etwas anderes, als die Fortschreibung von Unternehmensdaten in einer Datenbank. Event Sourcing scheint mir in dieser Hinsicht ein sehr probates Mittel, um Klarheit zu schaffen.

Interaktionen, die mit Zustand zu tun haben, der sich über die Lebenszeit eines Softwaresystems durch das Softwaresystem entwickelt, sollten sich dafür ein Gedächtnis bewahren. Das ist für mich der neue Default.

Und damit dieser Default nicht über Gebühr verstört, sollte klar sein, wo er gilt. Das sind nur die “historischen Interaktionen”. Alle anderen - auch wenn sie Seiteneffekte haben - können weiter wie bisher gehandhabt werden.

 

PS: Natürlich können unreine Interaktionen auch gemischter Art sein: Sie können aus Wiedergabe und Seiteneffekt oder aus Lookup und Aufzeichnung bestehen usw.

In jedem Fall sollten diese Aspekte aber in eigenen Funktionen gekapselt sein. Das Integration Operation Segregation Principle (IOSP) gilt auch hier.

Endnoten

[1] Technische Ereignisse sind Tastendrücke, Mausklicks/-bewegungen oder Notifikationen von Timern oder Geräten.

[2] Die Schnittstelle zwischen Umwelt und Domäne nehme ich von dieser Funktion aus. Die Interaktionsfunktion kümmert sich nicht darum, woher ihre Input-Daten kommen und was mit ihren Output-Daten gemacht wird. Sie kennt die Schnittstelle nicht, über die sie aufgerufen wird. Das kann ein WPF-Dialog oder ein Webservice sein.

Es ist also nicht Sache einer Interaktionsfunktion, sich Daten aus Eingabefeldern des UI zu beschaffen oder Ergebisse im UI, über das sie aufgerufen wurde, anzuzeigen.

Montag, 22. Juli 2013

Zustand ist eine armselige Optimierung

Das Denken des Softwareentwicklers kreist ständig um Daten. Wer auf Anforderungen schaut, sucht zuerst nach Daten. Wer an Infrastruktur denkt, denkt zuerst an Persistenztechnologien. Und wenn nicht immer, dann zumindest oft.

Nicht umsonst gibt es die Mittel der Objektorientierung als Weiterentwicklung von Sprachen wie C und Pascal: um Daten nicht nur zusammenzufassen (class), sondern auch noch funktional zu kapseln (interface).

Was dann in Objekten steckt, ist Zustand. Das sind nicht nur einfach Daten, sondern Daten in einer aus vielen Veränderungen hervorgegangenen Konstellation.

Hier ein simples Beispiel:

image

Mit jedem Aufruf von Sum() berechnet ein Aggregator die Summe einer Anzahl von Werten. Zu diesem Zweck führt das Objekt einen Zustand. Der ist das Ergebnis einer Reihe von Veränderungen:

image

Er stellt also einen Schnappschuss dar. Wie es zu ihm gekommen ist, liegt im Dunkeln.

Das macht für diesen Zweck auch nichts. Ich meine nicht, dass man eine solche Aggregationsfunktionalität dringend anders realisieren müsste. Dennoch kann sie helfen, deutlich zu machen, dass solcher Umgang mit Daten nur eine Option von mehreren ist. Und das deshalb ihr Entscheidungen zugrundeliegen, die ungünstig sein können, wenn wir sie nicht kennen oder leichtfertig treffen.

Wie könnte es anders gehen? Die Funktionale Programmierung (FP) würde vielleicht sagen, der Zustand in der Klasse sei unschön. Sum() ist keine reine Funktion, eben weil sie von unsichtbarem Zustand abhängt. Für einen gegebenen Input - z.B. (2) - lässt sich nicht vorhersagen, welchen Output sie erzeugt.

Als Antwort könnte die Summierung so umgebaut werden:

image

Es gibt immer noch Zustand im obigen Sinne, doch der wird irgendwo gehalten. Er ist nicht mehr Sache der nun reinen Funktion Sum(). Für einen gegebenen Input - z.B. (2, 1) - ist deren Output immer gleich - z.B. (3).

Ist dadurch etwas gewonnen? FP sagt, ja. Über reine Funktionen lässt sich besser nachdenken, die composability ist höher, die Testbarkeit besser. Und dann die Daten auch noch immutable sind… Besser geht´s nimmer ;-)

Ich möchte jedoch noch ein Stückchen weitergehen. Selbst wenn der reingereichte Zustand immutable ist, juckt mich etwas.

Warum gibt es in diesem Beispiel die Summe als Zustand? Ich glaube, der Grund ist ganz schlicht und einfach, aus Platz- und Performancegründen.

Denn es ginge ja auch anders. Der Zustand als punktuelle Konstellation hervorgegangen aus einer Linie von Veränderungen, kann immer durch diese Linie plus eine Reihe von Transformationen ersetzt werden:

image

Sum() hat jetzt überhaupt keinen Zustand mehr, weder internen noch externen. Stattdessen sieht die Funktion immer nur einen Liste von Veränderungen bestehend aus vielen historischen und einer aktuellen. Daraus berechnet sie das aktuelle Resultat - und schreibt die Liste der historischen Veränderungen fort.

Das Ergebnis ist dasselbe wie in den Varianten vorher. Nur wird jetzt mehr Platz und Zeit gebraucht.

Hat das einen Vorteil? Ja, ich denke, schon. Vorteil #1: Die Entwicklung des Ergebnisses ist immer komplett nachvollziehbar. Das hilft bei der Fehlersuche. Vorteil #2: Wenn eine Korrektur der Funktionalität nötig ist, können Ergebnisse nachträglich angepasst werden, falls das sinnvoll ist.

Diese Vorteile stechen in diesem Kleinstbeispiel nicht so ins Auge. Aber wenn Sie das Aggregat mal als persistent und etwas umfangreicher ansehen, dann werden sie relevant. Dann ist es interessant, diese Option denken zu können.

Ob die Liste der Veränderungen in die Funktion reingereicht wird oder aus einer globalen Quelle beschafft werden kann, finde ich gerade nicht so wichtig.

image

Hier geht es mir um den Wechsel der Sichtweise: vom Zustand zum Veränderungsstrom. Dass es schlicht möglich ist, auf Zustand zu verzichten. Dass seine Herstellung eine selbstverständliche und intuitive Optimierung ist - die wir als solche erkennen sollten.

Über die Optimierung in Hinsicht auf Platz und Zeit steckt darin sogar noch eine: eine Optimierung im Hinblick auf Form.

Wenn Sie Zustand denken - sei das eine Summe wie oben oder eine Datenklasse wie Person, Auktion oder Produkt -, dann denken Sie auch immer sofort an eine konkrete Struktur. Sie überlegen sehr sorgfältig, wie die aussehen soll. Davon hängen Klassen-, Datei- und Datenbankstrukturen ab. Bei solchen Strukturen darf Ihnen kein Fehler unterlaufen. Davon hängt ja viel ab. Sie zu ändern, allemal, wenn sie persistent sind, macht viel Aufwand.

Was aber, wenn es nicht mehr um Zustand geht? Dann müssen Sie nicht mehr über die eine beste Struktur für Daten nachdenken. Bei der obigen Summe ist diese Struktur trivial. Aber nehmen wir eine Software für ein Ortsamt: Wie sollte darin am besten die Klasse oder Tabelle oder das Dokument für einen Einwohner strukturiert sei?

Wie lange sollte man darüber nachdenken? Wieviele Anforderungen sollte man dafür analysieren? Nach wievielen Jahren Entwicklung und Änderungen an der Software kann man sich sicher sein?

Ich weiß es nicht. Und deshalb ist für mich jede Festlegung ohne Erfahrung und Mustererkennung eine vorzeitige Optimierung.

Die Lösung besteht für mich darin, das harte Nachdenken aufzugeben. Schluss mit Zustand! Stattdessen einfach nur Veränderungen speichern. So, wie sie in einem Inkrement gerade anfallen.

Aus diesen Veränderungen kann dann jederzeit eine Datenstruktur nach aktuellem Bedarf gegossen werden. Oben ist das zunächst nur eine Summe. Doch jederzeit könnte der auch noch ein Mittelwert zugesellt werden:

image

Hinter dem steckt nicht nur eine andere Logik, sondern auch eine andere Datenstruktur (double).

Zustand statt Veränderungen (events) zu speichern, ist mithin eine Optimierung, die unsere Optionen reduziert. Sie lässt uns langfristig verarmen - für kurzfristige Gewinne.

Wir glauben, wir hätten weder Platz noch Zeit, um Zustände bei Bedarf aus den ursprünglichen Veränderungen herzustellen. Deshalb treiben wir viel Aufwand in Bezug auf universelle in-memory wie persistente Datenstrukturen. Doch letztlich wissen wir nicht, ob wir mit dieser Befürchtung recht haben. Wir probieren ja nie die Alternative aus.

Dabei gäbe es viel zu gewinnen an Flexibilität. Und die brauchen wir dringend, wenn Software lange leben soll.

Freitag, 19. Juli 2013

Was ist eigentlich Kopplung?

Funktionseinheiten von Software sollen lose gekoppelt sein. Das ist ein Prinzip, dass wir alle runterbeten können - aber was bedeutet das denn konkret?

Irgendwie scheint es auch schwer einzuhalten, denn die Mehrheit der Softwaresysteme, die ich sehe, folgt ihm eher nicht. Monolithen sind das Gegenteil von loser Kopplung. Da hilft auch kein Einsatz von Interfaces.

Also: Was ist (lose) Kopplung und warum ist sie so schwer herzustellen?

Ein Blick in Wikipedia fördert leider nicht wirklich Hilfreiches zu Tage. Danach ist Kopplung…

die Verknüpfung von verschiedenen Systemen, Anwendungen, oder Softwaremodulen, sowie ein Maß, das die Stärke dieser Verknüpfung bzw. der daraus resultierenden Abhängigkeit beschreibt.

Und danach findet sich eine Liste Kopplungsarten, die ich, hm…, akademisch finde. Nicht falsch, aber eben nicht praxistauglich. Zumindest nicht für den Einstieg.

Der Eintrag zu loser Kopplung bringt auch nichts, würde ich sagen. Da steht z.B.

Eine lose Kopplung bedeutet in der Softwarearchitektur, dass Komponenten einer Software nur über wenige Schnittstellen mit anderen Komponenten kommunizieren bzw. von anderen Komponenten abhängig sind.

Wenn es nicht weit her ist mit der losen Kopplung in den heutigen Softwaresystemen, dann ist das kein Wunder, scheint mir. Aus solcher Schwurbelei lassen sich konkrete Maßnahmen nur schwer ableiten.

Ich versuche deshalb mal, den Begriff alltagstauglich zu fassen.

Kopplung konkret

Kopplung ist nötig, wenn Kooperation im weitesten Sinn gewünscht ist. Daran geht nichts vorbei. Sollen zwei Funktionseinheiten - Funktionen, Klassen, Bibliotheken, Services, Anwendungen, Softwaresysteme - miteinander arbeiten, dann müssen sie gekoppelt werden.

Das ist wie bei einer Lokomotive und Waggons. Wenn die Lokomotive Waggons ziehen soll, müssen die an die Lokomotive und untereinander gekoppelt sein, damit sich die Zugkraft überträgt.

Und auch hier ist die Stärke der Kopplung schon ein Thema. Sie braucht ein rechtes Maß: Ist sie zu lose, fällt der Zug [sic!] leicht auseinander. Ist sie zu fest, zerbricht es den Zug z.B. in Kurven.

Die erste Frage an eine Kopplung ist also die nach dem Zweck. Das Fragewort ist Wozu. Bei Lokomotive und Waggons geht es um Kraftübertragung zum Zwecke der Bewegung. Die Lokomotive ist Zugkraftdienstleister für die Waggons.

Die Kopplung im Sinne eines Zwecks ist immer eng, würde ich sagen. Weil das der Sinn einer Kopplung ist.

In allen anderen Belangen jedoch lässt sich die Stärke von Kopplung variieren von eng bis lose.

Hier meine Liste von solchen Belangen. Ich versehe sie alle mit einem Fragewort, um deutlich zu machen, dass wir uns immer wieder fragen sollten, wie stark die einzelnen Kopplungen denn wirklich sein müssen.

  • Wer: Wer ist es, an den eine Funktionseinheit gekoppelt ist? Ist eine Funktionseinheit an eine konkrete Instanz oder Identität einer anderen gekoppelt? Das Prinzip IoC und die Diskussion um Zustand in Servern drehen sich um diesen Kopplungsbelang.
  • Wo: Ist einer Funktionseinheit der “Ort” der anderen bekannt - egal, wie eng sie an eine Instanz gekoppelt ist. “Orte” sind Speicherbereiche, Prozesse oder Geräte. Dieser Belang hat jedoch zwei Seiten: Adresse und Entfernung. Die Adresse bezeichnet einen Platz in einem Adressraum, das kann ein physikalischer Speicher sein oder ein virtueller Raum wie das Internet. Die Diskussion um DI Container, Warteschlangen und Servicebusse drehen sich um diesen Kopplungsbelang. Die Entfernung hingegen sagt etwas darüber aus, wie leicht/schnell die Kommunikation zwischen zwei Orten stattfinden kann. Wenn Kopplungspartner sich nicht über ihre Hauptspeicheradresse kennen, geht es hier um Transportmedien, Protokolle, Kompression oder Caches.
  • Wohin: Wie ist die Kopplung zwischen zwei Funktionseinheiten gerichtet? Ist sie unidirektional oder bidirektional? Wer muss wieviel vom anderen wissen? Die Diskussion um die Vermeidung zirkulärer Referenzen adressiert diesen Belang. Aber auch bei der reaktiven Programmierung bzw. asynchroner Verarbeitung geht es darum.
  • Wann: Wann erwartet eine Funktionseinheit das Ergebnis einer anderen? Oder ist eine Funktionseinheit daran gebunden, wann ihr Kopplungspartner existiert? Darum geht es bei asynchroner Verarbeitung, Event-Driven Architecture und Cloud-Diensten wie ironWorker.
  • Womit: Wie sind die Daten (Parameter, Resultat, globale Daten), aber auch die Dienste strukturiert, auf denen Kopplungspartner arbeiten? Inhaltlich, also in Bezug auf den Zweck (Wozu) gibt es da immer eine enge Kopplung (logische Abhängigkeit). Aber bei der Form gibt es Spielraum. Darum geht es u.a. bei dynamischen Programmiersprachen oder NoSql.
  • Wie: Wieviel weiß eine Funktionseinheit darüber, wie es in ihrem Kopplungspartner aussieht? Kann sie algorithmische oder datenbezogene Details kennen - oder nutzt sie sie gar? Ist sie nicht nur an eine Leistung, sondern auch an die Plattform, auf der die erbracht wird, gekoppelt? Wenn über Entkopplung gesprochen wird, dann meist in dieser Hinsicht. Objektorientierung, Information Hiding, Interfaces: das sind Begriffe, die bei der Diskussion über diesen Belang fallen.

Auf alle W-Fragen zu Belangen der Kopplung kann die Antwort mehr oder weniger spezifisch sein. Je spezifischer aber, desto enger die Kopplung.

Eine bestimmte Instanz an einer bestimmten Adresse in bestimmter Entfernung, die zu einem bestimmten Zeitpunkt vorhanden sein muss und zeitlich in bestimmter Spanne mit ganz bestimmten Strukturen in einer ganz bestimmten Struktur in genau bestimmter Weise arbeitet… an die ist die Kopplung eben sehr, sehr eng.

Oder eben umgekehrt ist die Kopplung lose, wenn die Antworten unbestimmter ausfallen. Wenn Instanzen egal sind, wenn nicht genau gewusst werden muss, wo und in welcher Entfernung sind sie befinden. Wenn der Zeitpunkt ihrer Existenz und ihrer Antwort nicht festgeschrieben sein müssen. Wenn die Datenstrukturen und Aufrufstrukturen nicht in Beton gegossen sind. Und wenn es egal ist, wie im Detail die Leistung erbracht wird.

Wenn Sie dem Prinzip der losen Kopplung folgen wollen, dann stellen Sie sich am besten immer wieder diese Fragen. Bemühen Sie sich, die Antworten möglichst allgemein, offen, locker zu halten. Als Gewinn winken höhere Evolvierbarkeit und bessere Testbarkeit.

Aber auch ein Preis ist zu bezahlen. Lose Kopplung ist eine Form von Flexibilität. Das sehen Sie an der Verbindung zwischen Waggons. Flexibilität jedoch steht im Gegensatz zu Effizienz. Wo Sie lose koppeln, leidet also irgendeine Form von Effizienz. Das könnte die Entwicklungsgeschwindigkeit sein oder die Performance der Software. Das ist nicht zu vermeiden.

Lose Kopplung gibt es also nicht umsonst, sondern nur in einem Trade-off mit anderen Werten. Seien Sie sich derer also bewusst. Und denken Sie nicht nur an heute, sondern auch an morgen und übermorgen.

Deshalb ist für mich der default, im Zweifelsfall eher in lose Kopplung zu investieren, also in Flexibilität, denn in Effizienz. Das scheint mir nachhaltiger.

Mittwoch, 17. Juli 2013

Die Teile und das Ganze

Softwareentwicklung verliert immer wieder das Ganze aus dem Blick. Das ist auch nicht verwunderlich bei all dem Druck, der in Projekten herrscht. Denn unter Druck blicken wir schnell in einen Tunnel. Dann schauen wir nur noch vor die Füße aufs Vorankommen. Irgendwie.

Unter Druck ist der Blick nach innen auf Details gerichtet. Entwickler starren auf Code - und der Code starrt zurück. Die Außenwelt, das Ganze tritt in den Hintergrund. Das mag positiv mal ein Flow-Zustand sein - häufiger jedoch scheint es mir ein verkrampftes Kreisen umeinander. Die Teile nehmen dann nur noch sich wahr.

image

Ich denke, wir müssen das erkennen und bewusst dagegen wirken. Damit eine solche Konzentration von Teilen nicht zu lokalen Optimierungen auf Kosten des Ganzen geht, braucht sie ein Gegengewicht.

Der Kraft, die Beziehungen nach innen ausüben, ist eine Kraft entgegenzusetzen, die die Beziehungspartner von einander getrennt hält - damit sie nicht miteinander verschmelzen oder in den in-fight gehen.

image

Verschmelzung bedeutet Strukturverlust, Aufgabe von Identität und Erhöhung der Entropie. In-fight bedeutet Konflikt und durch drohende Zerstörung ebenfalls Erhöhung von Entropie.

Das Gegengewicht für die Dyade Entwickler-Code ist der Architekt. Er sorgt dafür, dass die Codierung im Detail das Big Picture der Softwaresystemstruktur nicht aus den Augen verliert.

Natürlich entsteht dadurch wieder eine Beziehung, die einen Tunnelblick entwickeln kann. Architekt und Entwickler-Code Dyade sind auch nur Teile eines größeren Ganzen. Sie bilden eine Dyade auf höherer Ebene - die wiederum ein Gegengewicht braucht.

image

Damit Architekt und Entwickler sich nicht in der Technik verlieren, muss ein Abnehmer [1] ständig an ihnen ziehen. Software ist kein Selbstzweck, sondern soll ihm dienen. Der Abnehmer ist dafür verantwortlich, dass “die Techniker” ein ihm nützliches Ganzes nicht aus den Augen verlieren.

Aber auch diese Dyade braucht ein Gegengewicht. Denn auch diese Dyade kann einen Tunnelblick entwickeln. Nicht jeder Weg zum Ganzen ist ein guter. Abnehmer und Techniker können sich in Konflikten aufreiben. Also braucht es eine Instanz, die auf Fortschritt achtet, d.h. das Ganze des Fortschrittsprozesses im Auge behält: den Prozessbevollmächtigten.

image

Welchem Prozess die Softwareproduktion folgt, ist mir hier nicht wichtig. Ohne geht es jedoch nicht. Und dass der eingehalten wird, darauf muss jemand achten. Sonst besteht Gefahr, dass die Produktion sich im Hin und Her des Abnahmezugs aufreibt.

Und wie geht es weiter? Braucht nicht auch diese Dyade ein Korrektiv? Natürlich. Doch dann ist ja kein Ende solcher Gegengewichthierarchie abzusehen. Bis zum Bundespräsidenten möchte ich dieses Muster nicht fortsetzen ;-)

Aber mir scheint es praktikabel, die Hierarchie mit einem Kollektiv abzuschließen:

image

Das macht auch deutlich, dass keines der Gegengewichte bzw. der Teile “besser” oder “mächtiger” ist. Sie unterscheiden sich nur in ihrem Horizont. Das Softwaresystem ist ein Ganzes und so ist auf oberster Ebene auch die Führung des Produktionsprozesses ein Ganzes.

Ich denke, an diesen Rollen geht nichts vorbei. Wir brauchen sie - das sehe ich immer wieder, wenn sie fehlen. Ob sie durch unterschiedliche Personen oder in Personalunion ausgefüllt werden, ist mir an dieser Stelle nicht so wichtig. Ab einer bestimmten Projektgröße ist es immer sinnvoll, Personalunionen aufzugeben.

Wichtiger ist, diese Verantwortlichkeiten als solche zu erkennen und allseitig dafür Bewusstsein zu schaffen.

Endnoten

[1] Begriffe wie “Kunde” oder “Product Owner” oder “Produktmanager” will ich vermeiden. Daran hängen mir gerade zu viele Vorstellungen.