Follow my new blog

Donnerstag, 3. Juni 2010

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.

Montag, 31. Mai 2010

Gezieltes Coding Dojo

Dass das Coding Dojo am 26.5.2010 kein Coding Dojo war, darüber sind Ilker und ich uns durchaus einig. Der Rahmen war zwar der des üblichen Münchner Dojos – der Inhalt wich dann jedoch stark davon ab. Ilker hat diese Abweichung jetzt nochmal begründet und ich habe auch schon innerhalb dieser bezweckten Abweichung reflektiert. Was bleibt da noch zu sagen?

Messlatte #1: Die offizielle Zieldefinition

image Mir geht der Kommentar von Steffen Forkmann nicht recht aus dem Kopf. Ist da wirklich etwas zu kurz gekommen beim Dojo? Wenn etwas zu kurz kommt, dann weicht etwas vom Ziel ab. Was aber ist das Ziel eines (oder des Münchner) Coding Dojos?

In einem früheren Posting von Ilker ist zu lesen, das Ziel eines Coding Dojos sei…

“[d]ie erfolgreiche Vermittlung von nutzbaren Lerneffekten – für jeden Teilnehmer. Er sagte dass ein Dojo keine feste, durchgeplante Veranstaltung mit fixem Programm oder Paradigma sei, sondern dass das Dojo primär die Teilnehmer und den Lerngedanken im Fokus hat.”

Wenn ich das in Anschlag bringe, dann bin ich ziemlich sicher, dass auch das letzte Coding Dojo sein Ziel erreicht hat. Niemand sollte zu kurz gekommen sein. Denn einen “nutzbaren Lerneffekt” hat es für jeden gegeben. (Wer keinen hatte, melde sich bitte bei mir. Dann bessere ich nach.)

Und da es keine “feste, durchgeplante Veranstaltung mit fixem Programm oder Paradigma” ist, sollte auch meine anders als übliche Nutzung der Dojo-Zeit dojokonform gewesen sein.

Allerdings: Wie steht es mit den “Teilnehmer[n] und de[m] Lerngedanken”? Mein Eindruck war, dass die Teilnehmer nicht nur Empfänger waren, sondern stets mitgestaltet haben. Anders als sonst, aber immer noch aktiv. Sehr aktiv sogar.

Fazit: Gem. der öffentlichen Definition des Münchner Coding Dojos ist alles im grünen Bereich gewesen. Anders, ungewohnt, aber zielführend. Ilker hat sich “nichts zu Schulden kommen lassen”. Als “Salonier” des Community Lernens hat er seine Aufgabe erfüllt. Merci, Ilker!

Messlatte #2: Selbstgebastelt

Soweit die offizielle Definition von Coding Dojo. Ich möchte jedoch die Gelegenheit nicht verstreichen lassen, mir auch selbst über die Ziele eines Coding Dojos Gedanken zu machen. Was gehört aus meiner Sicht dazu?

Zunächst einmal zum Begriff bzw. dem Bild des Dojo: Steffen Forkmann (und andere) scheinen enttäuscht gewesen zu sein, dass im letzten Dojo soviel “Lehrereinsatz” war. Das ist allerdings – zumindest im Hinblick auf das klassische Dojo – ein Missverständnis. Ein klassische Dojo ist ein Ort des Lernens des rechten Pfads, der auch von Religion geprägt ist. Im Dojo geht es also klassisch um einen Kanon vermittelt durch eine Lehrer/Meister-Schüler/Lehrling-Hierarchie. Der Gedanke einer gleichberechtigten Community in Bezug auf die Lerninhalte ist dem klassischen Dojo fremd.

Wenn nun die Softwaregemeinde den Begriff Dojo für sich entdeckt, dann halte ich es für vermessen (oder zumindest missverständlich) ihn ohne diese klassische Wurzel zu denken. Ein Dojo nur als Dojo zu akzeptieren, wenn es keinen Lehrer gibt, täte also besser, die Veranstaltung anders zu nennen.

Das soll nicht bedeuten, dass in einem Coding Dojo alles so laufen soll wie in einem Kendo Dojo im Japan des 17. Jahrhunderts. Modernisierung im Geiste westlicher Gleichberechtigung und Individualität ist natürlich völlig ok. Achtsamkeit im Umgang mit einem Traditionsbegriff scheint mir jedoch angebracht. Insofern bin ich der festen Überzeugung, dass in einem Dojo auch mal eine Hierarchie auftreten darf: einer weiß was, die anderen wollen es von ihm lernen.

Überwachung der Zielerreichung: Ob mit der ohne Lehrer – in Ilkers Definition ist das ja auch offen gelassen –, ein Ziel ist für ein Dojo wie für jede Veranstaltung nötig. Laut Ilker ist das “nutzbare Lerneffekte”; im Dojo soll also irgendetwas gelernt werden. Die Teilnehmer sollen nachher schlauer sein. Was das ist, sei einmal dahingestellt. Ich nehme irgendeinen fachlichen Aspekt an. Ilker lässt auch das frei in seiner Definition – konkretisiert jedoch in der normalen Praxis. Test-Driven Development scheint mir am ehesten die Praktik, die das Dojo als Lerninhalt zu vermitteln versucht.

Wichtiger als der Lerninhalt ist mir an dieser Stelle jedoch, wie der erreicht wird. Steffen und andere favorisieren ein “Peer-Learning”, d.h. ein Lernen irgendwie ohne Lehrer, sondern von Teilnehmern auf mehr oder weniger gleicher Kenntnisstufe. Dazu gleich mehr.

Das ist ein valider Ansatz – allerdings kann ich dahinter nur Ernsthaftigkeit erkennen, wenn im Rahmen des Dojo auch eine Überprüfung stattfindet, ob das Ziel erreicht wurde. Wie findet aber eine Lernerfolgskontrolle statt? Bei den Dojos, die ich erlebt habe – sorry to say –, habe ich davon wenig gesehen.

Am Ende Spaß gehabt zu haben, ist keine Lernerfolgskontrolle in Bezug auf ein fachliches Ziel. Auch Funktionalität hergestellt zu haben, ist keine Lernerfolgskontrolle. Wie also stellen sich Steffen und die anderen es sich vor, den Lernerfolg in Bezug auf TDD oder anderes festzustellen? Solange der Anspruch existiert, dass nur ein ganz bestimmter Stil ein rechtes Dojo ausmacht, solange sollte auch klar gemacht werden können, wie denn dieser Stil seinen Erfolg sicherstellt.

Lernen von einander: Das Lernen in der Gruppe ist eine gute Sache. Lernen ist eine durchaus soziale Angelegenheit. Und Lernen ohne Machthierarchie ist auch wunderbar. Da bin ich dabei und finde es toll, das Ilker sich für einen solchen Lernprozess als “Facilitator” immer wieder zur Verfügung stellt. (Und nicht nur das! Ilker ist auch nimmermüder Motivator der Dojo-Sache. Das ist nicht zu unterschätzen.)

Ein Facilitator überwacht jedoch nur einen Prozess. Läuft alles gem. Prozessregelwerk? Tanzt keiner aus der Reihe? Gibt es keine Hindernisse, die dem Prozess im Wege stehen? Das im Blick zu behalten, ist Ilkers Sache.

Das bedeutet im Umkehrschluss und zur Entlastung von Ilker: Die Wissensvermittlung bzw. –generierung ist Sache der Teilnehmer.

Wie funktioniert das aber nur? Die Prämisse ist, dass es ein zu erlangendes Wissen gibt. Irgendwo hängt im Raum eine Latte (immer höher), die das Dojo überspringen soll. Aber wo hängt die? Wer weiß es, wenn kein “Lehrer” anwesend ist/sein darf?

image Leider, leider muss ich sagen, dass der Anspruch eines “Community Lernens”, bei dem alle gleich sind in der Diskussion, zwar schön ist – mir jedoch wenig effektiv und effizient erscheint. Es drängt sich mir das Bild eines Debattierclubs auf, in dem alle eine Meinung haben, es aber keiner so genau weiß.

Allemal solange keine systematische Ergebniserarbeitung und –sicherung stattfindet, wird da schnell viel geredet, die Energie fließt – nur wohin?

Wie gesagt: dass am Ende irgendwie Code läuft, ist mir nicht genug.

Wenn alle gleich “ahnungslos” sind, wer stellt am Ende fest, ob eine Lösung wirklich gut ist (oder zumindest besser als eine andere)? Gefühle haben dazu natürlich alle; und ein Durchschnittsgefühl lässt sich auch ermitteln. Solange wir jedoch den Anspruch haben, in einer Branche zu arbeiten, in der es auch mal gesichertes, also überindividuelles und geschmacksunabhängiges Wissen gibt, ist mir auch das zuwenig.

Übungsgruppen in der Universität arbeiten auch zusammen. Doch sie arbeiten in den Grenzen eines Korrektivs. Am Ende müssen sie ihre Ergebnisse präsentieren. Und die werden mit dem kanonischen Wissen verglichen. (Ausdrückliche Forschung, d.h. die Generierung neuer Erkenntnisse nehme ich hier aus.)

Beim Coding Dojo ist das zumindest nach dem Ideal einiger per Definitionem nicht der Fall; das nimmt kein kanonisches Wissen an – so scheint es mir zumindest. Wie also – das möge man mir erklären – kann ein Teilnehmer dann sicher sein, etwas zu lernen, wenn letztlich die anderen nicht schlauer sind als er – oder zumindest keine Rolle einnehmen dürfen, in der sie ihre Erkenntnisse gezielt den anderen vermitteln?

Man möge mich nicht falsch verstehen! Ich rede keiner Lehrerattitüde das Wort. Und der Wissenskörper befindet sich für mich immer im Fluss. Auch kann aus meiner Sicht Lernen immer nur jeder selbst; ein Lehrer ist bestenfalls Facilitator. (Im Clean Code Developer Camps haben Stefan Lieser und ich deshalb spaßeshalber das Ideal, dass das Lernen am besten funktioniert, wenn wir nur noch Kaffee ausschenken müssen und der Rest von selbst läuft ;-)

image Doch auch wenn der Wissenskörper im Fluss ist, hat er eine vom Einzelnen unabhängige (vorübergehende) Form, die zu erkennen sich lohnt. Zu der mögen TDD, Lambda Ausdrücke oder Funktionale Programmierung gehören. All das kann man besser oder schlechter tun. Aber wer weiß, ob eine Coding Dojo Gruppe es eher besser oder eher schlechter tut? Der Spaß an der Sache ist kein wirklicher Gradmesser dafür.

Mir geht es nicht darum, eine bestimmte Person auszuloben, die “es” besser weiß. Auch ich weiß nicht alles besser – aber von manchem glaube ich schon, dass ich es besser als manche weiß. Und so geht es mit anderen Themen anderen Community-Mitgliedern. Jeder weiß etwas – und auch mal besser als andere. Ist halt so. Und ist gut so.

Warum soll dann nicht der, der ein Thema besser beherrscht als andere, diese anderen anleiten? Warum muss Coding Dojo bedeuten, dass ohne Lehrer gearbeitet wird? Das will mir nicht in den Kopf.

Woher der Lehrer kommt, ist ja egal. Und über die Didaktik und Methodik lässt sich auch noch trefflich diskutieren.

Wenn es aber einen Lehrer gibt, also einen mit Wissensvorsprung, warum soll der nicht die anderen anleiten? Das halte ich für die effizientes Form des Lernens. Wir müssen uns ja nicht dümmer stellen als wir sind. Wie die Kinder zu lernen, also alles selbst herausfinden, ist eine romantische Vorstellung. Kinder lernen allerdings so, weil sie oft nicht anders können. Besser lernen sie, wenn man sie geeignet anleitet. Dann kann man nämlich das Verhältnis von Erfolg und Misserfolg steuern. Die Lernerfahrung ist dann eher motivierend.

Meine Zieldefinition

Und was ist der langen Rede kurzer Sinn? Was ist meine Definition von Coding Dojo? Hier Stichpunkte:

  • Coding Dojo bedeutet lernen in Gemeinschaft
  • Coding Dojo bedeutet den Willen zu Clean Code (Development)
  • Coding Dojo bedeutet, zunächst den state-of-the-art zu erarbeiten
  • Coding Dojo bedeutet, Code zu produzieren
  • Coding Dojo bedeutet Ergebnissicherung
  • Coding Dojo bedeutet, über das Lernen zu reflektieren
  • Coding Dojo bedeutet, Spaß mit gleichgesinnten Entwicklern zu haben

Das sind eine Menge Eckpunkte für ein Coding Dojo. Damit die alle verlässlich angelaufen werden, ist eine Moderation nötig. Ilker, du hast also auch in meinem Bild einen Job ;-)

Diese Eckpunkte spannen einen Raum auf, das Dojo. Wie das dann konkret mit Leben gefüllt wird, ist eine andere Sache. Ob Katas gemacht werden (kleine Aufgaben) oder über mehrere Sitzungen mit Hausaufgaben Projekte realisiert, das ist egal. Ob einer den anderen etwas vermittelt oder nicht, auch das ist mir letztlich egal – allerdings habe ich da so meine Meinung, wie effektiv das Lernen ist, wenn alle aus ihrer eigenen Unsicherheit heraus versuchen, Sicherheit zu erlangen. Das kann funktionieren – ist dann aber Forschung. Wer als pragmatischer Entwickler will aber forschen mit all dem, was an dem Begriff hängt?

Und nun kommt die Community. Was macht ein Dojo aus? Was gibt es diesen Eckpunkten hinzuzufügen? Was muss weg? Oder braucht es gar keine Definition und jeder macht einfach so für sich etwas und nennt es nur, wie die anderen?

Für mehr Qualität im Netz durch Identität [OOP 2010]

Auch mal anonym sein dürfen, ist ne schöne Sache. Keine Frage. Deshalb lieben wir das Bargeld immer noch. Ihm haftet nichts von uns an; damit können wir genauso Geschenke wie “Schmuddelkram” kaufen, ohne dass eine Spur auf dem Bankkonto entstünde, die uns verraten könnte.

Und auch sonst finden wir es entspannend, nicht überall und immer erkannt zu werden. Die Berühmten beneiden wir ja eher nur, solange wir nicht unter einer “Beobachtung” wie sie stehen. Wir verstehen, dass ihre hohen Gagen auch “Schmerzensgeld” sind zur Kompensation des Verlustes von Privatsphäre und Anonymität.

Das Internet schließlich hat die Anonymität zur Lebensweise erhoben. Wer hat in ihm nicht gleich mehrere Identitäten? In Diskussionsforen herrscht die Anonymität; kaum ein Eintrag wird mit Realname gemacht:

image

Das ist die ersten 25 Jahre online Foren witzig gewesen, würde ich sagen. Aber inzwischen finde ich es kontraproduktiv in seiner Selbstverständlichkeit. Warum?

 Weil Identität für mich mit Vertrauen und Qualität zu tun hat.

imageWer kennt nicht Flame-Kommentare, wer kennt nicht Trolle in Foren, wer kennt nicht die anonymen Rezensenten von Büchern, Musik und Software? Rechts als Beispiel ein Ausschnitt aus einer Liste von Buchbeurteilungen bei Amazon. Was soll ich von der Meinung von “Ein Kunde” oder “Sonja H.”  oder “fionara” halten? Nichts. Es ist egal, ob sie gut oder schlecht bewerten. Ich kann ihnen nicht vertrauen – allemal, da ich weiß, dass Verlage Rezensionen in Auftrag geben. Gute wie schlechte (für Werke anderer Verlage, versteht sich ;-)

Wer anonym bleibt, investiert nichts. Nicht mal seinen “guten Namen”.

Ohne Investition ist aber keine Ernsthaftigkeit zu erwarten. Ohne Investition ist auch nicht zu erwarten, dass jemand den Empfänger respektiert. Denn Disrespekt hat ja keine Konsequenz.

Konsequenzlosigkeit führt denn auch zu den tollsten Blüten: vom Flame-Kommentar bis zu Vandalismus und Schlimmerem.

Das wissen wir alle – und doch haben wir ein merkwürdig ambivalentes Verhältnis zu Identität und Anonymität. Wir möchten selbst anonym bleiben, hätten es aber gern, dass andere ihre Identität enthüllen. Das kann jeder an sich selbst beobachten, wenn er “gezwungen” wird, sich mit seinem richtigen Namen irgendwo im Internet anzumelden – und sich andererseits aufregt über unflätige Bemerkungen in anonymen Foren.

Ich will nicht abstreiten, dass Anonymität Vorteile hat – zum Beispiel für die “guten Kräfte” in totalitären Regimen. Ich will auch nicht abtun, dass anonyme Beiträge hohe Qualität haben können. Und schließlich erkenne ich auch an, dass ein Avatar/Nick zu einer Identität mit eigenem Wert werden kann; die mag von einer realen Person entkoppelt sein und doch vertrauenswürdig sein. (Dazu braucht es allerdings einige Zeit und wiederkehrende positive Erfahrungen mit dem Avatar/Nick.)

Unterm Strich möchte ich Anonymität also nicht grundsätzlich missen. Weder in der realen, noch in der digitalen Welt.

Dennoch plädiere ich dafür, dass wir uns öfter “zu erkennen geben”. Ich glaube, einfach daran, dass unsere Kommunikation effektiver und effizienter wird, wenn wir identitätsvoll kommunizieren. Ich melde mich daher immer öfter bei Plattformen mit meiner realtweltlichen Identität an (wobei ich mir Abkürzungen erlaube, solange ich über weitere Informationen identifizierbar bin). Das ist für mich eine Form von Investition oder Geben. Denn wenn ich nicht bereit bin, mich als Person (bekanntzu)geben und damit auch verletzlich zu machen, wie kann ich dann erwarten, dass andere das tun? Ohne Identität und Verletzlichkeit ist jedoch kein (zügiger) Vertrauensaufbau möglich. Und ohne Vertrauen kann ich nicht erwarten, dass irgendjemand sich bemüht. Und ohne Bemühen kann ich nicht auf Qualität hoffen.

imageGerade in unserer Branche, wo alle nach Qualitätsinformationen hungern, finde ich es wichtig, dass wir uns – wo immer möglich – persönlich, d.h. mit unseren realen Identitäten “in die Augen schauen” (auch wenn wir uns nicht gegenübersitzen, sondern nur chatten oder Forennachrichten austauschen).

Wir erwarten von unseren Kommunikationspartner in Twitter, Facebook, Google Groups, Blogosphere das Beste? Wir erwarten, dass Sie uns ernsthaft informieren? Und umgekehrt erwarten wir, dass andere uns ernst nehmen, wenn wir bloggen, twittern, kommentieren? Dann sollten wir als Minimum unsere Identität investieren. Kommen wir aus unserem Anonymitätsschneckenhaus heraus! Profil zeigen, Rückgrat strecken. Ja, auch wenn es mal kontrovers werden sollte.

Das Gute am Internet ist, dass wir vielfach anonym bleiben können. Wir haben die Wahl. Solange wir uns aber reflexartig für die Anonymität entscheiden, nutzen wir sie nicht. Erwachsener Umgang mit dem Internet bedeutet deshalb für mich, dass wir uns immer wieder diese Wahlmöglichkeit bewusst machen – und sie dann auch nutzen, indem wir uns für die Identität entscheiden.

Denn was können wir von einer Kultur der Anonymität, die letztlich für Unsicherheit, Angst und Grenzziehung steht, erwarten? Unsicherheit, Angst und Grenzen gebähren Unsicherheit, Angst und Grenzen - und deren Gegenteile: Sicherheitsdenken, Gewalt, Narzismus. Negative Extreme führen zu negativen Extremen.

Aber was gebiehrt der Wille zur Idendität, der Mut, Vertrauen, Offenherzigkeit, Rückgrat, Wohlwollen, Verletzlichkeit erfodert? Ich bin optimistisch, dass daraus mehr Mut, mehr Vertrauen, größere Offenherzigkeit, mehr Rückgrat, mehr Wohlwollen und mehr Sensibilität entstehen. Und wer wollte das nicht?

Zum Glück können wir alle dafür etwas tun. Mit ein bisschen weniger Anonymität jeden Tag ist ein erster Schritt getan.

Sonntag, 30. Mai 2010

Coding Dojo Muc Retrospektive

Softwareentwicklung mit Energie – die war wieder mal zu sehen beim Coding Dojo in München. Knapp 30 Entwickler waren zusammengekommen, um sich in geselliger Runde in der .NET Programmierung anhand einer Aufgabe zu üben.

Das Coding Dojo Muc hat sein Format und seinen Rhythmus. Im Fokus steht gewöhnlich die testgetriebene Entwicklung (TDD) einer Funktionalität, die sich mit einer Funktion als API ausdrücken lässt  (Beispiele: FizzBuzz, LOC Counter). Das Motto: Alles denkt, einer tippt.

Doch dieses Mal war vieles anders…

Der Ablauf

image Dieses Mal hatte Ilker mich eingeladen, um in das Dojo ein wenig architektonisches Denken hineinzutragen. Das Motto: Alles denkt, keiner tippt ;-) Zumindest nicht die ersten zwei Stunden.

Die Frage nun: hat das geklappt?

Die Aufgabe

Um architektonisches Denken zu üben, musste die Aufgabe natürlich etwas größer ausfallen als Dojo-gewöhnlich. Auf der anderen Seite durfte sie nicht zu groß sein, denn ich wollte ja auch mit Ergebnis mit den Teilnehmern erzielen.

Angemessen erschien mir schließlich eine Spellchecker-Anwendung: Das Programm sollte Texte in verschiedenen Sprachen auf Rechtschreibfehler überprüfen. Wenn man so ein Programm aufsetzen will, sind mehr als eine architektonisch relevante Funktionseinheit nötig; Architektur lohnt sich also.

Der Umfang dieser Aufgabe führte dann gleich zu einer ersten Neuerung bei Dojo: der Anforderungsanalyse. Nicht, dass bisher die Teilnehmer nicht versucht hätten, die Aufgaben zu verstehen. Aber so ein Programm machte eine systematischere Herangehensweise nötig. Die bestand aus zwei Schritten:

1. Featureliste

image Im ersten Schritt haben wir die Anforderungen mündlich ventiliert. Da kamen einige Fragen an den “Kunden” zusammen. Und der Kunde blieb keine Antwort, keinen Wunsch schuldig – allerdings konnte nicht alles wirklich in das Backlog übernommen werden. Wir hatten ja nur einen Abend für die Realisierung zur Verfügung. Nichtsdestotrotz war die Diskussion wichtig, um die Gruppe auf das Thema einzuschwingen.

Was am Ende dann realisierbar erschien, haben wir in eine Featureliste übertragen. Wichtig dabei: Nur aus Kundensicht Relevantes ging darin ein.

2. Ubiquitous Language

image Die Featureliste hat den generellen Scope der Entwicklung abgesteckt. Sie hat eine Grenze gezogen. Wie sah aber das Terrain innerhalb dieser Grenze aus? Wie sollten Problem und Lösung zusammenfinden?

Um einen Übergang in die Lösungswelt zu finden, haben wir im zweiten Schritt für die Features eine Ubiquitous Language mittels einer Concept Map erarbeitet.

Darin wurden die zentralen Begriffe der Domäne nicht nur gelistet, sondern auch qualifiziert in Beziehung gesetzt. Interessante Erkenntnisse wurden dadurch herausgearbeitet:

1. Um sich unzweideutig über die Domäne unterhalten zu können, ist zwischen Prüfworten und Fehlerworten zu unterscheiden. Prüfworte sind die, die auf Korrektheit geprüft werden müssen; Fehlerworte sind die, die als Inkorrekt erkannt wurden.

Fehlt die Unterscheidung, wird die Rede über die Domäne im besten Fall mühsam, im schlimmsten Fall jedoch Missverständlich.

2. Nachdem das Flipchart schon fast voll war, stellte sich die Frage, wer denn überhaupt die Rechtschreibprüfung vornehmen würde. Ihre Elemente (z.B. Prüfworte, Wörterbuch) waren vorhanden, es fehlte aber eine Instanz, die damit umging.

Diese Instanz war in der Diskussion immer implizit vorhanden. Das Team hatte sich oder das ganze Programm dafür in der Verantwortung gesehen. Das ist aber natürlich ungenügend, weil trivial bzw. impraktikabel. Es gilt vielmehr: Zentrale Domänenaufgaben müssen als “Bubbles” in einer Concept Map der Ubiquitous Language auftauchen.

Und so wurde spät aber doch der Prüfer als Vokabel in die Ubiquitous Language aufgenommen. Er prüft, ob ein Wort im Wörterbuch und damit korrekt oder als Fehlerwort zu melden ist.

Die Architektur

Nach soviel Vorgeplänkel waren die Teilnehmer natürlich gespannt darauf, wie denn aus den Anforderungen eine Architektur entstehen würde.

Einige wollten mit Klassendiagrammen anfangen. Andere wollten Schichten übereinander malen. Doch ich hatte mich entschieden, eine Architektur “nach der neuesten Mode” zu versuchen. Event-Based Components (EBC) sollten es sein.

Da keine Zeit für lange Erklärungen des Konzepts sein würde, fand ich mich mit dem EBC-Ansatz recht mutig. Aber es half nichts: Ich wollte einfach nicht mehr überholte Ansätze wie IBC (Injection-Based Components) widerkäuen. Viel simpler wäre es auch nicht nicht geworden. Womöglich sogar schwieriger, weil sich Abstraktionsniveaus mit IBCs schlechter modellieren lassen.

Also bin ich mit einem System-Umwelt-Diagramm eingestiegen und habe dessen System dann als “Hauptplatine” einer EBC-Architektur verfeinert und bin dann nochmal eine Ebene tiefer in den SpellChecker (SC) hinunter gestiegen:

  

In drei Abstraktionsschritten von den Anforderungen zur Architektur. Das war sicherlich überraschend für einige Teilnehmer.

Interessanterweise gab es aber weder dazu noch zu den “komischen Pfeilen”, die ja nicht wie üblich Abhängigkeiten, sondern Nachrichtenflüsse darstellen, grundsätzlich kritische Kommentare. Puh. Das hat mich erleichtert.

Die Kontrakte

Nach soviel Gerede und soviel Malerei stand dann zum Glück aber etwas Code auf dem Programm. Es galt, die die Kontrakte der ermittelten Komponenten festzuzurren. Trotz EBC bleibt Contract-First Design zentral für eine Architektur, die in der Implementierung nicht erodiert.

Für die Kontrakte (und den weiteren Code) hatte ich schon ein Google Project aufgesetzt und mit einer Visual Studio Solution ausgestattet. In die haben wir die Servicekontrakte für die Funktionalität und die Nachrichtenkontrakte “hineingegossen”:

image

Je Komponente (Bauteile und Platinen) ein Interface. Interface-Abhängigkeiten gibt es nicht – das ist ja der Trick bei EBCs.

Hier ein Beispiel für einen Servicekontrakt:

image

Und hier ein Teil des Nachrichtenkontrakts:

image

Die Vokabeln der Ubiquitous Language tauchen konsequenterweise darin wieder auf.

Die Implementation

Nach mehr als 2,5 Stunden war es dann soweit. Die Teilnehmer konnten in die Tasten greifen. Vier kleine Teams gingen daran, die Bauteil-Komponenten zu implementieren. Auch Gastgeber Ilker ließ es sich nicht nehmen, mitzumachen.

Anders als üblich arbeiteten die Teams allerdings still. “Entertainment” hatte es genug gegeben. Jetzt sollte nicht mehr lange (öffentlich) diskutiert werden. Gummi musste auf die Straße.

Um aber nicht nur Gummi bei der Implementation, sondern auch bei der Integration auf die Straße zu bekommen, schlug ich vor, zunächst nur Minimalfunktionalität ohne Tests zu implementieren. Das versetzte mich in die Lage, die Platinen zu prüfen.

Also machte der USB-Stick ein erstes Mal die Runde, um den Teams die Kontrakte zuzuspielen. Und dann ein zweites Mal die Runde zurück zu mir, um ihre rudimentären Implementationen abzuliefern. Darauf aufbauend war meine Aufgabe, die Hauptplatine und die SpellChecker-Platine zu implementieren und alles mit einer kleiner Host-EXE zu starten.

Anschließend gaben die Teilnehmer alles, um ihre Implementationen auszufleischen.

Um 22:17h war es dann soweit: Die Anwendung lief. Texte konnten gegen ein englisches und ein deutsches Wörterbuch geprüft werden. Dabei holperte es noch etwas im UI und die Wörterbücher waren mager – aber “der Kunde” konnte sehen, dass seine Anwendung lief.

Reflexion

Unterm Strich war das Coding Dojo ein Erfolg. Ich bin grundsätzlich zufrieden mit dem Ergebnis. Auch wenn es gedauert hat und wir über Stolpersteine gelaufen sind, gab es am Ende eine lauffähige Anwendung, die von einem verteilten Team realisiert worden ist.

Was ist gut gelaufen?

Ich würde sagen, dass das grundsätzliche Vorgehen gepasst hat. Anforderungen verstehen, Features aufschreiben, Context Map, Kontrakte implementieren: der Fluss war gut. Es gab allerdings Kritik, dass ich dabei zuviel angeleitet hätte. Diese Kritik kann ich gut nachvollziehen, doch ich sehe nicht, wie es hätte anders laufen können.

Wenn bei sonstigen Dojos nach 3 Stunden eine Funktion realisiert ist, dann wäre bei einer ganzen Architektur am Ende kaum etwas Lauffähiges herausgekommen. So schön eine gemeinsame Entwicklung durch die Teilnehmer gewesen wäre – sie hätte nicht mehr als ein gutes Erlebnis produziert. Da bin ich mir sicher. Der Grund: Wenn schon bei der Implementation einer Funktion die Meinung weit auseinandergehen, dann ist das noch mehr der Fall beim Thema Entwurf. Die unterschiedlichen Ansätze, ein leeres Flipchart-Blatt zu füllen, sind dafür der Beweis.

Ziel war nicht, die Gruppe “irgendwie” zu einem Ergebnis kommen zu lassen, sondern einen Weg systematisch zu beschreiten. Das bedarf der Anleitung.

Ich behaupte nicht, dass der Weg, den ich durch den Architekturdschungel “vorgetrampelt” habe, der beste ist. Aber ich behaupte, dass er lohnenswert war, einmal erfahren zu werden. Ich bezweifle, dass viele von den Teilnehmern in ihren Projekten je erlebt haben, wie nach einer sauberen Planung echt parallel und ohne Codekollisionen entwickelt werden kann. Das zu demonstrieren, war mein Anliegen.

Für eine “Diskussionskuschelrunde” mit etwas Architektur hätte Ilker mich auch nicht einladen müssen.

Bei einem nächsten Übungsprojekt wäre es möglich, mehr Arbeit ins Team zu verlagern. Dann würde sich zeigen, ob es etwas vom Vorgehen mitgenommen hat. Bei diesem ersten Mal war jedoch Anleitung in Form eines “fragend entwickelnden Unterrichtsgesprächs” und einem Architekturparadigma nötig.

Gefreut hat mich auch, dass die Concept Map so gut aufgenommen wurde und die Ubiquitous Language sich selbst bei diesem kleinen Projekt als hilfreich herausgestellt hat. Ich hatte mich zunächst gefragt, ob der Aufwand lohnt – doch jetzt bin ich gewiss. Die Concept Map sollte nie fehlen.

Basierend auf der Concept Map habe ich dann auch die Nachrichtenkontrakte gestaltet. Darin kamen im Grunde keine einfachen Typen mehr vor. Statt eines string hat das Frontend ein PrüfTextPaket geschickt. Das hat niemand als Overkill bemängelt. Sehr schön. Ich fühle mich in dem Ansatz bestätigt, zumindest auf Komponentenebene im Grunde keine primitiven Typen mehr einzusetzen. Man vergibt sich mit primitiven Typen nämlich ein Mittel, den Code verständlich zu machen. Aber dazu mehr in einem anderen Blogartikel.

Cool war es zu sehen, wie motiviert alle mitgedacht haben. Soviel Offenheit und Durchhaltevermögen bei Architekturdiskussion! Toll! Und dann auch zu später Stunde noch viel Ehrgeiz bei der Implementation. Wahnsinn!

Was ist weniger gut gelaufen?

Bis zur Implementation ist alles nach Plan gelaufen. Doch dann… Zwar hatte ich den richtigen Gedanken, Integrationsproblemen mit einer “Tracer Bullet” “Kurzimplementation” vorzubeugen. Aber die Ausführung habe ich schlecht vorbereitet.

Das Problem war nicht so sehr, dass Code nur per USB-Stick die Runde machen konnte. Schöner wäre zwar gemeinsamer Zugang mit Mercurial Repository gewesen… doch es ging auch so. Nur langsamer. Viel schlimmer war, dass ich zuwenig über Konventionen gesprochen habe. Denn wo Tools fehlen, müssen Konventionen her.

Die binären Kontrakte haben natürlich die grundsätzliche “Zusammensteckbarkeit” der getrennt entwickelten Komponenten garantiert. Das hat funktioniert. Wir hatten kein wirkliches Integrationsproblem der Assemblies.

Wie der Code aber an mich ausgeliefert wurde, das war ein Problem. Visual Studio kann ja in so unterschiedlicher Weise benutzt werden. Da habe zuwenig erklärt bzw. vorgegeben. Besser wäre es gewesen, ich hätte für jedes Komponenten kurz eine Rumpf-Projektmappe aufgesetzt. Das hätte zwar etwas Zeit gekostet – am Ende wäre dadurch aber so mancher Reibungsverlust vermieden worden.

Lehre #1: Solange Konventionen für die Codeorganisation unbekannt sind oder noch nicht verlässlich eingehalten werden, sollten Komponentenimplementationen als Rahmen vorgegeben werden. Dann gibt es keine Frage, wohin der Output-Path zeigen soll, woher NUnit kommt, wo die Tests zu hinterlegen sind, wie die Bennungen sind, so dass der autom. Buildprozess schlank bleibt.

Kreativität ist ja eine gute Sache – aber sie sollte einen Rahmen haben. Dass z.B. Entwickler die Freiheit haben, Komponenten nicht gem. der Ubiquitous Language zu benennen, ist falsch verstandene Kreativität.

Lehre #2: Die Ubiquitous Language ist heilig. Spaß mit Benennungen mögen Entwickler vielleicht hier und da in ihren Komponenten haben. An den Schnittstellen jedoch und auf Architekturebene ist dafür kein Raum. Verständlichkeit entsteht nur, wenn die gemeinsame Sprache auch wirklich gesprochen wird.

Die Zeit war am Ende knapp. So haben wir alle auf eine erfolgreiche Integration gestarrt. Darunter hat die Codequalität gelitten. TDD im Speziellen war dieses Mal nicht wirklich ein Thema. Und Clean Code Developer Bausteine im Allgemeinen (jenseits der Architektur) auch nicht. So haben sich – wie ein Blick in den Code zeigt – Unschönheiten eingeschlichen. Manche davon waren sogar auf der Projektion zu sehen, weil sie mit dem GUI zu tun hatten.

Lehre #3: Keine Integration ohne Code-Review. Soviel Zeit muss sein. Für den Event war es ok, mal ohne Review zu leben. Es ging ja vordringlich um den Architekturentwurf. Die Implementierung am Ende war Kür und Spaß. Wenn es jedoch etwas ernster wird, dann gibt es kein “Done!” ohne Review.

TDD erleben, ist eine gute Sache. Architekturen mit “parallel rauchenden Köpfen” umsetzen, ist aber auch eine gute Sache. Beides sollten Entwickler öfter tun. Für TDD mag dabei eine Projektion reichen. Beim parallelen Entwickeln ist jedoch erstens ein Coderepository und zweitens ein Zugriff für alle darauf unverzichtbar.

Lehre #4: Das Google Projects Repo war eine gute Sache  - allerdings nur für mich. Das sollte beim nächsten Mal anders sein. Wie könnte das gehen? Mehr Teilnehmer mit einem UMTS-Stick. Oder ein WLAN mit einem Rechner, der ein Repo hostet. Oder ich muss einfach auf dem Zettel haben, dass ich ja einen WLAN UMTS Stick habe, über den ich auch anderen Internetzugang geben kann. Die 4 Rechner hätte der wohl stemmen können.

Schwieriger wird es bei der Versionskontrollsoftware. Viele sind mit SVN oder TFS vertraut. Mit denen würde ich soetwas aber nicht machen wollen. Ein verteiltes Versionskontrollsystem wie Mercurial (oder auch Git) ist viel besser geeignet für verteilte Teams. Da kann jedes Team z.B. in mehreren Commits bis zur Tracer Bullet Implementation kommen, die mit einem Push auf den Server schieben und dann lokal weiterarbeiten, während die Integration sich den Tracer Bullet Stand von allen mit Pull herunterlädt.

Beim nächsten Mal versuche ich es mit TortoiseHg für alle. Das ist einfach zu bedienen. Damit bekommt jeder ein Clone, ein Commit und ein Push hin. Insbesondere ich Mercurial auch sehr tolerant gegenüber Löschungen und Umbenennungen.

Fazit

Trotz einiger Stolpersteine ist das Coding Dojo aus meiner Sicht schön gelaufen. Mein Eindruck war, die Teilnehmer haben etwas mitgenommen. Und sie haben auch Spaß gehabt. Sonst wären sie nicht bis zum bitteren Ende nach knapp 4 Stunden geblieben. Super! Danke an alle fürs Kommen und Mitdenken und Mitmachen.

Und Danke an Ilker für die Einladung. Und Danke an Microsoft für Raum und Annehmlichkeiten. Von mir aus können wir das gern wieder mal machen ;-)

Wem es Spaß gemacht hat und wer tiefer eintauchen möchte in systematischen Softwareentwurf und komponentenorientierte Entwicklung, der kann ja mal überlegen, ob er/sie beim Clean Code Developer Praktikum im Juli mitmachen will. Das bietet 5 Tage Praxis pur – und ist auch noch kostenlos.

Montag, 24. Mai 2010

Überraschende Relevanzwellen [OOP 2010]

image Schon interessant, was zu Wellen im Twitter-Teich führt. Da stolpere ich während der Lektüre von “Projektmanagement” von Thorsten Reichert beim “Zusammenfassungsservice” getAbstract über den Satz

„Viele Unternehmen haben nie Zeit, Dinge richtig zu tun, aber stets Zeit, Dinge mehrmals zu tun.”

Den twittere ich – und erzeuge unerwartet weitreichende Reaktionen. Wow!

Nicht, dass mich die Funktionsweise von Twitter wundert. Nein, mich wundert, was die Gemüter hier in Resonanz versetzt. Ist nicht mein erster Tweet, aber der erste, bei dem ich aus so unterschiedlichen Ecken Retweets sehe. Das Thema schlägt offensichtlich einige Saiten in der Entwicklergemeinde an.

Was sind denn typische Beispiele, wo das DRY-Prinzip vom Management nicht eingehalten wird? Doppeltarbeit statt “einmal richtig tun”? Wer mag Situationen und Fälle benennen? Je mehr Konkretes zusammengetragen wird, desto eher ists möglich, daraus Ursachenmuster abzuleiten und vielleicht doch mal etwas dagegen zu tun. In kleinen Schritten.

Freitag, 21. Mai 2010

Biosphere 2 - Sind Wissensinseln “böse”? [OOP 2010]

image Grad kommt alles zusammen. Ich lese einen Thriller über die Evolution, die auf einer abgeschiedenen Insel zu sehr bösen Kreaturen geführt hat (“Biosphere” von Warren Kaye). Dann höre ich Jens Coldewey auf der SET in Zürich, der darüber spricht, wie böse es für Projekte ausgehen kann, wenn sie sich nicht gegen Inseln wehren, gegen Wissensinseln.

Und dann spreche ich am selben Tag mit Ivan Engler, dem Regisseur des Sci-Fi Spielfilms “Cargo”, der kein Problem mit Wissensinseln zu haben scheint. Im Gegenteil! Sie machen für ihn einen Teil der Effizienz einer Spielfilmproduktion aus.

Ja, was denn nun? Sind (Wissens)inseln “gut” oder “böse”?

Natürlich gibt es da keine pauschale Antwort. Es kommt immer darauf an, auf den Kontext. Deshalb könnte ich das Thema auch zu den Akten legen und einfach sagen: Beim Film sind Wissensinseln ok, bei der Softwareentwicklung hingegen sind sie unakzeptabel. So ist das halt.

Doch ich mag mich noch nicht von den Wissensinseln in der Softwareentwicklung verabschieden. Der Begriff suggeriert Kontraproduktivität – aber den kann ich ja austauschen. Wenn ich stattdessen Spezialisierung sage, dann ist die Konotation schon ganz anders.

Wenn Wissensinseln pauschal “böse” sein sollten, dann ist Spezialisierung pauschal “gut”. Spezialisierung ist schließlich das Ergebnis einer komplizierten Welt, in der nicht mehr jeder alles können kann (oder will). Damit meine ich nun nicht die seelenlose Spezialisierung eines seiner Produkte entfremdeten Fließbandarbeiters. Nein, ich meine eine Spezialisierung wie die einer Maskenbildnerin oder eines Kameramannes oder eines Motoringenieurs oder eines Kommunikationselektronikers.

imageIvan Engler hat keine Angst, dass eine Maskenbildnerin krank wird. Sie ist auch in einer laufenden Filmproduktion in ihrer Funktion ersetzbar. Das mag nervig sein, weil dann Aufwand zu treiben ist für eine Ersatzkraft, doch das ist kein Beinbruch. Und auch VW hat keine Angst, dass ein Motoringenieur ausfällt. Ist der eine krank, übernimmt ein anderer diese hochspezialisierte Aufgabe.

In der Softwareentwicklung allerdings geht die Angst um. Der truck factor scheint quasi immer bei 1 zu sein, sobald sich eine Spezialisierung entwickelt. Das scheint mir der Befindlichkeit in vielen anderen Branchen zu widersprechen. Ich ahne eine Überempfindlichkeit der Softwareentwicklung.

Natürlich kann ich Jens Coldeweys grundsätzlichen Gedanken zustimmen. Es gibt Wissensinseln, die “böse” sind. Und man sollte sie sich auch nicht unbewusst entwickeln lassen. Praktiken wir Reviews und Pair Programming sind probat, um dem entgegen zu wirken. Kein Problem. Ich bin für diese Praktiken und Wissensaustausch im Team.

Und dennoch… Ich bin auch weiterhin der Überzeugung, dass es illusorisch ist anzunehmen, alle Entwickler könnten gleichermaßen Experten in all den Technologien sein, die in einer größeren Anwendung zum Einsatz kommen. Alle sind Silverlight-, WCF-, NHibernate-, WF-, Azure-, InRule-, Oracle-Profis usw.? Das ist Quatsch.

Im Umkehrschluss bedeutet das: Solange der Wille besteht, alle auf dem gleichen Level zu halten, kann dieses Level nur mittelmäßig at best sein.

Soweit die beiden Positionen: Wissensinseln sind “böse”, Spezialisierung ist für Höchstleistung unvermeidbar.

Lässt sich da noch eine Brücke schlagen?

Ja, mir scheint, das geht. Dazu müssen wir aber die Architektur in den Blick nehmen. Auch Jens Coldewey hat Conway´s Law erwähnt: Organisationsstrukturen spiegeln sich in Produktstrukturen wider. Da hake ich mal ein und frage: Wenn denn nun die Organisation dazu führt, dass eben keine Grenzen mehr zu sehen sind – alle sind in ihrem Wissen gleich –, wie soll denn dann die Software aussehen, die so eine Organisation produziert?

Vor dem Hintergrund von Conway´s Law bezweifle ich, dass eine Organisation, in der alle gleich sein sollen, Software herstellen kann, die wahrhaft modular ist, d.h. in der es ganz klare Verantwortlichkeiten gibt. Collective Code Ownership widerspricht insofern sauberer Architektur. Oder anders: Collective Code Ownership tut nichts dafür, dass es zu einer evolvierbaren Architektur durch Grenzziehung in Form von Entkopplung kommt. Wenn die hergestellt werden soll, dann muss ganz bewusst an anderer Stelle Kraft aufgewandt werden. Es gibt aus der Haltung zur Vermeidung von Wissensinseln heraus kein Bedürfnis, in Komponenten (oder gar “Services”) zu denken.

Und jetzt die Umkehrung: Ich setze voraus, dass Architektur modular (komponentenorientiert, serviceorientiert, entkoppelt usw.) sein soll. Punkt. Daran geht für mich nichts vorbei. Soviel ist der Langlebigkeit von Software geschuldet. Es geht ums pure Überleben des Produktes. Das bedeutet – im Sinne von Conway´s Law –, dass die Organisation ebenfalls “irgendwie” modular sein muss. Andernfalls reibt sie sich immer wieder auf, um Strukturen herzustellen, die ihr nicht entsprechen.

Unverbrüchliche Architekturprinzipien sollen also die Organisation treiben!

Daraus folgt für mich, dass z.B. eine grundlegende Trennung von Infrastruktur und Domäne in der Organisation zu sehen sein sollte. Domänenlogik ist etwas ganz anderes als ein Frontend, die Persistenz, die Sicherheit, die Kommunikation usw.

Nun die Masterfrage: In welchem dieser Bereiche sind Wissensinseln ein Problem? Aus meiner Sicht nur bei der Domäne. Software wird sehr sensibel überall dort, wo Domänenlogik im Spiel ist. Und das waren auch die Beispiele, die Jens Coldewey gebracht hat. Domänenlogik ist so sensibel, weil die Experten, die sich mit Softwareentwicklung und (!) Domäne auskennen, so dünn gesät sind.

Das ist übrigens auch der Fall im Filmbusiness. Kameramann und Regisseur und Schauspieler sind nicht so leicht auszutauschen wie eine Maskenbildnerin oder ein Mikrofongalgenhalter. Jene sind mit dem Thema des Films, der Vision eng verwoben; das ist die Domäne einer Produktion. Alles andere ist “Beiwerk” – wichtig, aber ohne Domänenbindung. Maskenbildner etc. haben generische Kompetenz.

Übertragen auf die Softwareentwicklung meine ich daher, dass wir dahin kommen müssen, architektonische Prinzipien ernst zu nehmen. Alles, was mit Infrastruktur zu tun hat, ist domänenunabhängig von der Architektur so zu kapseln, dass es von Experten mit generischer Kompetenz realisiert werden kann. Das mögen – je nach Anwendung – 5%, 10% oder 30% der Code sein. Immer bleibt der Löwenanteil jedoch in der Domäne.

Dennoch ist das ein Gewinn. Es ist nun ganz klar, welche Funktionalität grundsätzlich ohne truck factor realisierbar ist. Denn fällt der Silverlight-Experte aus, wird der nächste angeheuert. Der braucht nur wenig Einarbeitung, weil er keine Ahnung von der Domäne haben muss. Das hochspezialisierte Wissen für optimalen Technologieeinsatz macht kein Wissensinselproblem, weil es breit verfügbar ist (oder zumindest sein könnte). Hier kommt es vor allem auf Erfahrung mit nicht-funktionalen Anforderungen an. Die sind aber quasi in jedem Projekt zu gewinnen.

Es bleibt übrig der Bereich der Domäne. Hier werden die Experten in jedem Team individuell herangezüchtet. Und tatsächlich darf es in Bezug auf die Domäne im Team keine Wissensinseln geben. Domänenwissen ist so breit wie möglich verfügbar zu machen im Team. Denn dort sitzen die quasi einzigen Experten weltweit.

Von Domänenexperten dann aber auch noch Hochleistungen in technologischer Hinsicht zu erwarten, halte ich für eine Überforderung. Natürlich darf, kann, soll jeder Domänenexperte über eine Domänenkenntnis und gute “Programmierkenntnisse” der Plattform hinaus auch noch technologische Spezialgebiete haben. Nur sollte sich ein Projekt darauf nicht verlassen. Und es sollte nicht versuchen, solche technologischen Kompetenzen breit zu streuen. Das funktioniert nicht.

Stattdessen ist es Aufgabe der Architektur, Spezialgebiete (oder Belange) deutlich zu trennen. Die Domäne ist ein solches Spezialgebiet, Persistenz, Frontend etc. sind andere. Und für jedes Spezialgebiet ist Redundanz in Bezug auf die Kompetenzen herzustellen.

Redundanz in der Domäne ist Aufgabe des Projekteams, Redundanz bei Infrastrukturtechnologiekompetenzen ist Aufgabe des Marktes.

Die Filmindustrie hat das hinbekommen. Maskenbildern, Tischler, Bühnenbildner gibt es zuhauf. Bei der Domäne ist es dort aber naturgemäß schwierig. Dafür sind die Projekte auch zu kurz und personengebunden. Deshalb gibt es Versicherungen. Fällt der Hauptdarsteller wegen Unfall aus, kompensiert eine Versicherung den Verlust.

Das könnte auch in der Softwareindustrie funktionieren – wenn denn ein Team nachweisen kann, dass es Wissensinseln vermieden hat, soweit das möglich ist. Aber das ist ein anderes Thema…

An dieser Stelle ich mir wichtig, die Vereinigung aus Spezialisierung und Vermeidung von Wissensinseln herzustellen. Die liegt darin, die architektonisch saubere Separation of Concerns auf das Team zu übertragen. Daraus folgt, dass das Team bzw. Kernteam vor allem die Verantwortung für die Domäne bekommt. Darin sollen dann alle im Team Experten werden; Wissensinseln sind zu vermeiden, weil es außerhalb des Teams kaum Ersatz gibt.

Alle anderen Belange sollte das Kernteam aber nicht auch noch exzellent abdecken. Stattdessen sollte es sie strukturell isolieren und von externen Experten aufsetzen lassen. Im Team muss dann nur noch  Pflegekompetenz existieren. Teammitglieder müssen damit arbeiten und nur noch verstehen, was da gemacht wurde und hier und da korrigierend eingreifen können. Falls tiefere Kenntnis nötig werden sollte, wird wieder ein Experte “eingekauft”.

Damit vermeiden Teams den unmöglichen Spagat zwischen hoher Domänenkompetenz auf der einen Seite und technologischer Exzellenz auf der anderen Seite. Und so entsteht auch ein Markt für technologische “Söldnerexzellenz", die öfter als bisher in unterschiedlichen Projekten eingesetzt wird.


Anders, so glaube ich, kommen wir nicht heraus aus dem ewig mittelmäßig bis schlechten Einsatz von Technologien. Wissensinseln sind also tatsächlich zu vermeiden; das bedeutet jedoch nicht, wir müssen auf eine Spezialisierung verzichten. Spezialisten sind nötig, dürfen nur nicht unersetzbar sein. Deshalb darf der Anspruch nicht sein, alle Spezialisten “selbst herzustellen”. Das ist die Herausforderung.

Natürlich ist es einfacher, “alle sollen möglichst gleich sein” zu denken. Das macht weniger Planungsaufwand. Und das ist auch ok. Nur darf ein Team dann nicht erwarten, wenn es in vielen Disziplinen “gleichgeschaltet ist”, in allen auch Höchstleistungen zu erbringen. Das Prinzip “Wissensinseln vermeiden” sollte daher nicht pauschal allein Anwendung finden.

image

image PS: Ivan Engler wird übrigens auf der prio.conference die Keynote halten. Zumindest wenn er dann nicht gerade in Hollywood weilen muss. Doch ich bin da mal zuversichtlich…