Follow my new blog

Dienstag, 29. Juni 2010

Zeig mir deinen Code und ich nenn dir deinen Dojo-Stil

image Die Coding-Dojo-Welt hat sich geteilt. Es gibt nun den münchener Latifa-Stil von Ilker. Und es gibt den Schwarm-Stil von Stefan Lieser und mir. Bei der Teilung hat es ein wenig gerumpelt in der Community – aber nun sollten wir in friedlicher Koexistenz coden können. Die Welt ist halt bunt.

Den einen wahren, kanonischen Coding-Dojo-Stil gibt es nicht. Es gab und gibt nur Interpretationen dessen, was Leute wie z.B. Robert C. Martin mal gezeigt haben. Einzig in der Praktik des TDD sind sich die Dojo einig – und natürlich darin, dass Code geschrieben werden soll.

Über den Prozess, wie zu diesem Code im Dojo gekommen werden sollte, ist schon geschrieben worden. Das ist es, worin sich Latifa- und Schwarm-Stil unterscheiden. Und das macht natürlich einen Unterschied im Lernen der Teilnehmer.

Aber welchen Einfluss hat das auf das Produkt eines Dojos? Merkt man es dem Code an, ob er nach diesem oder jenem Stil entwickelt wurde? Ich habe mal versucht, das herauszufinden.

Für die Kata FizzBuzz habe ich versucht, mich in den Latifa-Stil hinein zu versetzen. Ich hoffe, das Ergebnis ist nicht zu sehr gefärbt von irgendwelchen Missverständnissen oder Voreingenommenheiten.

Anschließend habe ich den Code nach Schwarm-Stil Manier nochmal entwickelt.

FizzBuzz à la Latifa

Gerade nach der Erfahrung des letzten Coding Dojo zum dotnetpro.powerday bin ich mir sicher, dass ein Latifa-Dojo die grundsätzliche Codestruktur so aufgesetzt hätte:

image

Implementation und Test sind in einem Projekt zusammengefasst. Begründung: Mehr braucht es nicht. Das Beispiel ist so klein, dass alles andere Overkill wäre.

Von der Implementation nehme ich an, dass sie im Wesentlichen so aussehen würde:

public class FizzBuzzer
{
    public List<string> Generate()
    {
        var numbers = new List<string>();
        for (int i = 1; i <= 100; i++)
        {
            if (IsFizz(i) && IsBuzz(i))
                numbers.Add("fizzbuzz");
            else if (IsFizz(i))
                numbers.Add("fizz");
            else if (IsBuzz(i))
                numbers.Add("buzz");
            else
                numbers.Add(i.ToString());
        }
        return numbers;
    }

    private bool IsBuzz(int i)
    {
        return i % 5 == 0;
    }

    private bool IsFizz(int i)
    {
        return i % 3 == 0;
    }
}

Die Aufgabe ist klein. Hexenwerk ist nicht nötig. Alles geradlinig implementiert. Die Funktionen IsFizz() und IsBuzz() sind bei einer Refaktorisierung herausgezogen worden.

Exemplarisch hier auch noch ein Ausschnitt aus den Tests:

[TestFixture]
public class Test_FizzBuzzer
{
    [Test]
    public void Check_numbers()
    {
        var sut = new FizzBuzzer();

        var numbers = sut.Generate();

        Assert.AreEqual("1", numbers[0]);
        Assert.AreEqual("91", numbers[90]);
    }

    [Test]
    public void Check_fizz()
    {
        var sut = new FizzBuzzer();

        var numbers = sut.Generate();

        Assert.AreEqual("fizz", numbers[2]);
        Assert.AreEqual("fizz", numbers[98]);
    }
   …

Sie setzen alle beim FizzBuzz-API an – öffentliche Methode Generate() der FizzBuzzer-Klasse. Alle Tests sind Black Box Tests.

So, wie ich bisher die Latifa-Dojos erfahren habe, glaube ich nicht, dass weitere Refaktorisierungen vorgenommen worden wären. Ich glaube auch nicht, dass andere Datenstrukturen gewählt worden wären.

Und warum auch? Läuft doch.

FizzBuzz à la Schwarm

Nach solcher (visionierter) Vorlage eines Latifa-Dojos stellt sich die Frage, inwiefern eine Lösung überhaupt anders aussehen kann. Das Problem ist ja trivial. Kann es da überhaupt zwei Meinungen geben?

Ja, es gibt Alternativen. Der Schwarm-Stil unterscheidet sich also nicht nur im Prozess vom Latifa-Stil, sondern auch in seinem Anspruch an die Lösungsformulierung. Die beginnt bei der Codeorganisation:

image

Zwei Projekte, statt einem. Denn beim Schwarm legen wir Wert nicht nur auf TDD und Funktionalität, sondern auch auf Klarheit in der Form. Verständlichkeit ist uns wichtig. Separation of Concerns (SoC) gilt nicht nur innerhalb von Code, sondern auch für seine Form. Deshalb sind für uns Tests und Implementation immer getrennt. Der Schwarm bemüht sich, möglichst viele Clean Code Developer Bausteine zu berücksichtigen.

An der groben Codeorganisation ist aber noch mehr als eine SoC abzulesen. Das Testprojekt besteht aus drei Klassen. Die spiegeln wider, dass vor dem Coden über den Lösungsansatz nachgedacht wurde. Es hat eine kurze Kreativphase gegeben, in der erkannt wurde, dass die Lösung ein Prozess ist, der aus zwei Phasen besteht:

  1. Zahlengenerierung
  2. Zahlenübersetzung (Mapping).

Um diese Erkenntnis im Code festzuhalten, habe von vornherein für beide Phasen Methoden im Code vorgesehen. Und deshalb konnte ich mich auch entscheiden, welche ich davon zuerst nach TDD implementiere. Ich habe mich für die Zahlenübersetzung entschieden. Sie ist ja auch der Kern des FizzBuzz-Problems.

public class FizzBuzzer
{
    internal static string MapNumber(int number)
    {
        if (IsFizz(number) && IsBuzz(number)) return "fizzbuzz";
        if (IsFizz(number)) return "fizz";
        if (IsBuzz(number)) return "buzz";

        return number.ToString();
    }

    private static bool IsBuzz(int number)
    {
        return number % 5 == 0;
    }

    private static bool IsFizz(int number)
    {
        return number % 3 == 0;
    }

Natürlich habe ich die Tests jeweils vorher geschrieben:

[TestFixture]
public class Test_MapNumber
{
    [Test]
    public void Map_straight_number()
    {
        Assert.AreEqual("1", FizzBuzzer.MapNumber(1));
        Assert.AreEqual("2", FizzBuzzer.MapNumber(2));
        Assert.AreEqual("4", FizzBuzzer.MapNumber(4));
        Assert.AreEqual("98", FizzBuzzer.MapNumber(98));
    }

    [Test]
    public void Map_fizz_number()
    {
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(3));
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(6));
        Assert.AreEqual("fizz", FizzBuzzer.MapNumber(99));
    }

Von den Testfällen her unterscheiden die sich eigentlich nicht vom Latifa-Stil. Aber betonenswert anders ist, dass ich sofort mit dem Kern des Problems anfangen konnte, weil ich wusste, dass es dafür eine Funktionseinheit gibt. Und die Tests sind fokussierter, weil sie nur die Übersetzung testen und nicht immer auch noch die Zahlengenerierung berücksichtigen müssen. Schließlich ist der Code in der MapNumber()-Methode auch noch simpler als der Übersetzungscode beim Latifa-Stil, weil er weniger tief schachtelt (keine else-Zweige).

image Hätte der Latifa-Stil mit konsequentem TDD auch dahin kommen können? Klar. Aber die Latifa-Dojo-Erfahrung zeigt, dass das nicht passiert. Da es im Latifa-Stil kein “offizielles” Nachdenken über den Lösungsansatz gibt, gibt es auch keine Gewissheit, dass das demokratische Voranschreiten so tief ins Refactoring einsteigt, wo es doch viel spannender ist, überhaupt eine lauffähige Lösung zu produzieren.

Nach Implementation des Mappings habe ich die Zahlenerzeugung implementiert. Die ist natürlich trivial. Dem Schwarm-Stil ist das jedoch egal. Wenn in der Kreativphase ein Lösungsansatz erarbeitet wurde, der die Zahlengenerierung ausweist, dann wird sie in einer expliziten Funktionseinheit implementiert. Das entspricht dem Single Responsibility Principle (SRP).

internal static IEnumerable<int> NumberGenerator(int from, int to)
{
    for (var i = from; i <= to; i++)
        yield return i;
}

Die Zahlenerzeugung ist kein Bestandteil des API. Deshalb erfolgt sie in einer internen Methode.

Beim Testen ist zu sehen, dass auch der Schwarm-Stil pragmatisch vorgeht. Ich habe nur einen Test geschrieben:

[TestFixture]
public class Test_NumberGenerator
{
    [Test]
    public void Numbers_1_to_3()
    {
        var numbers = FizzBuzzer.NumberGenerator(1, 3);
        Assert.AreEqual(new[]{1,2,3}, numbers.ToArray());
    }
}

Im Sinne eines universellen Zahlengenerators ist das natürlich zuwenig. Wie würde der z.B. auf Zahlengrenzen < 0 reagieren? Wie würde er reagieren, wenn die erste Zahl größer als die zweite ist? Eine Beschränkung auf den problemrelevanten Happy Day Fall ist auch für den Schwarm-Stil ok.

Die Schritte des Lösungsansatzes müssen am Ende natürlich noch zusammengezogen werden. Das ist die Aufgabe der API-Methode:

public IEnumerable<string> Generate()
{
    return NumberGenerator(1, 100).Select(MapNumber);
}

Im Vergleich zur Latifa-Lösung folgt sie sofort dem Single Level of Abstraction (SLA) Prinzip. Die Lösung wird auf hohem, einheitlichem Abstraktionsniveau beschrieben. Und die Phasenfolge ist klar zu erkennen. Schwer zu verstehende Schachtelung ist nicht nötig.

Da die Einzelfunktionalitäten schon getestet sind, ist für die Integration nur noch ein Integrationstest nötig:

[TestFixture]
public class Integrationtest
{
    [Test]
    public void Check_number_generation_and_mapping()
    {
        var sut = new FizzBuzzer();
        Assert.AreEqual(
            new[] { "1", "2", "fizz" },
            sut.Generate().ToArray().Take(15));
    }
}

Der Test kann so kurz ausfallen, weil lediglich geprüft werden muss, ob die Funktionseinheiten überhaupt korrekt zusammenspielen.

Fazit

Wenn ich die typischen Vorgehensweisen von Latifa- und Schwarm-Stil hier halbwegs wirklichkeitstreu wiedergegeben habe, dann führen sie zu unterschiedlichem Code. Ob das eine Ergebnis besser oder schlechter ist, will ich nicht beurteilen. Mir war nur die Beantwortung der Frage wichtig, ob es überhaupt einen Unterschied gibt. Und wenn ja, was der ausdrückt.

Unterm Strich scheint mir hier Conway´s Law bestätigt: Der Code spiegelt Prozess und Organisation des Teams wider. Latifa setzt auf Demokratie, d.h. Gleichberechtigung und Gleichstellung, und hat darüber hinaus keinen Prozess jenseits TDD.

image Schwarm setzt auf einen klaren Prozess (Kreativphase gefolgt von Umsetzungsphase) und die Einhaltung von Prinzipien jenseits von TDD bei der Umsetzung.

Nun mag jeder (potenzielle) Teilnehmer an einem Dojo entscheiden, welchen Stil er/sie bevorzugt.

Samstag, 26. Juni 2010

Kollektiv intelligent codieren

Wenn ein einzelner Entwickler etwas codiert, dann hat das Ergebnis eine gewisse Qualität. Und wenn mehrere Entwickler zusammen etwas codieren, dann sollte das eine höhere Qualität haben. Oder?

Wie die sprießenden Coding Dojos zeigen, ist das nicht der Fall. Da mag der Spaß noch so groß sein, da mag die Motivation hoch fliegen – die Ergebnisse sind meist schlechter, als wenn ein einzelner Entwickler dasselbe probiert. Wenn nach 2 Stunden zwei 5zeilige Tests stehen und noch vielleicht 10 Zeilen Domänenlogik, ohne dass abzusehen wäre, wann die Aufgabe ganz gelöst sein wird, dann ist das ziemlich wenig.

Wie kann das aber sein, was geht schief beim Coding Dojo? Warum bleibt das Kollektiv der Dojo-Teilnehmer hinter Einzelleistungen verlässlich zurück? Oder sehe ich das ganz falsch und nichts geht schief? Ist das einzig schiefe der Sitz meiner Brille, durch die ich das Coding Dojo sehe?

Die anhaltende Diskussion (s. z.B. hier oder hier oder hier) über Sinn und Zweck von Coding Dojos lässt mich nun vermuten, dass die Diskutanten (mich eingeschlossen) aneinander vorbei reden.

Also setze ich heute mal meine übliche Brille erstmal ab und stecke mir eine Lupe aufs Auge.

Struktur von Dojo-Problemlösungen

Worum geht´s denn beim Dojo überhaupt? Es wird ein Szenario präsentiert, zu dem die Teilnehmer zusammen eine Lösung in Form von Code entwickeln sollen. Ob der Code durch einen Stellvertreter (Code Monkey) eingegeben wird, den die Gruppe “fernsteuert” oder ob Gruppenmitglieder reium den Code eingeben, ist an dieser Stelle nicht so wichtig. Ebenfalls noch nicht so wichtig ist, dass Dojos einen bestimmten Weg der Implementation favorisieren: das TDD.

Entscheidend ist die Art der Aufgabe: Übersetzung von Anforderungen in Code.

Diese Art der Aufgabe hat es nämlich in sich. Sie besteht aus zwei Phasen, die auch mehrfach durchlaufen werden können:

  • Phase 1: Kreativphase
  • Phase 2: Umsetzungsphase

Zwischen Anforderungen und “Produkt” steht mithin eine Barriere. Das ist der Entwurf des Produktes. Wie ein Produkt aussieht, ergibt sich nämlich nicht einfach so. Bevor man ein Produkt bauen kann, muss man eine Vorstellung davon haben, wie es aussehen soll.

Ein Bild vom Produkt, von der Lösung zu entwickeln, das ist nun ein kreativer Prozess. Aus den scheinbar oder tatsächlich möglichen Lösungen ist eine auszuwählen.

Ist die Auswahl getroffen, kann sie umgesetzt werden. Umsetzung erfordert also Klarheit in der Vorstellung, was umgesetzt werden soll.

Lösungsfindung und Umsetzung sind ganz verschiedene Tätigkeiten. Sie sind so verschieden, dass die Psychologie dafür zwei Arten des Denkens unterscheidet: divergentes Denken (oder auch laterales Denken) und konvergentes Denken.

image

Divergentes Denken ist in der Kreativphase nötig, konvergentes Denken in der Umsetzungsphase. Die Umsetzung ist also kein “no brainer”, sondern erfordert auch Denken. Denn selbst wenn die Lösungsstruktur grundsätzlich klar ist, bedarf die Umsetzung durchaus noch weiterer Entscheidungen. Umsetzung kann weit entfernt von “Malen nach Zahlen” sein. Umsetzung braucht Erfahrung und Kompetenz.

image

Sind Erfahrung und Kompetenz nicht aber auch für die Lösungsfindung (Kreativphase) wichtig? Interessanterweise sind Erfahrung und Kompetenz, also Expertentum, überbewertet, wenn es um kreative Lösungen geht. Lösungssuchende Gruppen sollten sich daher um Heterogenität bemühen; sonst schmoren Experten zu sehr in ihrem eigenen “Denksaft”. Der Horizont von homogenen Gruppen ist schlicht enger als der von heterogenen.

Fazit bis hierher: Wenn ein Coding Dojo daran interessiert ist, Anforderungen mit Code umzusetzen, dann ist es gut beraten, die zwei grundsätzlichen Phasen auf dem Weg dahin zu beachten. Ein Dojo muss sich also überlegen, wie es divergentes und konvergentes Denken herstellt.

Divergentes Denken in der Gruppe

Kollektive können gut divergent denken. Kollektive Intelligenz kann bei Problemen, die divergentes Denken erfordern, individueller Intelligenz relativ leicht überlegen sein. Ein Coding Dojo ist also gut aufgestellt, schöne Lösungsansätze für die gegebenen Anforderungen zu finden, da die Teilnehmergruppe gewöhnlich sehr heterogen ist.

Allerdings braucht es dafür eine Voraussetzung: das Dojo muss überhaupt eine Kreativphase durchlaufen. Nach Verständnis der Anforderungen muss es eine Phase geben, in der die kollektive Intelligenz divergent denkt. Ob diese Phase 3 Minuten oder 30 Minuten dauert, sei dahin gestellt. Das hängt ja auch vom Problem ab. Aber ohne eine solche Phase, d.h. einen Zeitraum im “Divergenzdenkmodus”, gibt es keinen Lösungsansatz.

Und wie sieht eine solche Kreativitätsphase aus? Was tun die Gruppenmitglieder da? Meine Vorstellung davon sieht so aus:

  • image Am Anfang der Kreativphase steht eine Stillarbeitsperiode. Jeder überlegt für sich, wie er/sie die Lösung gestalten würde. Warum Stillarbeit, wenn doch alle in der Gruppe sitzen? Weil sonst bei der folgenden Diskussion leicht die “Lauten”, die “Redseligen”, die “Schnelldenker” sich durchsetzen. Wird ohne einen Moment der stillen Besinnung gleich das kreative Getümmel eröffnet, haben “Leise” und “Langsame” wenig Chance, ihre womöglich besseren Ansätze zu Gehör zu bringen. Ohne Stillarbeit reduziert das Kollektiv also sein Potenzial – und nicht unbedingt auf die Kompetentesten. Hierzu mehr in “Group Genius”.
  • Auf die Stillarbeit folgt die Diskussion der verschiedenen Lösungsansätze. Die Gruppe trägt zusammen, was die Heterogenität ausgebrütet hat. Dabei sollten am besten alle (!) Teilnehmer gehört werden. Auch das wieder eine Maßnahme, um kompetente Zurückhaltende nicht zu verlieren.
  • Die Sammlung der Lösungsansätze erfolgt am besten am Flipchart/Whiteboard. Visual Studio oder eine Diagrammsoftware haben hier keinen Platz. Sie sind schlicht zu beschränkend. Die Kreativitätsphase braucht alle Freiheit und Flexibilität, die sie bekommen kann. Mit einem Stift in der Hand auf einer großzügigen Fläche ist sie am größten.
  • image Lösungsansätze sind keine Programme. In der Kreativphase wird nicht codiert. Es geht um ein Denken auf hohem Abstraktionsniveau. Es geht ums Modellieren. Stefan Lieser hat dafür ein Beispiel in seinem Blogartikel gebracht: eine Sequenz von Kullern (Funktionseinheiten mit klarer Verantwortlichkeit) war sein Modell für die Kata BankOCR. In seiner Lösungsskizze ist keine Klasse zu sehen. Und er hat sich auch nicht durch VS oder TDD oder C# behindern lassen. Wo ein konkreter C# Typname auftaucht, ist der auch nicht ganz scharf als Programmiersprachenkonstrukt gemeint. Er ist für Stefan eher nur ein Platzhalter oder ein Ausrutscher in eine andere Sprache. Ein Idiom, dass knackig eine Intention ausdrückt. Mehr nicht.
  • Damit die Kreativphase auf eine Lösung hin konvergiert, muss am Ende irgendwie gefiltert werden. Entscheidungen sind zu treffen. Wie Stefan bin ich der Meinung, dass dafür ein Konsens (Abstimmung) der denkbare falsche Ansatz ist. Im Konsens steckt nämlich kein Bezug zur Sache. Warum jemand die Hand bei einer Abstimmung hebt oder nicht, ist nicht erkennbar. Vielleicht ist sie von einer Lösung überzeugt, vielleicht genervt (und will deshalb schnell einen Mehrheitsbeschluss, damit es weitergeht), vielleicht unentschieden, aber von einem anderen Teilnehmer persönlich beeindruckt. Eine Abstimmungsmehrheit sagt nur aus, dass für einen Lösungsansatz aus irgendwelchen Gründen mehr Leute die Hand gehoben haben als für einen anderen. Das war´s. Alles andere ist eine hübsche Illusion. Konsens/Demokratie gehört in den Bereich der Politik und nicht in den der Wissenschaft oder des Ingenieurwesens.
    Aber was dann? Statt für Konsens plädiere auch ich für Konsent. Der Begriff kommt aus der Soziokratie und ist hier und hier und hier näher beschrieben. Bei Konsententscheidungen geht es nicht darum, eine Mehrheit für einen Vorschlag zu finden. Wer dafür ist, ist quasi egal. Stattdessen sucht der Konsent nach Widerstand. Er wird daher auch die “Herrschaft des Arguments” genannt. Damit kommt die Sache ins Entscheidungsbild. Widerstände müssen sich nämlich auf den Vorschlag und die Sache bzw. ihren Kontext beziehen. Müdigkeit oder persönliche Beeindruckung haben da keinen Platz. Wer Widerstand leistet, d.h. einen begründeten Einwand hat, der muss auch persönlich hervortreten und sich erklären; damit auch hier keine Stimme untergeht, wird im Konsentprozess ebenfalls jedes Gruppenmitglied befragt. Das fokussiert die Diskussion ungemein. Konsententscheidungen sind daher effizient und effektiv. Das kann man von Abstimmungen nicht so einfach behaupten. Denn Mehrheit bedeutet nicht Effektivität (das Richtige entscheiden) und Mehrheit bedeutet auch nicht Effizienz (schnell entscheiden). Wie wir aus der Politik wissen, kann es bis zu einem Konsens lange dauern. Mehr zum Thema z.B. in meinem Vortrag “Agile Entscheidungen” auf dem Mathema Herbstcampus.

Zweck der Kreativphase ist es, den umzusetzenden Lösungsansatz zu finden. Dabei stolpert die Gruppe selbstverständlich über Lücken oder Divergenzen im Verständnis der Anforderungen. Die Kreativphase überlappt daher mit der vorgelagerten Phase der Anforderungserfassung. Die habe ich oben jedoch nicht aufgeführt, weil sie nicht direkt mit der Softwareentwicklung zu tun hat. Softwareentwickler erheben keine Anforderungen, sondern müssen sie “nur” verstehen. Wer mag, darf aber gern Phase 0: Anforderungserfassungsphase vor die Kreativphase setzen.

imageNicht nur durch die Diskussion und weiterentwicklung verschiedener Lösungsansätze, sondern auch den “Rücksprung” in die Anforderungserfassung ist die Kreativphase also kein linearer Prozess. Sie ist “non routine work” und damit auch außerhalb der Reichweite von üblicher motivations-/produktivitätsfördernden Maßnahmen. Zuckerbrot oder Peitsche wirken nicht, um die Kreativitätsphase zu befördern. Im Gegenteil. Soviel als Anmerkung zur Arbeit in Projekten, die ja noch ausgeprägtere Kreativitätsphasen brauchen. Wer das nicht glaubt, der siehe hier.

Fazit: Lösungsansätze in der Softwareentwicklung entstehen nur in einem kreativen Prozess. Heterogene Kollektive können in so etwas gut sein. Allerdings braucht es dafür Raum und Struktur. Drauflosreden und Abstimmungen sind nicht genug. Gerade divergentes Denken muss also moderiert werden. Das Kollektiv soll sich ja auf den Inhalt konzentrieren, nicht auf die Form. Auch hier gilt wieder das Single Responsibility Principle.

Konvergentes Denken für die Umsetzung

Die Umsetzung eines Lösungsansatzes ist etwas anderes als den Lösungsansatz zu finden. Die Umsetzung braucht kein divergentes Denken, kein Querdenken, keine Vielfalt – sondern System. Bei der Umsetzung ist konvergentes Denken gefragt. Das greift auf einen Fundus an Erfahrungen und Techniken zurück. Es übersetzt den Lösungsansatz als Modell in die konkrete Welt der Implementationstechnologien.

Konvergentes Denken ist nun allerdings in der Gruppe schwierig. Tut mir leid, das berichten zu müssen. Aber so ist es. So sagt es die Psychologie. Wo beim divergenten Denken eine heterogene Gruppe hilft, da hilft beim konvergenten Denken eine homogene Gruppe, also eine Expertenrunde.

Wenn ein Coding Dojo nun aber eine heterogene Gruppe darstellt und vor allem eben keine Experten zum Thema TDD zusammenkommen (sondern Entwickler, die es lernen wollen), dann kann ein Coding Dojo per definitionem für die Umsetzung keine guten Voraussetzungen bieten.

In diesem Licht erscheint es doppelt kontraproduktiv, die Kreativphase wie im letzten Coding Dojo anlässlich des dotnetpro.powerday zu überspringen. Nicht nur wurde kein Lösungsansatz erarbeitet, nein, es wurde die Gruppe sogar sofort in die Phase gestoßen, für die sie am schlechtesten aufgestellt ist. Weder existierte also eine gemeinsame Vorstellung von der Lösung, noch eine gemeinsame Vorstellung von der Umsetzung.

Vielleicht ist Ilker da zurecht stolz auf 2 grüne Tests und ca. 10 Zeilen Domänenlogik für die Kata BankOCR gewesen. Denn bei so schlechten Voraussetzungen ist beides vielleicht eine Leistung.

Frustrierend empfinde ich das Ergebnis dennoch. Denn die Gruppe ist um größere Erfolge betrogen worden, weil man nicht “artgerecht” mit ihr umgegangen ist.

Wie hätte es denn aber besser aussehen können? Meine Vorstellung von einer “artgerechten” kollektiven Umsetzungsphase sieht so:

  • Das Coding Dojo definiert explizit, worauf bei der Umsetzung geachtet werden soll. Das kann einmalig geschehen (“Bei allen unseren Dojo achten wir bei der Umsetzung auf ABC und XYZ.”) oder für jedes Dojo immer weder neu (“Heute wollen wir uns auf XYZ bei der Umsetzung konzentrieren.”).
    Wichtig ist in jedem Fall die ausdrückliche Formulierung eines Umsetzungsrahmens. Denn nur so kann das Dojo bei der Umsetzung effizient gehalten werden. Kommentare, die sich auf Verhaltensweisen beziehen, die nicht im Fokus der Umsetzung liegen, müssen nicht verfolgt werden. Das entspricht wieder dem Konsentprinzip.
  • Während der Umsetzung übernehmen einer oder mehrere Experten in Bezug auf die Umsetzungsgrundsätze die Moderation. Sie setzen selbstverständlich nicht allein um; die Gruppe soll so gut es geht auch die Umsetzung vorantreiben. Aber die Experten leiten die Gruppe an. Sie halten die Gruppe im Rahmen der Umsetzungsgrundsätze, geben Hilfestellungen, regen an, erklären.
    Experte kann jeder sein, der sich in Bezug auf Umsetzungsgrundsätze berufen fühlt. Dazu muss niemand eingeflogen werden. Auch der sprichwörtliche Einäugige kann die Blinden anleiten. D.h. wenn sich auch nur einer aus der Gruppe etwas mehr mit einem Umsetzungsgrundsatz auseinandergesetzt und eine Meinung dazu entwickelt hat, kann er die Moderation über nehmen – und sollte es auch.
  • Nach der Umsetzung folgt selbstverständlich eine Reflexion. Wer mag, kann sie als Phase 3 ansehen. Darauf möchte ich hier aber nicht näher eingehen.

Warum braucht das Kollektiv bei der Umsetzung eine Moderation in Bezug auf die Umsetzungsgrundsätze? Weil die Gruppe zusammengekommen ist, um eben diese Grundsätze zu lernen. Wo aber keine/nur wenig Kompetenz ist, entwickelt die sich nicht spontan aus ungeleiteten, tappenden Versuchen einer heterogenen Gruppe.

Wenn man 10jährige in einem Raum sperrt mit einem Haufen elektrischer Bauteile und dem Auftrag, eine Lampenwechselschaltung zu bauen, ist kaum zu erwarten, dass da etwas herauskommt. Die Gruppe wird sich nicht das Wissen aneignen können. Zusammen mit einem Lehrer jedoch ist das möglich. Der baut die Schaltung nicht selbst, lenkt aber immer wieder die Aufmerksamkeit. Didaktik und Methodik müssen stimmen. Dann findet effizientes und effektives Lernen statt.

Bei einem Coding Dojo ist das nicht anders. Ohne Anleitung findet kein verlässliches Lernen in Bezug auf die ausgelobten Inhalte statt. Das sind meist die Umsetzungsgrundsätze (z.B. TDD, Architektur, OOP). Experten führen die Gruppe hin zur Kompetenz in Bezug auf die Umsetzungsgrundsätze. Sie greifen dabei so wenig wie möglich in den Gruppenprozess ein, aber soviel wie nötig, um den Fokus zu halten.

Irgendwas kann natürlich jeder auch ohne Anleitung lernen. Ich z.B. habe beim letzten Dojo gelernt, wie man rechteckige Blöcke in VS2010 markiert. So nett das aber auch ist, dafür gehe ich nicht ins Coding Dojo. Das kann ich auch bei einer Plauderei einer .NET User Group erfahren.

Fazit: Damit die Umsetzung eines Lösungsansatzes zügig und erfolgreich ist, braucht es Können. Wo das Können fehlt, muss es über Experten in die Gruppe eingebracht werden. Aber auch wo das Können schon in der Gruppe ist, braucht es Moderation für eine kohärente Arbeit der Gruppe.

Kollektive Intelligenz mit System

Ich schätze Ilker für seine hochmotivierte Art, Dojos durchzuführen. Und ich schließe mich seinen Latifa-Werten an.

Aber ich möchte bei ihnen nicht stehenbleiben. Sie sind für mich nur ein Rahmen, in dem für mich das eigentliche Lernen stattfindet. Und dafür ist meine Vision, dass es verlässlich stattfindet in Bezug auf klar kommunizierte Ansätze in der Umsetzungsphase oder auch der Kreativphase.

Darüber hinaus bin ich auch der Überzeugung, dass solches Lernen nur funktioniert, wenn es nicht eine gewisse Führung gibt. Im besten Fall versteht die mehr vom Lerninhalt als der Rest der Gruppe. Sollte das jedoch einmal nicht der Fall sein, ist noch nicht alles verloren. Allerdings muss die Moderation dann besonders sensibel sein.

Bei allem Willen zu Spaß beim Dojo und selbstverständlich grundsätzlicher Gleichberechtigung aller Teilnehmer glaube ich allerdings nicht, dass demokratische Anwandlungen oder “code first” Denke zielführend sind.

Mir scheint, dass die bisherigen Dojos die Kompliziertheit von Problemen unterschätzen und die Leichtigkeit, mit der sich Ansätze wie TDD lernen lassen, überschätzen. Sie glauben, ohne Kreativitsphase (oder gar Anforderungserfassungsphase) auszukommen. Sie glauben, ohne Expertenmoderation in der Umsetzungsphase auskommen zu können. Ihr Credo: “Durch den Code zur Wahrheit und zur Erkenntnis”

Dass es nicht so einfach ist, zeigen dann aus meiner Sicht die magerer Erfolge der Dojos. Wie gesagt: Irgendwas wird schon gelernt. Aber wird das gelernt, was sich die Dojo-Macher wünschen? Ich bezweifle es. (Wenn Sie sich überhaupt etwas wünschen jenseits des Barnum-Mottos “Hier ist für jeden was dabei.”)  Wird gelernt, was sich die Teilnehmer wünschen? Ich bezweifle es. (Wenn die überhaupt einen konkreten Lernwunsch haben und nicht bloß aus Neugierde kommen.)

Damit will ich nicht das Engagement bisheriger Dojo-Macher kritisieren. Im Gegenteil! Ich finde es sehr cool, dass wir hier in der .NET-Welt eine solche Szene entwickelt haben und eifrig diskutieren. Nochmal danke an Vorreiter Ilker!

Aber was am Anfang gut und ausreichend gewesen sein mag, ist es vielleicht jetzt nicht mehr. Der Geschmack kommt beim Essen. Und da es nun Katas und Dojos gibt, ist es – so scheint mir – an der Zeit, weiter zu gehen. Bisher war Dojo 1.0. Ich möchte jetzt Dojo 2.0. Ich möchte Coding Dojos nach oben skizziertem Schema, die noch mehr bieten als die bisherigen. (Oder vielleicht nicht mehr, sondern Anderes.)

image Und deshalb präge ich jetzt mal einen weiteren Dojo-Stil: den Schwarm-Stil. Ilkers Stil ist der Latifa-Stil, meiner der Schwarm-Stil. Warum Schwarm in der Stilbezeichnung? Das ist für mich eine Anspielung auf das Buch “Der Schwarm” von Frank Schätzing. Darin kommt eine Intelligenz vor, die sich aus “sichtbaren” Individuen zusammensetzt, eine kollektive Intelligenz, eine Schwarmintelligenz.

Mir ist klar, dass am Begriff Schwarmintelligenz eine Menge hängt, was nicht zum Coding Dojo passt. Aber egal ;-) Ich finde ihn knackig und es steckt auch das Kollektiv drin.

Jetzt stellt sich nur die Frage, wann und wo das erste Schwarm-Stil Coding Dojo stattfindet. Mal überlegen… Wenn es konkreter wird, berichte ich hier im Blog.

Dienstag, 8. Juni 2010

Zwischenmahlzeit: Kurzvorträge zum Mittag in Karlsruhe, freier Eintritt

Wer mag sich zwischendurch inspirieren lassen? Das ist nämlich am Donnerstag 10. Juni 2010 in Karlsruhe möglich. Leicht verdaubare Happen für den Entwicklergeist statt Suppenkoma bieten nämlich acht Abschlussvorträge eines Rhetoriktrainings.

Von 13:00 Uhr bis ca. 14:30/15:00 Uhr halten die Absolventen des Trainings 10-15 minütige Kurzvorträge in der Albert-Nestler-Str. 10 im Raum Paris des Technologiezentrums bei der andrena objects ag. Hier ein Auszug aus der Themenliste:

  • Umstieg von .NET 3.5 auf .NET 4.0
  • Deliberation und Gruppenentscheidungen
  • Scrum
  • eclipse BIRT
  • Einblick in die Sprache R

Der Eintritt ist natürlich frei. Die Teilnehmer, meine Trainer-Kollegin Renate Klein und ich freuen uns über jeden, der seine (verlängerte) Mittagspause bei ein wenig geistiger Anregung mit uns verbringt. Es gibt Spannendes zu hören. Und die Teilnehmer profitieren vom unvoreingenommenen Feedback eines größeren Publikums.

Also, wie wärs?

image

Freitag, 4. Juni 2010

Intensiv, im Team, kostenlos – Das Clean Code Developer Praktikum

image  Clean Code Developer (CCD) werden, hat weniger mit Technologien zu tun, als vielmehr mit Gewohnheiten und Konzepten. CCD-Tugenden zu vermitteln stellt daher besondere Anforderungen an Trainer. Wie stellen die dann sicher, dass sie sie erfüllen?

Stefan Lieser und ich haben in den letzten 14 Monaten einige CCD-Trainings durchgeführt und fühlen uns durchaus wohl mit den Ergebnissen. Aber wir hätten die CCD-Initiative ja nicht begründet und auch noch die Reflexion auf den Werte-Schild gehoben, wenn wir selbst nicht danach leben wollten und würden.

Deshalb finden wir es an der Zeit, über unsere Trainings zu reflektieren. Das Feedback aus den bisherigen Trainings haben wir analysiert; daraus konnten wir wertvolle Hinweise ableiten. Aber einige Aspekte lassen sich schwer mit Fragebögen erheben. Wir meinen, dass sich Konzepte, Vorgehensweisen und Didaktik nochmal ganz anders analysieren und reflektieren lassen, wenn sie ganz bewusst von Anfang an Thema sind, wenn sozusagen ein Training in einer “Meta-Atmosphäre” stattfindet.

Für ein solches Training, bei dem wir nicht nur etwas weitergeben, sondern selbst viel lernen wollen, können wir ja aber natürlich kein Geld verlangen. Es wäre auch kein normales Training, sondern eher… tja, was denn? Wir haben es mal Praktikum genannt.

Der Deal

Im Juli bieten Stefan Lieser und ich ein Praktikum an, in dem wir 5 Tage mit 5 “Praktikanten” 1 Projekt nach CCD-Manier durcharbeiten wollen.

Dieses CCD-Praktikum ist kostenlos für die Teilnehmer.

Wir bringen unsere CCD-Erfahrung ein. Die Teilnehmer sollen also etwas lernen. Aber wir wollen das – wie gesagt – auf einem anderen Niveau als in einem üblichen CCD-Training tun. Statt in kleinen Schritten Inhalte zu vermitteln, wollen wir in großen Sprüngen voran. Wir wollen mehr anwenden, als erklären. Denn nur dann kommen wir wirklich weiter bei einigen CCD-Bausteinen.

Die Teilnehmer müssen daher schon ein gewisses Erfahrungsniveau in Bezug auf CCD und .NET-Entwicklung haben. Deshalb erlauben wir uns, ein Auswahlverfahren vorzuschalten. Interessierte bewerben sich durch Lösung von zwei Aufgaben.

Keine Angst, das sind keine schlimmen Aufgaben. Wer es ernst meint mit CCD, wird sie leicht lösen können. Hier ein Beispiel für eine Einreichung zu Aufgabe 1 von Steven Weiß.

Also, wie wär´s? Wer hat Lust und macht mit? Es gibt was zu lernen, es gibt was zu schaffen – und es gibt die Möglichkeit, uns mal so richtig die Meinung zu sagen ;-)

Hier mehr Infos zur Termin und Ort sowie Hinweise zur Bewerbung auf der Seite des Professional Developer College.

Wie sind sehr gespannt auf eure Beiträge!

Donnerstag, 3. Juni 2010

Wenn das Ziel der Weg ist – Über typische Verwechslungen [OOP 2010]

Erfolgreiche Typen haben immer klare Ziele. Ohne Ziele geht nix. “10% mehr  Umsatz im nächsten Jahr!” oder “Maximal 1 Bug Report pro Monat im Support” oder “Feature X bis zum 30.6.2010 realisieren” – das sind Ziele für echte Männer (und gern auch Frauen, wenn sie sich angesprochen fühlen). Die sind auch total SMART – ist zumindest in Bezug auf A=Attainable und R=Relevant zu hoffen.

Soweit der Stand der Ziel-Dinge. Den habe ich auch schon mal versucht zu verinnerlichen. Fällt mir aber für mich selbst oft nicht ganz leicht; SMARTe Ziele allerdings für andere zu fordern, ist etwas ganz anderes ;-) Doch darum geht es mir nicht. Ich bin vielmehr über etwas Grundsätzliches gestolpert.

Solche oder ähnliche Ziele sind wohl nötig “im Business” und auch sonstwo. Ok. Dazu kommen dann aber noch aus den Zielen abgeleitete Rahmenbedingungen. “Um das Ziel ‘10% mehr Umsatz im nächsten Jahr’ zu erreichen, bekommen Sie das Budget X.”, “Um maximal 1 Bug pro Monat zu erreichen, erhalten Sie die Ressourcen Y” usw. Ziele sind quasi nicht von Grenzen zu trennen, in denen sie erreicht werden müssen. Sie sind immer an irgendwelche Budgets gekoppelt (Geld, Zeit, Menschen, Maschinen, Material usw.).

Das hört sich auch plausibel an. Die Rahmenbedingungen stehen für den maximalen Preis, den man zahlen will, um ein Ziel zu erreichen. Und da immer viele Ziele zu erreichen sind, die zur Verfügung stehenden Ressourcen jedoch meist begrenzt sind, müssen sie in zielindividuelle Budgets partitioniert werden. Oder?

So plausibel sich diese Argumentation anhört, mir scheint sie ganz grundsätzlich an einem Missverständnis zu leiden. Oder sogar an mehreren.

Missverständnis #1: Das Ziel ist der Weg

Da ist zum einen das Missverständnis, Ziele seien irgendwie unbeweglich. Klar, das gibt es. In sehr künstlichen Umgebungen wie Sportschießständen sind Ziele meist unbeweglich. Ich habe selbst mehr als 10 Jahre Leistungssport auf solchen Schießständen getrieben und weiß, wovon ich rede. Selbst solche starren Ziele sind schwer zu treffen. Im Sportschießen bringt man deshalb einiges Material in Anschlag (sogar im doppelten Sinn). Da gibt es Schuhe, Jacken, Hosen, Handschuhe, Mützen, Riehmen um die Bewegungsfreiheit des Schützens einzuschränken – auf das er das Ziel leichter erreiche, äh, treffe. Trotzdem meinen Glückwunsch dem, der in diesem Rahmen Höchstleistungen vollbringt.

Allerdings, da wollen wir ehrlich sein, realitätsnah ist das Sportschießen nicht. Es ist eine tolle Disziplin, in der man eine Menge lernen kann – nur nicht den Umgang mit der Waffe in realen Situationen. Die sind nämlich insofern entscheidend anders, als dass man sich erstens selten auf sie so vorbereiten kann und zweitens – das ist der Knackpunkt – allermeistens mit Zielen aufwarten, die nicht stillhalten.

In der realen Welt sind Ziele immer das, was sich bewegt. Militär, Polizei und “außersportliche” Waffenfreunde wissen das. Deren Schießstände sehen deshalb auch anders aus. Dort bewegen sich die Ziele.

Die Kunst, Ziele im realen Leben zu treffen, besteht also darin, nicht zu erstarren, sich nicht einzuschnüren, sondern so beweglich wie das Ziel zu bleiben – und es trotzdem zu treffen. Managerseminare sollten deshalb vielleicht mal Völkerball ins Programm nehmen – da bewegen sich nämlich anders als bei Handball oder Fußball die Ziele. Oder wie wäre es mit einer Runde Paintball? Die würde nicht nur Erkenntnise zur Natur realer Ziele, sondern auch zur persönlichen Kondition bringen ;-) Doch das nur am Rande…

Also: Ziele “in der Natur” sind beweglich. Deshalb müssen Jäger ebenfalls beweglich sein. Menschen sind den Tieren insofern überlegen, als dass sie nicht nur körperlich, sondern auch geistig beweglich sind.

Umso verwunderlicher ist es, dass diese Beweglichkeit üblicherweise massiv eingeschränkt wird, wenn es um Ziele in Unternehmen geht. Nicht nur sind die merkwürdig unnatürlich fix; nein, auch die Beweglichkeit der Mitarbeiter, die sie treffen, äh, erreichen sollen, ist eingeschränkt.

Das fühlt sich für mich so an, als sei eigentlich der Weg, d.h. die Bewegung in den Randbedingungen oder gar die Randbedingung selbst das eigentliche Ziel. “Wichtig ist, dass Ihr das Budget einhaltet!” Besonders deutlich wird das Missverständnis, wenn Budgets ausgeschöpft werden müssen. Müssen! Denn sonst droht ja, dass das Budget im nächsten Jahr kleiner wird. Unverbrauchtes Budget ins nächste Jahr (oder zum nächsten Projekt) zu retten, ist kaum möglich.

Philosophisch-spirituell ist es hübsch, vom Weg als Ziel zu sprechen. In wirtschaftlichen Zusammenhängen fühlt sich das jedoch komisch an. Weg und Wegbegrenzungen dürfen nicht das Ziel sein. Wo das passiert, wird der Unternehmenszweck pervertiert und die Motivation der Mitarbeiter mit Füßen getreten. (Ok, ausgenommen die der Mitarbeiter, die dafür verantwortlich sind darauf zu achten, dass der Weg als Ziel eingehalten wird.)

Also: Ich plädiere für einen natürlichen Umgang mit Zielen. Wenn schon Ziele, dann solche, die sich bewegen dürfen. Wenn sich im Verlauf eines Jahres herausstellt, dass 10% Umsatzzuwachs nicht zu erreichen sind (aufgrund zu geringen Budgets oder sonstiger Faktoren), dann muss man den eigenen Anschlag nachführen. Die Objekte der Begierde sind da, wo sie sind – und nicht dort, wo man mit seinen Rahmenbedingungen hinzielt. Man muss sich suchen – dafür gibt es einen Sucher bzw. Kimme und Korn – und man muss sich ihnen nachführen, wenn sie sich bewegen. Das nennt man Zielen. Zielen ist eine Tätigkeit, kein Zustand.

Wer ein Ziel benennt, der muss das im Blick haben. Er muss seinen Jägern vertrauen, dass sie gut zielen können. Und er muss damit leben, dass sich die Ziele bewegen und die Jäger Freiheit brauchen, um ihnen folgen zu können. Und schließlich: Es kann die Zielverfehlung keine Sache der Ehre und der quasi persönlichen Enttäuschung sein. Zielverfehlung ist vielmehr der Normalfall in einer Welt hoch beweglicher oder im Nebel verborgener Ziele.

Zielverfehlung also mit noch engeren Rahmenbedingungen zu beantworten ist grob kontraproduktiv. Nicht Einschnürung hilft Jägern, sondern Befreiung, Mobilität, Autonomität.

Das haben Polizei und Militär schon lange begriffen. Die Helden sind heute nicht mehr Heere, sondern hochmobile, intelligente, autonome Einsatzkommandos – die vor Ort jeder unvorhergesehenen Bewegung ihrer Ziele folgen können.

Missverständnis #2: Das Ziel ist der Zweck

Dann gibt es noch ein zweites Missverständnis. Es werden nämlich oft Ziele für Zwecke gehalten. “Warum machen wir das alles hier, Leute? Um Geld zu verdienen! Und deshalb brauchen wir im nächsten Jahr 10% mehr Umsatz.” So wird ein Ziel schnell als Zweck geadelt.

Doch das halte ich für ein großes Missverständnis. Weder ist “10% mehr Umsatz” ein Zweck, noch ist “Geld verdienen” ein Zweck. Beides sind nur Mittel. Sie sind Mittel zu einem Zweck, zu etwas “Höherem”.

Da will ich jetzt nicht gleich Weltfrieden und Klimaziele beschwören – aber ein bisschen geht´s schon in die Richtung. Zweck ist, wofür etwas getan wird. Was ist der Antrieb hinter einer Unternehmung? Um dessentwillen werden Menschen zu Kunden. Der Zweck ist die Antwort auf die Frage: “Warum sollte sich irgendjemand für uns interessieren?” (Die Betonung liegt dabei auf “uns”, denn die “Identität der Firma” ist am Ende das einzige, was sich nicht austauschen lässt. Sie ist das, was sich nicht in ein fernes Land auslagern lässt, um es billiger herzustellen.) Er drückt aus, warum einer/eine Firma wirklich etwas tut.

Wenn der Zweck also “Geld verdienen” sein soll, dann ist die Frage, ob irgendjemand deshalb Kunde würde. Nein, ich glaube nicht. “Ach, du willst Geld verdienen? Klar, dann kaufe ich dein Produkt.” So funktioniert es nicht.

Aber: “Wir bauen die besten Autos der Welt”, ja, das ist ein Zweck. Oder “Unser Unternehmen gibt es, weil wir verstanden haben, was Ihnen bei der Ernährung am Arbeitsplatz fehlt.” Oder “Wir glauben daran, dass Software ohne Support auskommen kann.”

Das sind Zwecke, denn dazu können Sie sagen “Dafür kämpfe ich!”, “Dahinter stehe ich!”, “Das ist auch mein Anliegen!”, “Es gibt mir etwas, wenn ich mithelfen kann, diesen Zweck zu erreichen.”

“Viel Geld verdienen” oder “10% mehr Umsatz” oder “1 Bug pro Monat” sind keine Zwecke. Es sind nur Ziele. Deshalb sollte jede offene oder unterschwellige Überhöhung sofort angezeigt werden. Ziele dürfen nicht zu Zwecken umgemünzt werden, um ihnen mehr Gewicht zu verleihen.

Das bedeutet im Umkehrschluss: Wenn Ziele eben nur Ziele sind, dann können sie jederzeit hinterfragt werden im Sinne von Zwecken. Wer ein Ziel benennt, der muss Antwort darauf geben können, inwiefern es welchem Zweck diene. Welchem Unternehmenszweck dient “10% mehr Umsatz im nächsten Jahr”? Welchem Zweck dient “Nur 1 Bug pro Monat”?

Tja, bei solchem Nachfragen kann dann herauskommen, dass “10% mehr Umsatz” keinem Unternehmenszweck dient. Denn “Das beste Callcenter für Medizinprodukte!” als Zweck zu haben, bedeutet nicht unbedingt, “10% mehr Umsatz im nächsten Jahr” machen zu müssen. Es könnte sein, dass ein Ziel wie “Die Fluktuation der Mitarbeiter um 10% verringern” (bei gleichem Umsatz) dem Zweck viel dienlicher ist.

Fazit

Es ist so eine Sache mit den Zielen. Alle sollen welche haben – dafür gibt es ja auch Personalgespräche mit Zielfestlegungen. Aber der richtige Umgang mit ihnen, die passende Kultur zur Natur von Zielen, die sich in der realen Welt bewegen, ist nicht leicht zu finden. Und die Gefahr der Verwechslung von Ziel mit Zweck lauert überall und umso mehr, je verzweifelter man versucht, das Ziel zu rechtfertigen und zu erreichen.

imageWas tun? Mehr Völkerball spielen könnte helfen ;-) Oder mehr mit den Menschen umgehen, statt mit Human Resources. Denn wer letztlich Menschen, Maschinen, Geld und Zeit über einen Leisten schlägt, weil er in allem nur Ressourcen sieht, der vergisst, was am Ende des Tages der ultimative Zweck aller Unternehmen ist: den Menschen zu dienen. Egal, ob sie im Unternehmen sind oder draußen. Womit wir dann doch bei der Philosophie angelangt sind. Denn dass Menschen immer nur Zweck und nicht Mittel sein sollen, das sagte weiland Immanuel Kant.

Remote Communication mit Event-Based Components und Application Space

Wie Asynchronizität über “Zwischenstücke” in eine EBC-Architektur eingebaut werden kann, habe ich in einem früheren Blogposting beschrieben. Jetzt ist in der myCsharp.de Community die Frage aufgetaucht, wie denn eine verteilte Architektur mit EBCs realisiert werden könnte.  Die einfache Antwort: genauso ;-)

Damit meine ich, dass das, was zum Aspekt remote communication gehört, in eine EBC Standardkomponente verpackt werden sollte. Die Domänenlogik-Komponenten bekommen davon nichts mit. Hier als Beispiel eine simple Echo-Kommunikation: ein Client schickt eine Nachricht an einen Service, der sie (fast) unverändert wieder zurückschickt.

image

Die Kontrakte für die Komponenten sehen so aus:

public interface IClientEBC
{
    event Action<string> Out_RequestTextProcessing;

    void In_ProcessedText(string text);
}

public interface IServiceEBC
{
    void In_EchoText(string text);

    event Action<string> Out_Echo;
}

Und das sollte sich auch nicht ändern, nur weil sie nicht im selben Prozess laufen. Die Event- bzw. Nachrichtenorientierung der EBCs ist dafür eine gute Voraussetzung.

Wie die Implementationen für die Kontrakte aussieht, ist eigentlich uninteressant. Ein Blick auf die Verdrahtung lohnt allerdings. Hier der Host für beide Komponenten, solange sie im selben Prozess laufen:

namespace Servent
{
    class Program
    {
        static void Main(string[] args)
        {
            var service = new ServiceEBC();
            var client = new ClientEBC();

            client.Out_RequestTextProcessing += service.In_EchoText;
            service.Out_Echo += client.In_ProcessedText;

            Application.Run(client);
        }
    }
}

(Ich habe die Client-Komponente als WinForms-Formular ausgelegt; deshalb ruft der Host am Ende Application.Run() auf.)

Wie zu erwarten werden Instanzen beider Komponenten direkt zusammengesteckt. Der Client kommuniziert ohne Umwege mit dem Service.

Remoting zwischenstecken

Der Trick bei EBCs ist, dass Kommunikation über “Drähte” verläuft. Während Objekte normalerweise sozusagen zusammengeschweißt sind, gibt es bei EBCs immer eine Indirektion. Zwischen Client und Service sitzt ein Delegat.

Wie ich in einem früheren Blogposting gezeigt habe, ist das der Schlüssel zu großer Flexibilität. Denn wo sich Client und Service nicht wirklich “berühren”, ist es eigentlich egal, welche Distanz sie haben bzw. was zwischen ihnen sitzt.

Das nutze ich für´s Remoting nun aus. Zwischen Client und Service schiebe ich einfach zwei Standardkomponenten:

 

image

Die Client-Komponente kommuniziert nun nicht mehr direkt mit dem Service, sondern mit einem Proxy für ihn. Und der Service wird nicht mehr von der Client-Komponente angesprochen, sondern von einem Stub.

Ganz wichtig: Für Client und Service macht das keinen Unterschied! In WCF müssen Sie vorausschauen und für einen Service einen Service-Kontrakt definieren. Bei .NET Remoting müssen Sie einen Service durch Ableitung von MarshalByRefObject kennzeichnen. Immer, wenn Sie Funktionalität entfernt betreiben wollen, müssen Sie also speziellen Code dafür entwickeln.

Das ist hier nun anders. Der Code der Standardkomponente ist immer gleich. Sonst wäre es ja auch keine Standardkomponente ;-) Mit EBCs müssen Sie nie mehr Code für die Verteilung entwickeln. (Oder höchstens einmal für das Kommunikationsmedium Ihrer Wahl.)

Wie gesagt, Client und Service ändern sich nicht dadurch, dass Sie verteilt betrieben werden. Der Code jedoch, der die Komponenten verdrahtet, sieht anders aus. Er muss die Remoting-Infrastruktur starten und Client bzw. Service mit ihr verdrahten. Der Host für den Client sieht dann z.B. so aus:

namespace Client
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            using (var host = new RemotingHost(0)) // #1
            {
                var proxy = host.CreateProxy<string, string>(
                                     "localhost:9000/EchoService", // #2
                                     ProxyResponseHandlingModes.Sync); // #3
                var client = new ClientEBC();

                client.Out_RequestTextProcessing += proxy.In_Send;
                proxy.Out_Received += client.In_ProcessedText;

                Application.Run(client);
            } 
        }
    }
}

Der Komponenten-Host – eine Konsolenanwendung – startet die Remoting-Infrastruktur (#1) und erzeugt dann darüber einen Proxy für den Service statt des Service selbst. Wo der Service läuft, gibt eine URL an (#2). Wie die Kommunikation zwischen Proxy und Service läuft, kann dem Client egal sein. Hier kommen der Einfachheit halber aber TCP-Sockets zum Einsatz. Die Option ProxyResponseHandlingModes.Sync (#3) legt fest, dass Antworten im Synchronization Context des Client ankommen sollen; so gibt es kein Problem in WinForms-Anwendungen, denn durch das Remoting sind garantiert mehrere Threads im Spiel.

Hervorhebenswert: Der Proxy ist generisch. Er kann in jede Kommunikationsstrecke eingesetzt werden, die seinem Format folgt. Hier ist das eine bidirektionale Kommunikation auf zwei “Drähten”.

Ein Client-Host unterscheidet sich, wie Sie sehen, nur marginal von dem, der Client und Service gehostet hat. So soll es sein.

Und wie sieht es beim Service aus? Genauso simpel:

namespace Service
{
    class Program
    {
        static void Main(string[] args)
        {
            using(var host = new RemotingHost(9000)) // #1
            {
                host.CreateStub<string, string, ServiceEBC>(
                        "EchoService",
                        s => s.In_EchoText, 
                        (s, c) => s.Out_Echo += c);

                …
            } 
        }
    }
}

Der Service-Host startet dieselbe Remoting-Infrastruktur – allerdings unter Angabe eines TCP-Ports (#1). Die finden Sie beim Client in der Adresse des Service wieder (#2 im Listing davor). Der Client-Host übergibt an die Infrastruktur 0 als Port, weil ihm der Port egal ist. Er muss keinen Endpunkt definieren, weil er nicht explizit adressiert wird.

Dann erzeugt der Service-Host einen Stub für den Service. Das ist der Kommunikationsendpunkt, bei dem Nachrichten vom Proxy ankommen. Er leitet sie weiter an den eigentlichen Service. Für den Service ist der Stub der Client. Und wieder ist die Infrastruktur generisch und der eigentliche Service merkt nichts davon, dass er nun entfernt von Clients betrieben wird.

Achtung: Der Service wird hier nicht instanziert! Sein Typ und zwei Lambda Ausdrücke werden allerdings an die Factory-Methode übergeben. Die reicht sie weiter an eine ServiceFactory (s.u.). Das ermöglicht eine Erzeugung des Service für jede Nachricht wie beim Single-Call-Modus von .NET Remoting. Services zustandslos zu machen ist daher ein kleines Zugeständnis an die Verteilung. Allerdings ist das nicht zwangsläufig nötig; in diesem Fall ist es nur etwas einfacher, um die Diskussion um Remoting nicht noch mit Aspekten der asynchronen Verarbeitung zu belasten.

Das war´s. So einfach kann Remoting mit EBCs sein.

Fragt sich nur, wie es unter der Haube funktioniert :-) Ich habe es mit dem Xcoordination Application Space implementiert. Ihn habe ich in der dotnetpro beschrieben und in Grundzügen auch in meinem englischen Blog. An dieser Stelle halte ich deshalb meine Erklärungen knapp:

Host und Proxy der Remoting Infrastruktur

Der Remoting Host selbst ist einfach. Er startet eigentlich nur einen Application Space und dient als Factory für Proxy und Stub:

namespace ebc.patterns
{
    public class RemotingHost : IRemotingHost
    {
        private readonly IXcoAppSpace space;

        public RemotingHost(int tcpPort)
               : this(string.Format("tcp.port={0}", tcpPort)) { }
        public RemotingHost(string configString)
        {
            this.space = new XcoAppSpace(configString);
        }

        public void Dispose()
        {
            this.space.Dispose();
        }
    …

Ein Proxy ist schnell erzeugt:

public IRemotingProxy<TRequest, TResponse> CreateProxy<TRequest, TResponse>(
       string serviceAddress,
       ProxyResponseHandlingModes mode)
{
    var remoteStub = this.space.ConnectWorker<Port<Request<TRequest, TResponse>>>(serviceAddress);
    return new RemotingProxy<TRequest, TResponse>(remoteStub, mode);
}

Dazu nimmt der Remoting Host Kontakt mit einem generischen Service-Worker auf, dessen Adresse ihm der Client übergibt. Und der RemotingProxy übernimmt die Aufgabe, EBC-Nachrichten in AppSpace Nachrichten zu übersetzen:

namespace ebc.patterns
{
    public class RemotingProxy<TRequest, TResponse> : IRemotingProxy<TRequest, TResponse>
    {
        private readonly Port<Request<TRequest, TResponse>> remoteWorker;
        private readonly Port<TResponse> responses;

        private readonly Port<Exception> exceptions;
        private readonly SynchronizationContext ctx;

        internal RemotingProxy(Port<Request<TRequest, TResponse>> remoteWorker, ProxyResponseHandlingModes mode)
        {
            this.remoteWorker = remoteWorker;

            if (mode == ProxyResponseHandlingModes.Sync)
                this.ctx = SynchronizationContext.Current;

            // Antwort vom Service verarbeiten
            this.responses = new Port<TResponse>();
            Arbiter.Activate(
                new DispatcherQueue(),
                Arbiter.Receive(true, this.responses, msg => this.ProcessMessageInSyncContext(msg, this.Out_Received))
                );

            // Exceptions vom Service verarbeiten
            this.exceptions = new Port<Exception>();
            Arbiter.Activate(
                new DispatcherQueue(),
                Arbiter.Receive(true, this.exceptions, ex => this.ProcessMessageInSyncContext(ex, this.Out_Exception))
                );

            this.Out_Exception += ex => { };
        }

        void ProcessMessageInSyncContext<T>(T msg, Action<T> messageHandler)
        {
            if (this.ctx != null)
                this.ctx.Send(x => messageHandler(msg), null);
            else
                messageHandler(msg);
        }

        public void In_Send(TRequest message)
        {
            var c = new Causality("ex", this.exceptions);
            Dispatcher.AddCausality(c);
            {
                var req = new Request<TRequest, TResponse> {
                                  Data = message,
                                  Response = responses};
                this.remoteWorker.Post(req);
            }
            Dispatcher.RemoveCausality(c);
        }

        public event Action<TResponse> Out_Received;
        public event Action<Exception> Out_Exception;
    }
}

Außerdem übernimmt der Proxy auch die Exception-Verarbeitung. Das habe ich bisher der Einfachheit unterschlagen. Wenn Exceptions vom Service kommen, dann leitet der Proxy sie auf einem speziellen Output-Pin weiter. Damit ist die Behandlung gleich, egal ob der Client Antworten vom Service in seinem Synchronization Context empfangen will oder nicht.

image

Stub der Remoting Infrastruktur

Auf der Service-Seite sieht es etwas kniffliger aus. Die ist nämlich grundsätzlich multi-threaded. Derselbe Stub wird für viele Anfragen und Antworten benutzt. Daher müssen Antworten vom Service, der nichts von solchen Feinheiten weiß, mit Anfragen korreliert werden. Nur so können sie an den, der die Anfrage geschickt hat, zurückgesandt werden.  Deshalb ist der Stub nicht fest verdrahtet mit einer Service-Instanz (was allerdings möglich wäre). Das obige Bild von Stub und Service ist also eine Vereinfachung. In Wirklichkeit sind die Verhältnisse so:

image

Das ändert zum Glück nichts daran, dass der Service nichts von seinem Glück wissen muss, remote betrieben zu werden.

Wie funktioniert das Ganze nun? Also…

1. Eine Anfrage kommt vom RemotingProxy über ein Transportmedium im RemotingStub beim StubWorker an. Der StubWorker ist ein AppSpace Worker, der die Übersetzung von CCR Port-Kommunikation auf EBC Events übernimmt. Da er asynchron arbeitet, braucht er eine Möglichkeit, Antworten Anfragen zuzuordnen. Deshalb enhält sein Nachrichtenaustausch mit einer angeschlossenen EBC KorrelationsIDs.

namespace ebc.patterns
{
    internal class StubWorker<TRequest, TResponse>
                   : Port<Request<TRequest, TResponse>>
    {
        private readonly Dictionary<Guid, IPort> responsePorts = new Dictionary<Guid, IPort>();

        [XcoConcurrent]
        public void ProcessRequest(Request<TRequest, TResponse> req)
        {
            var msg = new CorrelatableMessage<TRequest>(req.Data);
            lock (this.responsePorts)
            {
                this.responsePorts.Add(msg.CorrelationId, req.Response);
            }
            this.Out_Received(msg);
        }

        public void In_Reply(CorrelatableMessage<TResponse> msg)
        {
            IPort response;
            lock (this.responsePorts)
            {
                response = this.responsePorts[msg.CorrelationId];
                this.responsePorts.Remove(msg.CorrelationId);
            }
            response.PostUnknownType(msg.Data);
        }

        public event Action<CorrelatableMessage<TRequest>> Out_Received;
    }
}

2. Der Kommunikationspartner des Workers kann wg. der KorrelationsIDs nicht der Service sein. Der hat von solcherlei Dingen keine Ahnung. Stattdessen reicht der Worker die Anfrage weiter an die ServiceFactory. Die erzeugt dafür eine neue Service-Instanz – und schaltet ihr eine Standardkomponente vor, die Nachrichten mit KorrelationsID in solche ohne umwandeln kann (und umgekehrt).

namespace ebc.patterns
{
    internal class ServiceFactory<TRequest, TResponse, TService>
                   : IServiceFactory<TRequest, TResponse>
        where TService : new()
    {
        private readonly Func<TService, Action<TRequest>> inputPin;
        private readonly Action<TService, Action<TResponse>> connectOutputPin;

        public ServiceFactory(
                Func<TService, Action<TRequest>> inputPin,
                Action<TService, Action<TResponse>> connectOutputPin
            )
        {
            this.inputPin = inputPin;
            this.connectOutputPin = connectOutputPin;
        }

        public void In_Request(CorrelatableMessage<TRequest> request)
        {
            var corr = new SyncCorrelator<TRequest, TResponse>();
            var service = new TService();

            corr.Out_Received += this.inputPin(service);
            this.connectOutputPin(service, corr.In_Correlate);

            corr.Out_Reply += r => this.Out_Response(r);

            corr.In_DeCorrelate(request);
        }

        public event Action<CorrelatableMessage<TResponse>> Out_Response;
    }
}

Die ServiceFactory bastelt also dynamisch für jede Anfrage einen EBC-Platineninhalt zusammen, den sie mit ihren eigenen Pins verbindet.

Das ist kein Hexenwerk, aber doch ein bisschen umständlich. Zum Glück muss es nur einmal implementiert werden und ist dann für alle Kommunikationen gleich. Einfacher wäre es allerdings, der Service wäre darauf ausgelegt, asynchron und thread-safe zu arbeiten. Dann käme er selbst mit KorrelationsIDs zurecht und müsste nicht immer neu erzeugt werden. Dann wäre das Bild tatsächlich so einfach wie oben: Stub spricht mit Service. Wie Sie sehen, geht´s aber auch so, ohne Vorüberlegungen. Das ist das schöne an EBCs.

Im folgenden Bild stelle ich die beiden alternativen mal nebeneinander:

image

Der Quellcode, den Sie in einem Mercurial Google Projekt hier finden, spiegel das allerdings nicht wider. Er entspricht noch dem Bild, bei dem der RemotingStub die ServiceFactory enthält. Sauberer finde ich allerdings diese letzte abgebildete Variante. Sie entzerrt die Verantwortlichkeiten. Der RemotingStub ist nur für die Kommunikation zuständig – auch wenn das bedeutet, dass er Nachrichten mit KorrelationsIDs erzeugt/konsumiert. Darauf kann sich die bei Bedarf einstellen: Ist der Service nicht damit vertraut, muss er pro Nachricht neu instanziert werden. Dazu verdrahten sie ihn passend auf einer eigenen Platine.

Fazit

Client und Service müssen nicht speziell auf eine Verteilung vorbereitet werden. Und Sie müssen für eine Verteilung auch nichts extra programmieren, keine Service- oder Datenkontrakte sind nötig. Die Remoting-Infrastruktur kann generisch sein. Sie stecken sich also zusammen, was Sie brauchen.

Das bedeutet nicht, dass Sie keinen Gedanken verschwenden sollen, ob Services lokal oder entfernt betrieben werden. Die grundsätzliche Asynchronizität (oder auch mal Unverfügbarkeit ;-) eines entfernten Dienstes können seine Implementation beeinflussen. An der grundsätzlichen Einfachheit des Zusammensteckens ändert das jedoch nichts. Mal stecken Sie nur weniger, mal mehr zusammen.

Und nun: Basteln Sie schön :-) Arbeiten Sie mit der Remoting-Infrastruktur dieses Beispiels oder bauen Sie eine eigene auf Basis von .NET Remoting oder WCF oder was Sie wollen. Bei EBCs ist das noch erlaubt, weil sie so neu sind :-)

Dienstag, 1. Juni 2010

Die Ubiquitous Language konsequent codieren [OOP 2010]

Das war mir doch ein Experiment wert: Wie würden es Entwickler aufnehmen, wenn quasi alle Parameter von Methoden (zumindest in den Kontrakten von Komponenten) nicht primitiv sein sollen? Auf dem Coding Dojo in München habe ich es ausprobiert.

Das Beispielszenario dort war eine Rechtschreibprüfungsanwendung. In der gibt es dann irgendwo eine Funktion WortKorrekt() o.ä. Deren Signatur würde üblicherweise so aussehen:

bool WortKorrekt(string wort)

Darin sind zwei primitive Typen zu finden: bool und string.

Ich bin nun der Meinung, dass wir uns anstrengen sollten, solche Typen (zumindest in den Kontrakten von Komponenten) zu vermeiden. Statt unspezifischer primitiver Typen sollten wir spezifische Typen passend zur Problemdomäne definieren.

Codegrundlage Ubiquitous Language

Über die Problemdomäne sprechen wir unter uns und mit dem Kunden in der Ubiquitous Language (UL).  Es ist deshalb wichtig, sie in unserem Code wiederzufinden. Sonst müssen wir ständig Aufwand treiben, um unsere Rede in Code (und wieder zurück) zu übersetzen. Code würde weniger verständlich. Und wir wären unsicherer, ob wir unseren Code geeignet strukturiert haben, denn die Begriffe der UL sollten darin repräsentiert werden.

Damit wir uns der UL bewusst werden, habe ich im Dojo mit den Teilnehmern eine Concept Map der UL für die Domäne “Rechtschreibprüfung” erarbeitet:

image

Die sieht ein bisschen wüst aus, vor allem, weil man meine Schrift schon nach wenigen Minuten unleserlich wird ;-) Doch mir gehts hier nicht um Einzelheiten, sondern einfach mal eine Impression.

Die Concept Map enthält für jeden Begriff der Domänensprache einen “Kuller”. Unterschieden wird dabei nicht zwischen Daten und Diensten, Verben und Substantiven. Was wichtig ist, wird in einen Kreis gesetzt. Und dann werden die Begriffe verbunden mit qualifizierten Beziehungen. Ein “Wörterbuch” (Begriff) ist z.B. “abgefasst in” (Beziehung) einer “Sprache” (Begriff).

Der Vorteil einer Concept Map (z.B. statt eines Klassendiagramms) in einem frühen Stadium der Planung ist seine Informalität. Es gibt nur wenige Regeln zu beachten. Sie können Ihren Gedanken freien Lauf lassen. Ziel ist Vollständigkeit in Bezug auf Begriffe/Konzepte und Beziehungen auf einem hohen Abstraktionsniveau. Concept Maps sind Landkarten des Begriffsterrains der Problemdomäne.

Worauf ich nun hinaus will: der Concept Map können Sie die grundlegenden Funktionseinheiten und Daten der Problemdomäne entnehmen. Sie dient mithin der Hinführung zur Implementierung. Die Konzepte “Prüfer” und “Parser” in der Concept Map der Rechtschreibkontrolle sollten also z.B. als Services implementiert werden. (Nein, ich meine nicht SOA-Services, sondern benutze den Begriff “Service” für dienstleistungsorientierte Funktionseinheiten im Gegensatz zu solchen, die eher Daten halten.)

“Wörterbuch” hingegen hört sich eher wie eine Datenstruktur oder zumindest eine Entität an.

Soweit so normal. Sie wären auf diese Konzepte und ihre spätere Implementierung als Klassen vielleicht auf anderem Weg gekommen; identifiziert hätten Sie sie jedoch allemal.

Jetzt aber zu Prüftext, Prüfwort und Fehlerwort. Das sind ebenfalls Begriffe der Domänensprache. Doch wie übersetzen Sie die in Code? Das sind keine Services, keine Entitäten, nicht mal “echte” Datenstrukturen. Der Prüftext ist eher nur ein Text in einer bestimmten Situation. Dito das Prüfwort und das Fehlerwort. Prüfwort und Fehlerwort mögen sogar dieselben Texte sein, einmal vor der Prüfung und einmal hinterher.

Da liegt es doch nahe, diese Begriffe in string-Parameter/Felder zu übersetzen, oder? Die obige Funktion tut das exemplarisch und sieht normal aus, oder? Immerhin haben diese Begriffe keine weiteren Eigenschaften, sind nur “im Fluss” zwischen Services zu finden und ihre Daten müssen auch nicht persistiert werden. Einer so simplen Übersetzung steht daher nichts im Wege.

Primitiven der Ubiquitous Language codieren

Die klassische OOA/OOD kennt natürlich die Repräsentation von Domänenbegriffen als Klassen. Aus einem “Kunden” wird die class Customer, aus einem “Termin” die class Appointment, aus einem Auftragsstatus ein enum OrderState usw. Begriffe, die offensichtliche Eigenschaften haben oder zusammengesetzt sind und Daten repräsentieren, werden in class oder struct übersetzt.

Eine Rechnung besteht aus einer Rechnungsnummer, einem Rechnungsdatum, einem Kunden und vielem mehr. Die Übersetzung sieht dafür meist so aus:

class Invoice
{
    public string InvoiceNumber { get; set; }
    public DateTime InvoiceDate { get; set; }
    public Customer Customer { get; set; }
    …
}

Rechnung und Kunde haben eigene Datentypen bekommen, alle anderen Informationen sind als primitiv eingestuft und mit Standarddatentypen definiert.

Dem möchte ich nun entgegenhalten, auch Primitiven der Domänensprache durch eigene Datentypen zu repräsentieren. Ja, genau: Auch wenn ein Begriff nur für eine ganze Zahl oder eine Zeichenkette steht, sollten Sie ihm eine Klasse (oder eine Struktur) spendieren. Die Implementierung der Rechnung würde dann z.B. so aussehen:

class Invoice
{
    public InvoiceNumber Number { get; set; }
    public InvoiceDate CreatedAt { get; set; }
    public Customer Customer { get; set; }
    …
}

Oder die Prüfroutine der Rechtschreibkontrolle würde so aussehen:

bool WortKorrekt(PrüfWort wort)

Warum das? Ist nicht bool WortKorrekt(string wort) genauso gut zu lesen, genauso aussagekräftig wie bool WortKorrekt(PrüfWort wort)?

Landläufig betrachtet liegen beide in puncto Verständlichkeit nahe beieinander. Doch ich behaupte, dass Code, der weniger mit primitiven oder allgemeinen Typen des .NET Framework arbeitet und stattdessen konkrete Typen der UL benutzt, typsicherer ist und semantisch weniger Zweifel lässt.

Am Ort der Definition mag das weniger sichtbar sein als am Ort der Nutzung:

if (prüfer.WortKorrekt(new PrüfWort(…)))

oder

var inv = new Invoice(InvoiceNumber.Create(), InvoiceDate.Today(), …);

lassen weniger Zweifel, ob zusammenpasst, was zusammenpassen soll.

Einer Zeichenkette “Daten” können Sie nicht ansehen, was sie bedeutet. Es ist einfach nur eine Zeichenkette. Aber new PrüfWort(“Daten”) ist ganz eindeutig etwas anderes als new Dateiname(“Daten”). Ebenso ist new InvoiceDate(“2010-06-01”) (der erste Tag im Monat Juni) etwas anderes als new InvoiceNumber(“2010-06-01”) (die erste Rechnung im Monat Juni).

Die übliche Objektorientierung richtet ihr Augenmerk auf Objekte, d.h. “Dinger mit Eigenschaften”. Ich möchte Sie motivieren, genauer hinzusehen. Zoomen Sie näher heran und sehen Sie die Eigenschaft als letztlich nichts anderes als die Objekte. Die Welt ist rekursiv. Sie besteht aus “Objekten”, die aus “Objekten” bestehen, die aus “Objekten” bestehen usw. Und auf jeder Ebene kann es Begriffe der Ubiquitous Language geben.

Im Beispiel besteht ein Prüftext aus Prüfworten. Das versteht ein Entwickler wie auch der Kunde. Wird das bei der Codierung nur in die Zerlegung einer Zeichenkette in Zeichenketten übersetzt, dann entsteht eine Diskrepanz zwischen Sprache und Code. Der Grundstein für Missverständnisse ist gelegt.

So sollte in der Rechtschreibkontrolle also die Service-Funktionseinheit Parser besser wie folgt definieren:

class Parser
{
    IEnumerable<Prüfwort> ZerlegeText(Prüftext text) { … }
}

mit

class Prüftext
{
    public string Text;
}

class Prüfwort
{
    public string Wort;
}

(Dass ich hier der Klasse Prüftext keine Methode gebe, über die man an ihre Worte herankommt, möchte ich undiskutiert lassen. Das ist ein Thema für einen anderen Blogartikel.)

Zusammenfassung

Seien Sie rigoros bei der Implementierung. Nehmen Sie die Ubiquitous Language Ihrer Problemdomäne ernst. Repräsentieren Sie alle Begriffe durch “Kontrakte” (Interface/Klasse, Struktur, Methodensignatur). Primitive Typen des .NET Framework sollten “im API” von Komponenten auf das Nötigste beschränkt sein. Stattdessen übersetzen Sie selbst primitive Konzepte der Domäne in eigene Typen.

Sie machen damit ihren Code leichter lesbar und auch leichter veränderbar. Denn wenn heute ein Prüfwort womöglich nur eine Zeichenkette ist, dann könnte es morgen eine Zeichenkette mit einer Position in einem Text sein.

class Prüfwort
{
    public string Wort;
    public Textposition Position;
}

Und übermorgen ist es eine Zeichenkette mit einer Position in einer Sprache.

class Prüfwort
{
    public string Wort;
    public Textposition Position;
    public Wörterbuchsprache Sprache;
}

Machen Sie Ihren Code an jeder Stelle so präzise und domänenorientiert wie möglich.