Follow my new blog

Samstag, 29. August 2009

Entspannte Persistenz – The Lounge Repository

Relationale Datenbanken sind nicht die ganze Wahrheit für die Datenspeicherung. Das merken immer mehr Entwickler. Die Zahl der “alternativen Datenbanken” nimmt zu und die RDBMS-Frustrierten formieren sich schon: NOSQL ist der Schlachtruf.

image Einige Prominenz hat bei den “alternativen Datenbanken” nun CouchDB erlangt: eine schemalose Datenbank zur Speicherung von Dokumenten. Der API ist denkbar einfach, Queries sind natürlich auch möglich – aber für viele Anwendungen sind Dokumente eine unpassende Abstraktion ihrer Daten. Ein Kunde mit seinen Adressen mag noch als Dokument durchgehen. Doch wie ist es mit einem Kunden und seinen Rechnungen oder umgekehrt einer Rechnung mit ihrem Kunden? Kunden und Rechnungen als separate Dokumente anzusehen, funktioniert, doch sie müssen ja in Beziehung gesetzt werden. Beziehungen zwischen Dokumenten sind jedoch nicht natürlich. Das macht ja gerade die “Dokumentenhaftigkeit” aus, dass in einem Dokument Daten zusammengefasst sind, die eng zueinander gehören. Ein Dokument ist etwas Abgeschlossenes, es ist self-contained.

Dennoch übt die Einfachheit der Persistenz mit CouchDB Faszination aus. StupidDB versucht z.B. das Persistenz-Paradigma von CouchDB in die .NET-Welt zu bringen und noch “einen oben drauf zu setzen”: StupidDB ist bewusst serverlos, denn “[d]urch Replikation des Filesystems z.B. per Windows-DFS ist […] eine einfache Hochverfügbarkeit und Skalierbarkeit der Datenbasis” herstellbar.

So schön einfach die Persistenz mit StupidDB jedoch auch ist, sie leidet unter demselben Problem wie CouchDB. StupidDB verwaltet Dokumente, die nur als Ganzes gespeichert werden. Objektgraphen werden en bloc in eine Datei serialisiert.

Schemalosigkeit für Geschäftsanwendungen: The Lounge Repository

Motiviert durch diese Ansätze habe ich nun versucht, die Vorteile der Schemalosigkeit auch für Geschäftsanwendungen zu erschließen. Statt stupide auf dem Sofa abzuhängen, finde ich es jedoch zeitgemäß und auch geselliger, zu “loungen”. Deshalb habe ich meinen kleinen Open Source Persistenzframework “The Lounge Repository” genannt.

Der Name ist Programm:

  • “Lounge” soll anzeigen, dass es ein denkbar einfach und intuitiv zu benutzender Framework ist. Entspannt Objektgraphen persistieren: das soll The Lounge Repository möglich machen. Sie müssen das Persistenzmedium (hier: das Dateisystem) nicht mit einem Schema strukturieren, bevor Sie darin etwas speichern können.
  • “Repository” statt des verbreiteten Suffixes “DB” soll einen Hinweis auf die Art der Daten geben, die mit dem Framework verwaltet werden.  Repository ist ein Begriff aus dem Domain Driven Design (DDD) und bezeichnet eine Dienstleistung zur Speicherung von Entities: “Definition: A Repository is a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.”

Entspannt Entities persistieren, darum geht es bei The Lounge Repository. Es ist als generisches Repository für alle möglichen Arten von Entities gedacht.

Was ist eine Entity? Ein zustandsbehaftetes Objekt mit einer eigenen, speicherübergreifenden Identität. Sie identifiziert es im Hauptspeicher wie im Persistenzmedium.

Kunde und Rechnung sind naheliegende Entitäten für eine Faktura-Anwendung. Eine Rechnungsposition jedoch nicht, da sie nicht unabhängig von einer Rechnung existiert. Auf Kunden wie Rechnungen möchte man sicher direkt zugreifen, sie müssen unabhängig von einander adressierbar sein; auf Rechnungspositionen kommt man hingegen nur über ihre Rechnung. Rechnungspositionen sind im DDD-Jargon sog. Value Objects.

Wenn Sie Ihre Anwendung auf der Basis von DDD modellieren, dann kommen am Ende sicherlich auch Entities und Value Objects heraus, die Sie persistieren wollen. Entities sollen einzeln geladen und gespeichert werden oder in größeren Zusammenhängen, z.B. eine Rechnung zusammen mit ihrem Kunden. Solche Zusammenhänge (cluster) nennt DDD Aggregate.

image

Wie nun die Persistenz von Éntities bewerkstelligen? Sicherlich können Sie dafür ein RDBMS Ihrer Wahl heranziehen. ADO.NET macht es dann möglich. Oder Sie machen es sich schon etwas einfacher und benutzen einen O/R-Mapper Ihres Geschmacks – von EntityFramework über Open Access und LLBLGENPRO bis zu NHibernate. Aber ich versichere Ihnen, wenn Sie nicht sattelfest mit einem dieser Persistenzframeworks sind, dann werden Sie eine rechte Mühe haben, Ihre Entities zu persistieren. Persistor.Net verspricht zwar, Ihnen einige dieser O/R-Mapping-Mühen abzunehmen, aber auch er setzt auf ein RDBMS, das es dann zu verwalten gilt.

Entity Graphen im Dateisystem speichern

CouchDB & Co. haben es deshalb auch leicht gehabt, bei der Usability zu punkten. Ihre APIs sind allemal “für die schnellere Persistenz zwischendurch” sehr schön einfach. Nur leider passen sie, wie schon erwähnt, nicht so gut zum Datenmodell er üblichen Geschäftsanwendungen. Objektgraphen mit vielen Entities (Aggregate) lassen sich nicht wirklich als Dokument beschreiben. Denn wenn dieselbe Entity in mehreren solchen Aggregaten vorkommt, wird sie in mehreren Dokumenten persistiert. Sie verliert damit ihre zweckstiftende Eigenschaft, ihre Identität, ihre Eindeutigkeit.

image

So sieht Ihr schöner Entity Graph aus, wenn CouchDB oder StupidDB ihn gespeichert haben. Alles ist zu einem Dokumentenganzen zusammengefasst.

The Lounge Repository macht es hingegen anders! Jede Entity wird hier separat in einer Datei gespeichert, egal wie tief eingeschachtelt sie in einem Entity Graphen ist. Das funktioniert natürlich auch mit zyklischen Referenzen:

image

The Lounge Repository erhält die logische Identität durch physische Separierung. Das unterscheidet es wesentlich von StupidDB.

Der Lounge Repository API

The Lounge Repository ist zunächst eine Fingerübung (oder eine umfangreichere Code Kata, wenn Sie so wollen). Mit dem Lounge Repository will ich also zwei Fliegen mit einer Klappe schlagen: Ich möchte ein Werkzeug haben, mit dem ich meine Gedanken zum Thema Schemalosigkeit praktisch ausprobieren kann. Und ich möchte in kontrollierter Umgebung meinen Programmiersüchten nachgehen können ;-) Denn wer würde leugnen wollen, dass die Programmierung von Infrastruktur nicht süchtig macht? Im kleinen Freizeitrahmen ist das aber genauso wenig schlimm, wie ein gelegentliches Bierchen am Abend oder eine Zigarette alle Jahre wieder auf der Wies´n. In den endlich-clean.net Entzug muss ich deshalb jedenfalls noch nicht. Allemal, weil ich mich bemüht habe, den Lounge Repository Code clean zu halten. Doch Vorsicht vor Infrastrukturprogrammierung in Ihren Projekten!

Doch jetzt weiter zu Konkretem, zum Code. Wie sieht der API des Lounge Repository aus? Ein Hello-World-Beispiel zeigt das Grundsätzliche in wenigen Zeilen. Laden Sie den Quellcode von CodePlex herunter und machen Sie mit. Das geht mit einem SVN-Client wie Tortoise ganz schnell. Die Quellen enthalten auch eine Projektmappe mit kleinen Beispielen.

Hier nun ein Beispielprojekt, wie Sie es aufsetzen können, wenn Sie die Lounge Repository Quellen mittels deren Projektmappe einmal übersetzt haben. Es stehen dann im globalen bin-Verzeichnis des Quellbaumes die Assemblies des Frameworks bereit:

image

Davon binden Sie zunächst aber nur zwei ein: LoungeRepo.Core und LoungeRepo.Contracts:

image

Mehr ist als Vorbereitung nicht nötig. The Lounge Repository kennt keine Datenbankdateien und hat (noch) keinen Serverprozess. Es ist eine experimentelle “embedded database”.

Und jetzt der Hello-World-Code:

    1 using System;

    2 using LoungeRepo.Contracts.Core;

    3 using LoungeRepo.Core;

    4 

    5 namespace BlogSample

    6 {

    7     class Program

    8     {

    9         static void Main()

   10         {

   11             using(ILoungeRepository repo = new LoungeRepository())

   12             {

   13                 repo.Store("hello, world!", "1");

   14 

   15                 string greeting = repo.Load<string>("1");

   16                 Console.WriteLine(greeting);

   17             }

   18         }

   19     }

   20 }

Mit Store() speichern Sie Entities, mit Load() laden Sie sie wieder. So einfach ist das mit der Persistenz.

Eine Entity ist jedes Objekt, dem Sie dieses Privileg zugestehen möchten. Sie müssen die Klassen zu persistierender Objekte nicht mit einem bestimmten Attribut kennzeichnen oder von bestimmten Klassen ableiten. Eine Zeichenkette kann genauso gut wie ein Kunde-Objekt eine Entity sein:

    5 namespace BlogSample

    6 {

    7     class Kunde

    8     {

    9         public string Name { get; set; }   

   10     }

   11 

   12     class Program

   13     {

   14         static void Main()

   15         {

   16             using(ILoungeRepository repo = new LoungeRepository())

   17             {

   18                 Kunde k = new Kunde {Name="Peter"};

   19                 repo.Store(k, "2");

   20 

   21                 k = repo.Load<Kunde>("2");

   22                 Console.WriteLine(k.Name);

   23             }

   24         }

   25     }

   26 }

Wichtig ist, dass jede Entity auch wirklich eine Identität hat. Die ist beim Speichern und Laden wichtig. Denn aus ihr “berechnet” das Repository den Namen der Datei, in der es die Entität speichert bzw. aus der es sie lädt.

Ein string oder das obige Kundenobjekt haben keine von ihrer Hauptspeicheradresse unabhängige Identität. Also muss der Code explizit eine beim Speichern angeben.

Die Identität einer Entity besteht aus zwei Teilen: einer Id (eine Zeichenkette Ihrer Wahl) und einer optionalen Partition (ebenfalls eine Zeichenkette Ihrer Wahl).

Partitionen unterteilen den “Persistenzraum” (hier: das Dateisystem) und ermöglichen auf lange Sicht eine Lastverteilung. Entities einer bestimmten Partition könnten von dedizierten Servern verwaltet werden. Auch wenn das Zukunftsmusik ist, habe ich mir gedacht, das grundlegende Konzept der Partitionierung schon jetzt mit in die Funktionalität aufzunehmen. Das mag ein wenig YAGNI sein… aber was soll´s? ;-)

Innerhalb einer Parition muss dann die Id eindeutig sein. Zusammen ergeben sie die Identität, die im “Persistenzraum” eindeutig ist. Ist keine Partition definiert, nimmt das Lounge Repository eine default Partition an.

Wenn Sie nicht wissen, was Sie als Partition angeben sollen, dann lassen Sie sie aus – oder wählen Sie z.B. den Klassennamen einer Entity als Partitionsnamen:

   12 class Rechnung

   13 {

   14     public string Rechnungsnummer;

   15     public Kunde Empfänger;

   16 }

   17 

   18 class Program

   19 {

   20     static void Main()

   21     {

   22         using(ILoungeRepository repo = new LoungeRepository())

   23         {

   24             Kunde k = new Kunde {Name="Maria"};

   25             Rechnung r = new Rechnung

   26                             {

   27                                 Rechnungsnummer = "090829-1",

   28                                 Empfänger = k

   29                             };

   30             repo.Store(r, "3", "Rechnung");

   31 

   32             r = repo.Load<Rechnung>("3", "Rechnung");

   33             Console.WriteLine("#{0} für {1}",

   34                         r.Rechnungsnummer,

   35                         r.Empfänger.Name);

   36         }

   37     }

   38 }

Aber nicht nur die Partition “Rechnung” für die Rechnung-Entity ist bemerkenswert an diesem Stück Code. Bitte beachten Sie auch folgendes:

  • Die Kundin Maria wurde natürlich zusammen mit der Rechnung persistiert.
  • Beim Laden der Rechnung wurde die Empfängerin natürlich auch wieder mit geladen.
  • In diesem Beispiel gibt es nur eine Entity: die Rechnung. Das Kunde-Objekt hat von sich aus keine Identität und wurde nicht ausdrücklich als Entity gespeichert.

Das Repository sieht jetzt so aus:

image 

Die Entities aus den vorangehenden Beispielen hatten keine eigene Partition und wurden daher in der default Partition gespeichert. Entity “3”, die Rechnung, steht in der explizit angegebenen Partition “Rechnung”.

Und wo ist die Kundin Maria? Sie steckt in der Rechnung-Entity mit Namen 3.entity, weil ihr Kunde-Objekt ja nicht als Entity gespeichert wurde. Das ist verständlich, oder? Schön ist es aber nicht. Sie wollen ja nicht alle Entity-Objekte explizit speichern müssen. Außerdem würde das nichts nützen, denn selbst wenn das Objekt zu Kundin Maria als Entity gespeichert worden wäre, würde das Repository das nicht merken während der Speicherung der Rechnung. Die “Entitätshaftigkeit” ist einem Kunde-Objekt ja nicht anzusehen. Bisher.

Um Entities nicht mit expliziter Identitätsangabe speichern zu müssen und auch in Entity Graphen erkennbar zu machen, können Sie sie das Interface ILoungeRepoEntityIdentity implementieren lassen. Das definiert nur zwei Properties: Id und Partition. Es trägt also nicht dick auf Ihre Domänenobjekte auf. Damit wird dann das kleine Szenario wirklich intuitiv:

    7 class Kunde : ILoungeRepoEntityIdentity

    8 {

    9     #region Implementation of ILoungeRepoEntityIdentity

   10     public string Id { get; set; }

   11     public string Partition { get { return "Kunden"; } }

   12     #endregion

   13 

   14     public string Name { get; set; }

   15 }

   16 

   17 

   18 class Rechnung : ILoungeRepoEntityIdentity

   19 {

   20     #region Implementation of ILoungeRepoEntityIdentity

   21     public string Id { get { return this.Rechnungsnummer; } }

   22     public string Partition { get { return "Rechnungen"; } }

   23     #endregion

   24 

   25     public string Rechnungsnummer;

   26     public Kunde Empfänger;

   27 }

   28 

   29 

   30 class Program

   31 {

   32     static void Main()

   33     {

   34         using(ILoungeRepository repo = new LoungeRepository())

   35         {

   36             Kunde k = new Kunde {Id="4", Name="Maria"};

   37             Rechnung r = new Rechnung

   38                             {

   39                                 Rechnungsnummer = "090829-1",

   40                                 Empfänger = k

   41                             };

   42             repo.Store(r);

   43 

   44             r = repo.Load<Rechnung>("090829-1", "Rechnungen");

   45             Console.WriteLine("#{0} für {1}",

   46                         r.Rechnungsnummer,

   47                         r.Empfänger.Name);

   48 

   49             k = repo.Load<Kunde>("4", "Kunden");

   50             Console.WriteLine(k.Name);

   51         }

   52     }

   53 }

Kunde und Rechnung implementieren nun das ILoungeRepoEntityIdentity Interface und liefern dem Repository darüber ihre Identitäten. Den Kunden habe ich zur Demonstration so ausgelegt, dass ihm die Id bei Erzeugung zugewiesen werden muss, die Rechnung entnimmt sie ihrer Rechnungsnummer. Beide enthalten jedoch eine fest verdrahtete Partition.

Die Rechnung wird wie erwartet auch jetzt wieder mit ihrem Kunden geladen, darüber hinaus kann der Code jedoch auf den Kunden auch direkt zugreifen, wie Zeile 49 zeigt.

Das ist im Grunde alles, was es zum Laden und Speichern zu sagen gibt. Sie müssen keine Vorbereitungen treffen, aber Entities sollten gekennzeichnet sein. Alle nicht gekennzeichneten Objekte sind für das Repository Value Objects.

Objektgraphen, d.h. Objekthierarchien und –netzwerke – auch solche mit Zyklen – werden korrekt gespeichert/geladen, d.h. Entitäten wandern in je eigene Dateien. Wie Sie Objektverweise aufbauen, ist Ihnen überlassen. Sie können einzelne Referenzen halten wie die Rechnung auf ihren Empfänger. Oder Sie benutzen Arrays oder Collections, um mehrere Referenzen zu verwalten.

The Lounge Repository persistiert alle Felder der Objekte, die ihm zur Speicherung übergeben werden. Immer. Es findet (derzeit) kein change tracking statt. Wollen Sie ein Feld ausschließen, dann setzen Sie darüber das [NonSerialized] Attribut, das Sie von der .NET-Serialisierung kennen.

Dass Sie Entitäten auch löschen können, ist selbstverständlich. Rufen Sie Delete() auf dem LoungeRepository unter Angabe der Identität auf.

Zum Schluss bleibt nur noch eine Frage: Kann man eigentlich auch Entitäten durch Queries ermitteln? Ja, man kann. Das Lounge Repository sammelt alle Entitäten eines Typs in einem sog. Extent. Das ist nichts weiter als eine lange Liste von Objekten, die das Repository als IEnumerable<T> anbietet. Deshalb können Sie darauf mit Linq in gewohnter Weise zugreifen:

   33 static void Main()

   34 {

   35     using(ILoungeRepository repo = new LoungeRepository())

   36     {

   37         Kunde k = new Kunde {Id="4", Name="Maria"};

   38         Rechnung r = new Rechnung

   39                             {

   40                                 Rechnungsnummer = "090829-1",

   41                                 Empfänger = k

   42                             };

   43         repo.Store(r);

   44 

   45         r = new Rechnung

   46                     {

   47                         Rechnungsnummer = "090715-2",

   48                         Empfänger = k

   49                     };

   50         repo.Store(r);

   51 

   52         k = new Kunde { Id = "5", Name = "Dennis" };

   53         r = new Rechnung

   54                     {

   55                         Rechnungsnummer = "090803-3",

   56                         Empfänger = k

   57                     };

   58         repo.Store(r);

   59 

   60 

   61         var mariasRechnungen =

   62             from rg in repo.GetExtent<Rechnung>()

   63                 where rg.Empfänger.Name == "Maria"

   64                 select rg;

   65 

   66 

   67         foreach (Rechnung mariasRg in mariasRechnungen)

   68             Console.WriteLine("#{0} für {1}",

   69                 mariasRg.Rechnungsnummer,

   70                 mariasRg.Empfänger.Name);

   71     }

   72 }

Die Zeilen 37 bis 58 bauen eine kleine Datenbasis an persistenten Entities auf. Und die Zeilen 61 bis 64 fragen sie mit einer Linq-Query ab. repo.GetExtent<T>() liefert dafür die Grundlage in Form einer Liste aller Rechnung-Entities, die geladen oder gespeichert wurden.

Hier liegt z.Z. noch eine Begrenzung des Lounge Repository: Extents enthalten zunächst nur Objekte, die das Repository “gesehen” hat. Objekte, die auf der Platte im Repository liegen, aber vom Repository weder direkt oder indirekt geladen wurden, sind (noch) nicht in dessen Cache enthalten und tauchen daher nicht im Extent auf.

Das können Sie jedoch ausbügeln, indem Sie zu Beginn einer Sitzung den internen Cache mit allen persistenten Entities populieren. Ja, so laden Sie zwar die ganze Datenbank in den Hauptspeicher, aber das macht nichts. The Lounge Repository ist (zunächst) genau für solche Szenarien gedacht, in denen Sie eben nicht Gigabytes an Daten verwalten. Selbst einige Hundert Megabytes in den Hauptspeicher zu laden auf einem 4 GB Laptop sollte allerdings den Kohl nicht fett machen.

Für einen solchen sog. “prefetch” binden Sie einfach die Assembly LoungeRepo.Core.Extensions ein und importieren Sie den gleichnamigen Namensraum. Dann sind alle Entities über ihre Extents zu erreichen:

    5 using LoungeRepo.Core.Extensions;

    6 

    7 namespace BlogSample

    8 {

    9     …

   32     class Program

   33     {

   34         static void Main()

   35         {

   36             using(ILoungeRepository repo = new LoungeRepository())

   37             {

   38                 repo.PrefetchAllEntities();

   39                 …

   63                 var mariasRechnungen =

   64                     from rg in repo.GetExtent<Rechnung>()

   65                         where rg.Empfänger.Name == "Maria"

   66                         select rg;

Ausblick

The Lounge Repository ist “a work in progress”. Ich habe mir damit eine Spielwiese angelegt, auf der ich Ideen zum schemalosen Umgang mit Daten und anderem ausprobieren kann. Das Projekt ist Open Source und Sie finden es bei CodePlex in seiner vollen “clean beauty”: http://loungerepo.codeplex.com/

Laden Sie den Quellcode runter und spielen Sie damit. Wenn Sie Fragen oder Einfälle haben, lassen Sie uns bei CodePlex darüber diskutieren. In der Projektmappe finden Sie auch eine kleine Aufgabenliste, die ich führe. Da sehen Sie, dass noch einiges zu tun ist am Lounge Repository. Und auch darüber hinaus habe ich schon Ideen, z.B. wie ein solche Repository verteilt und asynchron betrieben werden kann.

Einstweilen mag The Lounge Repository Datenbanken wie SQL Server oder selbst CouchDB nicht ersetzen. Aber ich würde mich freuen, wenn in ihm ein Keim läge, der es in einigen Szenarien zu einer Alternative zum default RDBMS machte. Prototypen, kleine Anwendungen… dort, wo Sie Entitäten identifizieren und “schnell mal persistieren wollen” ohne Schemaaltlasten, hat The Lounge Repository (bzw. eine der anderen “alternativen Datenbanken”) es sicher verdient, berücksichtigt zu werden.

Ich bin gespannt auf Ihr Feedback.

Sonntag, 23. August 2009

Was ist mit Bob? – Verwirrung anlässlich einer Code Kata

image Wollte mich heute mal zur Entspannung mit einer Code Kata beschäftigen. Meine Wahl fiel auf die "Bowling Game Kata" von unser aller Onkel Bob. Das PPT dazu sieht hübsch strukturiert aus:

  • Am Anfang werden die Regeln für die Punktezählung bei einem Bowling Game erklärt. Da besteht ein Spiel z.B. aus 10 Sätzen (Frame), innerhalb derer mehrere Würfe (Roll) gemacht werden dürfen, um zu einem Punktestand (Score) für den Satz zu kommen. Die Gesamtpunktezahl für ein Spiel ist dann die Summe der Punkte der Sätze.
  • Dann wird eine Sollklasse als Anforderung formuliert. Die ist in der Kata per TDD zu implementieren. Ich formuliere sie mal als Interface für C#:

image interface IGame
{
    void Roll(int pins);
    int Score { get; }
}

 

Mit den Regeln und der formalen Anforderung in der Tasche kann es dann losgehen.

Oder man linst mal auf die weiteren Folien der Katabeschreibung - immerhin sind es mehr als 50. Da erklärt Meister Bob nämlich sein Vorgehen.  Aber, wer hätte das gedacht: Statt mit TDD loszulegen, macht Onkel Bob eine Design Session!

image

imageWas ist mit Bob? Da gibt es nicht nur eine Klasse Game, sondern auch eine Klasse Frame und Roll. Hört sich ja auch plausibel an, wenn man die Regeln liest. Darin tauchen diese Begriffe als Substantive auf. Aber warum müssen die denn in ein Design einfließen? Ist es ausgemacht, dass man sie für die Implementation wirklich braucht?

Oder besser: Ist es aus den Anforderungen ablesbar, dass diese Klassen gebraucht werden? Ich glaube, das kann man nicht. Denn ich habe meine Implementation der Anforderung “Implementiere eine Klasse Game wie folgt…” strickt mit TDD begonnen. Für so ein kleines Beispiel habe ich schlicht keine ausdrückliche Design Session für nötig gehalten. Und ich habe auch gedacht, genau das sei eben der Sinn solcher kleinen Katas: dass man eben vor allem den TDD-Prozess einübt mit seiner Schrittfolge red-green-refactor. Das Design soll iterativ und absolut bedarfsgetrieben evolvieren. Und dann sowas von Bob?!

Bei meinem TDD-Vorgehen konnte ich keine Notwendigkeit erkennen, Klassen jenseits von Game zu implementieren. Den Grund halte ich für ganz naheliegend: Die Anforderung (!) enthält aber auch gar keinen Bezug zu Sätzen und Würfen.

Die Regeln sagen zwar, dass man pro Satz nur soundsoviele Würfe machen darf und bestimmte Wurferfolge Zusatzwürfe (Bonus) gestatten. Doch die Punktezählung ist am Ende nur eine schlichte Addition aller Wurferfolge (Pins).

Darüber hinaus abstrahiert die Klasse Game von all diesen Details, indem auf ihr einfach immer nur wieder Roll() aufgerufen werden soll:

game.Roll(5);
game.Roll(3);
game.Roll(7);
game.Roll(9);
…
Console.WriteLine(game.Score);

image Was, bitte, hat das noch mit Sätzen zu tun? Ob ein Wurf ein Bonuswurf ist, entscheidet nicht die Klasse Game. Das steht jedenfalls nicht in den Anforderungen. Auch ist nicht erwähnt, ob irgendwie die Einhaltung der Regeln geprüft werden soll. Oder warum die Einschränkung, Score nur am Ende aufzurufen? Es macht keinen Unterschied, wann man Score befragt, da die Gesamtpunktezahl immer nur eine Addition aller per Roll() gemeldeten Wurfergebnisse ist.

Was ist also mit Bob? Wie kommt es, dass er eine so merkwürdige Aufgabe stellt und sich letztlich nicht an die eigenen Prinzipien hält? Explizites Design statt TDD – was hat ihn bei der Aufgabengröße denn da geritten?

Oder habe ich da etwas übersehen, falsch verstanden? Ist mir in der Spieldefinition etwas entgangen? Ist mir der tiefere Sinn der Anforderung an die Klasse Game entgangen? Ich bitte um Aufklärung.

Empfehlung für Katas

Egal, ob ich etwas nicht richtig verstanden habe oder in der Aufgabenstellung der Wurm ist, ich denke, eine Lehre lässt sich in jedem Fall ziehen: keine Code Kata ohne Akzeptanztests!

Alles wäre leichter, wenn Bob der Kata eine Datei in einem einfachen Format beigegeben hätte, die Sollergebnisse enthält. Schon folgendes hätte gereicht:

7,2,5,4,…,103
3,4,10,7,…134
…

Wobei jede Zahl einen Wurf repräsentiert und die letzte den Score.

Wer also Code Katas sucht, der sollte darauf achten, dass ihr “Abnahmetests” beiliegen. Und wer Code Katas beschreiben will, dem sei empfohlen, ein bisschen Mühe auf die Definition von “Abnahmetests” zu verwenden. Sie steigern den Wert der Code Kata – oder machen sie erst überhaupt durchführbar.

Samstag, 22. August 2009

Code cleaning – aber wann? [endlich-clean.net]

image Neulich habe ich ein hübsches Scrum-Buch gelesen: Scrum mit User Stories. Darin gibt es eine “Definition of Done”. Die beschreibt, wann ein User Story (Anforderung) eigentlich vom Team als fertig anzusehen ist. Denn erst wenn sie fertig ist, kann das Team mit der nächsten in einem Sprint weitermachen.

Ein Kritierium für “Done” ist darin: “Die User Story führt zu keinem Anstieg der ‘Technischen Schuld’”. Das hört sich gut an. Da hab ich bei der Lektüre sofort zustimmend genickt. An anderer Stelle ist es so formuliert: “[Nicht] refaktorisierte User Stories [sind] nicht fertig.”

Dass zu fertig die Refaktorisierung gehört, steckt auch im TDD-Vorgehen red-green-refactor drin.

Also ist alles klar, oder? Fertig bedeutet refaktorisiert. Ein Entwickler fühlt sich nicht wohl, bevor der Code nicht sauber gemacht ist. Erst dann gibt er ihn guten Gewissens an die nächste Phase im Softwareproduktionsprozess weiter. Das ist Clean Code Development.

Oder vielleicht doch nicht? Heute sind mir nämlich Zweifel gekommen.

Nein, ich zweifle nicht am Wert von Clean Code. Refaktorisierung ist ne gute Sache. Aber wann? Sollten sie am Ende einer Implementierungsphase stehen? Nein!

Es ist ein Missverständnis, mit Refactoring eine Implementation abschließen zu wollen. Ich glaube, wenn man einen kleinen Moment drüber nachdenkt, dann ist das auch ganz einsichtig. Refaktorisierter Code ist ein Feature von Software. Er steht im Grunde auf derselben Stufe mit Funktionalität oder Performance. Nur fordert dieses Feature nicht der Anwender, sondern das Entwicklerteam.

Refaktorisierung unterliegt damit genauso den Prinzipien YAGNI, KISS und Beware of Premature Optimization!

Wenn ein Entwickler auf dem Zettel hat, die Multiplikation für einen Taschenrechner zu implementieren, dann ist seine Arbeit fertig, wenn er die Multiplikation korrekt aus Sicht des Kunden implementiert hat. Wenn er darüber hinaus jedoch auch noch nach dieser getanen Arbeit refaktorisiert, dann halte ich das für eine vorzeitige Optimierung. Niemand weiß, ob der refaktorisierte Zustand der Anwendung irgendwann mal nützlich wird. Vielleicht fällt dem Kunden ein, dass er die Multiplikation nicht braucht und alles per Addition rechnet. Dann ist der Refaktorisierungsaufwand vergebens gewesen.

Ein refaktorisierter Zustand ist daher genau wie jedes andere Feature erst dann herzustellen, wenn wirklich klar ist, dass es gebraucht wird. Das bedeutet, die Arbeit an der inneren Qualität findet vor (!) der Arbeit an der äußeren Qualität statt. Das wird auch klar, wenn wir den TDD-Prozess verlängern und etwas anderes notieren:

  • red-green
  • refactor-red-green
  • refactor-red-green
  • refactor-read-green
  • …

Refactor steht in der kurzen Phase “red-green-refactor” nur am Ende, weil impliziert wird, dass es danach weitergeht. Insofern finden während der Implementation eines äußeren Features natürlich immer auch Refaktorisierungen statt.

Ich halte es jedoch für sehr bedenkenswert, am Ende (!) nicht ruhen zu wollen, bevor nicht “alles” so richtig sauber ist. Stattdessen sollte der Entwicklungsprozess vorsehen, dass zu Beginn (!) der Arbeit an äußerer Qualität zuerst die nötige innere Qualität hergestellt wird. Und zwar nur die wirklich nötige innere Qualität!

Ohne ein Maß kann Refaktorisierung genauso zur Sucht werden wie Performanceoptimierung. Wenn also Performanceoptimierung nur stattfinden soll mit konkreter Zielvorgabe (“Die Suche nach einem Kunden darf höchstens 1 Sekunde dauern.”), dann soll auch Refaktorisierung nur mit konkreter Zielvorgabe stattfinden.

In Ermangelung quantifizierbarer Refaktorisierungsziele ist deshalb die Zielvorgabe einer Refaktorisierung eine innere Qualität, die die Implementation des nächsten Kundenfeatures leicht macht. Was dafür nötig ist, ist allerdings erst klar, wenn die Arbeit an diesem Kundenfeature beginnt.

Ich denke daher, die Arbeit an einer User Story sollte so definiert sein:

  1. Plane Implementation der User Story bzw. einer Task innerhalb der User Story
  2. Refaktorisiere vorhanden Code vor Beginn der Implementation nach Bedarf
  3. Implementiere mittels refactor-red-green
  4. Liefere User Story bzw. Task aus

Am Ende des letzten kleinen refactor-red-green-Schrittes bleibt dann zwar etwas technical debt übrigen, aber das macht nichts. Solange keine weitere Anforderung existiert (oder genauer: keine weitere User Story/Task begonnen wurde), wüsste ja niemand, welchem Zweck eine weitere Refaktorisierug dienen sollte.

Die ursprüngliche “Defintion of Done” ist damit sogar fast erfüllt. Zwar ist der Code nicht ohne “Technische Schuld”, aber die ist verschmerzbar klein, nein, sogar unvermeidbar, wenn Sie nicht in Bezug auf CCD in die YAGNI-Falle tappen wollen.

Nur so ist sichergestellt, dass die CCD-Bausteine konsequent, d.h. selbstbezüglich angewandt werden. CCD-Bausteine sind kein Selbstzweck und müssen mit Augenmaß angewandt werden.

Donnerstag, 20. August 2009

Ist Kunden schlechte Softwarequalität egal? [endlich-clean.net]

In einem Kommentar zum Interview über CCD, das heise Developer mit mir gemacht hat, heißt es, dass Kunden doch sowieso keinen Sinn für schlechte Softwarequalität hätten. Gemeint ist natürlich nicht die äußere Qualität, die unmittelbar ihren Anforderungen entsprechen soll. Kunden haben selbstverständlich kein Verständnis, wenn Funktionalität fehlt oder die Software zu langsam ist. Da sind sie ganz sensibel. Gemeint ist also ein mangelnder Sinn für die innere Qualität von Software, um die es bei Clean Code Developer geht.

Das hört sich plausibel an. Ihnen ist diese innere Qualität egal, deshalb wird soviel schlechte innere Qualität produziert und akzeptiert. Ist das aber wirklich so? Ist den Kunden die innere Qualität wirklich egal?

Nein, das glaube ich nicht. Sie ist ihnen natürlich nicht egal. Ihnen mag egal sein, was Entwickler bei der Produktion schlechter Qualität empfinden, ob denen das egal ist oder sie sich ständig dabei unwohl fühlen. Aber schlechte innere Qualität ist ihnen nicht egal. Warum ist die Qualität dann so schlecht, wenn es den Kunden doch nicht egal ist und vielen (oder gar den meisten?) Entwicklern auch nicht? Ich glaube, dafür gibt es zwei Gründe:

  1. Missverständnis: Die Kunden leiden unter einem Missverständnis. Sie glauben, die innere Qualität sei hoch. So wie die innere Qualität von Autos oder Butter hoch ist.
  2. Blindheit: Weil die Kunden glauben, die innere Qualität sei hoch, vertrauen sie der Softwareentwicklung, wenn die ihnen sagt, wie teuer etwas sein wird und wie lange es dauert. Schlicht aus Basarmentalität heraus feilschen sie dann selbstverständlich – jeder weiß ja, dass es immer noch ein wenig schneller und preiswerter geht, als ein Anbieter behauptet –, doch letztlich akzeptieren die Kunden die Behauptungen und Versicherungen der Softwareentwicklung. Und weil sie das tun – womöglich auch aus einem Gefühl der Inkompetenz heraus –, schauen sie nicht genauer hin. Sie haben keinen eigenen Anspruch an die innere Qualität und damit keinen Maßstab und keine Messinstrumente für die innere Qualität. Auf dem Auge “Innere Softwarequalität” sind sie sozusagen blind. (Die üblichen Controller sind da übrigens kein korrektiv, weil sie selbst auch keine Wahrnehmung, keinen Anspruch an innere Qualität haben und ihr Horizont eher eng ist.)

Das bedeutet, Kunden akzeptieren aus einem Missverständnis heraus und aufgrund einer Unfähigkeit, das Gegenteil behaupten zu können, die schlechte Qualität von Software. Aber nicht, weil sie ihnen egal ist.

Wenn nun jemand daher kommt und den Kunden klarmacht, dass 1. die innere Qualität von Software schlecht ist, sie also einem Missverständnis aufsitzen, und 2. diese schlechte Qualität sie viel Geld kostet, dass hier also einiges an Einsparpotenzial in den IT-Ausgaben schlummert, dann… ja, dann muss sich die Softwareentwicklung warm anziehen. Denn dann werden die Kunden ganz deutlich ihren Qualitätsanspruch formulieren. Wohl dem also, der dann schon die Kompetenz hat, ihre neuen Ansprüche auch zu erfüllen.

Oder kommt dann alles anders? Winken die Kunden dann ab und gähnen, weil sie das doch schon alles wissen? “Klar, wir wissen, die innere Qualität ist schlecht und wir hauen riesige Summen für Änderungen an verquarzter Software raus – aber es ist uns egal. Wir tun das ganz bewusst, weil wir schneller Ergebnisse jetzt sofort sehen wollen, statt später mit höherer innerer Qualität.” Könnte es sein, dass Kunden diese Antwort geben? Vielleicht der eine oder andere. Im Großen und Ganzen bezweifle ich es jedoch.

Es bleibt also dabei: Wenn die Kunden anfangen, sich den Sand des Missverständnisses aus den Augen zu wischen und ihre Blindheit überkommen, dann wird es wirklich spannend in der Softwarebranche.

Donnerstag, 30. Juli 2009

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

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

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

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

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

Dabei ist die Lösung ganz, ganz einfach:

Softwareprojekte brauchen noch mehr Termindruck!

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

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

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

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

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

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

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

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

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

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

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

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

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

Mittwoch, 29. Juli 2009

Hierarchische partielle Klassen [endlich-clean.net]

Neulich habe ich einen Weg beschrieben, um Code, der nach dem Single Level of Abstraction (SLA) Prinzip strukturiert ist, übersichtlicher zu machen – ohne gleich andere CCD-Bausteine in Anschlag zu bringen. Mein Vorschlag: Partielle Klassen. Damit kann man die durch SLA entstehende Unübersichtlichkeit einer Klasse auflösen, indem man sie partiell macht und auf mehrere Quelldateien verteilt. Ich finde, das funktioniert wunderbar.

Nur einen Nachteil gibt es: Dadurch steigt die Zahl der Quelldateien in einem Projekt. Also habe ich Teile einer Klasse, die sich mit niedrigeren Abstraktionsniveaus beschäftigen, in einen Projektordner ausgelagert:

Das war allerdings nur eine Krücke. Schön fand ich diesen Weg nicht, weil er die Klassenteile voneinander trennt. Viel besser wäre es, wenn man sie zusammenhalten könnte, indem sie dem höchsten Abstraktionsniveau echt untergeordnet werden (hier die Datei TextFileGeneratorSLAv2.cs). Meine Versuche, sie wie die Code-behind-Klassenteile “darunter zu ziehen”, schlugen leider fehl. Also habe ich mich mit dem Projektordner beschieden.

Dank Blog-Leser Karl (der Nachname ist leider mit einem Chatfenster verschwunden) kann ich diese Krücke nun jedoch wegwerfen. Karl hat mich auf dieses Visual Studio Add-In aufmerksam gemacht: VsCommands von Mokosh. Einfach runterladen, MSI-Datei auspacken und installieren; läuft mit VS 2008. Dann ist dies ganz einfach möglich:

image

Jetzt sind die Teile der partiellen Klasse auf einem niedrigeren Abstraktionsniveau direkt denen untergeordnet, die sie verfeinern. Mit VsCommands können partielle Klassen also quasi hierarchisch gemacht werden.

Um eine Datei einer anderen unterzuordnen, die beiden in der Reihenfolge “oben” - “unten” selektieren und dann im Kontextmenü “Group Items” aufrufen:

image

Sehr cooles kleines Tool! Es eröffnet eine neue Dimension der Codeorganisation in Visual Studio Projekten. Danke, Karl, für den Hinweis.

Sonntag, 26. Juli 2009

Wahrhaft modulare Software nur mit konsequent weniger Abhängigkeiten

Warum hat die Softwareentwicklung das mit der Modularität bisher nicht so richtig hingekriegt? Mir scheint eine wesentliche Ursache die zu große Abhängigkeit zwischen Modulen. Wir müssen anders über Abhängigkeiten nachdenken als bisher.

image Darüber habe ich auch schonmal geschrieben, z.B. grad neulich hier. Doch mein Denken kreis immer wieder um das Problem. Deshalb hier nochmal eine etwas andere Perspektive, über die ich schon lange schreiben wollte. Anstoß hat jetzt ein Artikel in der Geo über synthetische Biologie gegeben. Dabei geht es darum, neue Lebewesen (Bakterien) zu produzieren, indem man ihre DNA aus Bausteinen (BioBricks) zusammensetzt, die jeder eine gewünschte Eigenschaft beschreiben. Ein Comic erklärt recht plakativ, wie das zu verstehen ist und funktioniert. Nein, das ist kein Science Fiction! Es wird schon getan.

Die Elektroniker können es also, die Bauingenieure, ebenso die Möbelbauer können es – und nun auch noch die Biologen. Alle können ihre Produkte aus Bausteinen, Bricks, Komponenten zusammensetzen.  Nur die Softwareentwicklung tut sich immer noch schwer. Warum?

Naja, ganz grundsätzlich können wir das natürlich auch. Softwarebausteine gibt es schon lange. Früher mussten sie statisch gelinkt werden, heute geht das dynamisch. DLLs oder Bibliotheksassemblies sind kein Hexenwerk. Allemal die User Control Industrie hat das in den letzten 18 Jahren seit VB 1.0 bewiesen.

Und dennoch: irgendwas fehlt, habe ich das Gefühl.

Was das ist, ist mir jetzt nochmal wieder bewusst geworden bei Lektüre des BioBricks-Artikels. Es fehlt uns der rechte Umgang mit Abhängigkeiten.

Ich stelle mal ein paar ganz unterschiedliche erfolgreiche Bauteile nebeneinander:

image imageimage 

image image

Warum sind Bauteile von Lego über Moto bis Steuerelement so erfolgreich? Warum funktioniert Wiederverwendung hier?

Ich glaube, erfolgskritisch ist die Entkopplung von ihrer Umwelt. Klingt nicht neu, dass zur Modularisierung irgendwie lose Kopplung gehört. Aber es hilft ja nichts: Wenn wir wider diesen Wissens es nicht tun, dann kommen wir nicht dort an, wo wir hin wollen.

All den erfolgreichen Bauteilen ist gemeinsam, dass sie keine internen funktionalen Abhängigkeiten haben. Was meine ich mit internen funktionalen Abhängigkeiten?

Ich unterscheide zwischen Abhängigkeiten. Natürlich ist die Funktionalität eines Motors davon abhängig, dass er mit Treibstoff versorgt wird. Ein UI Control muss mit Daten gefüttert werden. In einen Transistor muss Strom fließen.

Das sind für mich aber externe oder gar triviale Abhängigkeiten. Ich möchte sie mit dem Begriff Input belegen. Ohne Input erfüllt ein Baustein seine Zweck nicht. Doch dieser Input kommt von außen. Er fließt sozusagen in den Baustein hinein. An der Tonerkartusche wird von außen ein Zahnrad bewegt, ein Legostein wird auf eine Platte gesetzt, ein Treeview Control wird von außen mit Daten befüllt.

Solchem Input, d.h. äußerer Abhängigkeit, stelle ich interne Abhängigkeit gegenüber. Intern ist eine Abhängigkeit, wenn ein Baustein beim Einbau intern verändert werden muss. Dann muss er nämlich seine Umgebung kennen.

Ein unabhängiger, entkoppelter, echter und erfolgreicher Baustein sieht so aus:

image

Alle oben angeführten Bausteine entsprechen diesem Bild. Man steckt sie mit anderen zusammen zu einem größeren Ganzen – das dann wieder ein Baustein auf einer höheren Abstraktionsebene sein kann. Ein Motor als Baustein eines Autos besteht z.B. aus Zündkerzen, Zylindern, Rohren, Kolben usw. die alle für sich wiederum Bausteine in Bezug auf den Motor sind.

Dieses Zusammenstecken geschieht entweder direkt wie beim Motor oder indirekt über ein Medium wie eine Leiterplatte:

image

Indirekter Zusammenbau ist flexibler. Die Bauteile müssen nicht so gut aufeinander abgestimmt sein wie beim direkten Zusammenbau, wo wirklich eines in das andere passen muss.

Was aber tun wir, wenn wir unsere “Bausteine”, die Klassen und Assemblies unserer Software entwerfen? Wir koppeln sie ganz fundamental eng, weil wir sie intern (!) voneinander abhängig machen. Da machen auch Interfaces und Adapter keinen Unterschied. Das sieht dann so aus:

image

Unsere Bausteine sind allermeistens keine Black Boxes, sondern höchstens Grey Boxes. Denn wo wir etwas über die Interna wissen, da herrscht zumindest etwas Durchsicht durch die Bausteinwand. Und nichs anderes als Interna sind es, wenn wir wissen, dass ein Baustein zur Adressdublettenprüfung von einem Persistenzbaustein abhängig ist:

image

Früher war diese Abhängigkeit statisch und dynamisch in der Klasse, heute ist sie nur noch statisch, weil die konkrete Implementation des Persistenzbausteins dynamisch zur Laufzeit per Dependency Injection (DI) in die Dublettenprüfung “gespritzt” wird. Ein Baustein A ist nicht mehr von einem konkreten Baustein B abhängig, sondern nur noch von Bausteinen, die irgendwie wie B aussehen und funktionieren.

class EinfacheDublettenprüfung : IDublettenprüfung
{
    private IPersistenz db;

    public Einfache Dublettenprüfung(IPersistenz db) { this.db = db; }

    …
}

Das ist ein netter Fortschritt, der uns schonmal erlaubt, die Dublettenprüfung isoliert zu bauen und zu testen. Eine Komponentenwerkbank ist damit möglich, auf der ich immer nur an einem Baustein zur Zeit konzentriert arbeite.

Je länger ich darüber jedoch nachdenken, desto mehr komme ich zu der Überzeugung, dass das nur eine Symtomkur ist oder ein Übergangsstadium. Wir sollten bei DI nicht stehenbleiben. Früher waren statische Abhängigkeiten, jetzt sind noch dynamische Abhängigkeiten. Morgen sollten es keine (internen) Abhängigkeiten mehr sein.

image Ja, das meine ich so. Wenn ich die erfolgreichen Bausteine anschaue, dann lehren sie uns, dass sie nur praktikabel sind, weil sie eben keine internen Abhängigkeiten haben. Wir stecken nichts in sie hinein. Es gibt keine Dependency Injection in eine Tonerkartusche hinein. Alle Abhängigkeiten sind an der Oberfläche und heißen Input. Ihnen stehen gegenüber andere “Oberflächlichkeiten”, nämlich der Output – der wiederum Input für andere Bauteile ist.

Solange wir unsere Softwarebausteine noch so entwerfen, dass wir sie (dynamisch) immer wieder anpassen müssen, solange haben wir ein Kompositionsproblem. Denn solange ist der Zusammenbau, die Komposition von etwas größerem immer schwierig. Solange gibt es nämlich kein Halten was die Kompliziertheit der “Steckverbindungen” angeht. Eine interne Abhängigkeit ist – so möchte ich mal sagen – per se eine enge Kopplung.

Sobald wir aber dahinkommen, unsere Bausteine nur über Input- und Output-“Pins” zu verbinden, werden wir erleben, dass die Entkopplung steigt und die Wiederverwendbarkeit. Dann werden wir nämlich anfangen, wirklich hierarchische System mit Bausteinen auf unterschiedlichen Ebenen zu bauen, die insbesondere auf niederer Abstraktionsebene besser wiederverwendbar sind.

Und dass sie das auf höherer Ebene nicht sind, macht dann nichts. Das liegt erstens in der Natur der Sache – ein Motherboard kann in weniger Zusammenhängen gebraucht werden als ein Kondensator, der darauf steckt – und zweitens können wir auf einer neuen “Leiterplatte” viel einfacher eine neue Baugruppe zusammenstecken.

Für mich ich also eine fundamentale Separation of Concerns (SoC) überfällig, die noch klarer als bisher mit DI Containern zwischen Baustein und Leiterplatte unterscheidet. Voraussetzung dafür: die Bausteine werden intern unabhängig.

Was könnte das für die Dublettenprüfung bedeuten? Sie wüsste nichts mehr von ihrer konkreten Umgebung. Sie würde von keinem Persistenzbaustein mehr abhängen:

image

Stattdessen hätte sie weitere oder anders gestaltete Inputs und Outputs, die dann mit beliebigen Inputquellen und Outputsenken verbinden ließen. Die Dublettenprüfung hätte damit die volle Hoheit über ihre “Oberfläche”. Sie wäre von keiner weiteren Schnittstelle abhängig.

image

Die Veränderung des Programmier- bzw. Denkmodells gegenüber dem heutigen mit seinen internen Abhängigkeiten wird noch deutlicher, wenn ich das obigen Zusammenspiel zwischen Dublettenprüfung und Persistenz “auseinanderziehe”:

image

Das bedeutet vielleicht etwas mehr “Leiterplattenaufwand”, aber das finde ich nicht schlimm. Der Gewinn an Entkopplung und Wiederverwendbarkeit und Klarheit (SoC) wiegt ihn mehr als auf.

Allerdings: dafür ist ein Umdenken nötig. Das will geübt sein. Ich fange grad auch erst damit an. Aber ich bin guter Dinge, dass in dieser Richtung Erfolg zu finden ist. Mehr Evolvierbarkeit tut Not. Denn wenn die biologischen System evolvierbar sind und jetzt auch noch mit echten Bausteinen “programmiert” werden können, dann sollte das doch auch ein Rezept für die Softwareentwicklung sein.

image