Follow my new blog

Sonntag, 22. August 2010

Nähe oder Distanz, das ist hier die Frage

Sollen Softwareteams in einem Raum arbeiten? Diese Frage haben die Agilisten ganz klar mit Ja beantwortet. Wohin man schaut die Empfehlung, Entwickler möglichst nah zusammen zu setzen. Vor kurzem hat Martin Fowler sich daher bemüßigt gesehen, Empfehlungen für die Gestaltungsfreiräume des Team Room zu geben.

Damit könnte doch das Thema ein für alle Mal zur Ruhe gebettet werden, oder? Setzt die Leute in einen Raum und der Rest wird sich finden. Die breitbandige und informelle Kommunikation in so einem Raum, wo die Entwickler “colocated” sind, kann die Software nur besser machen. Oder?

Ich habe selbst lange Jahre in solch einer Umgebung Software entwickelt und fand das gut. Und neulich beim Clean Code Developer Praktikum haben wir es auch so gehalten: eine Woche lang zu siebt in einem recht kleinen Raum lernen, planen, implementieren. Da entsteh eine sehr produktive und dichte Atmosphäre. Ich bin ganz dafür.

Aber… aber ist das alles? Golo Roden hat Zweifel angemeldet. In zwei Blog Postings hier und hier hat er keck behauptet, solche räumliche Nähe sei überbewertet. Solcher Nonkonformismus hat natürlich Widerspruch hervorgerufen in den Kommentaren und im Blog von Ilker Cetinkaya hier.

Der eine findet, räumliche Nähe wird überschätzt, der andere ist sich sicher, sie werde unterschätzt. Ja, was denn nun?

Ich neige Golos Position zu und habe das in Kommentaren bei ihm auch schon Kund getan. Aus meiner Sicht ist Golo auch nicht apodiktisch. Er sagt nicht, man müssen Entwickler dringend verteilt arbeiten lassen. Anders die Gegenposition der Agilisten, die eher darauf bestehen, dass es ohne räumliche Nähe nicht wirklich gehe.

Um nun die Diskussion zu “depolarisieren”, versuche ich einmal, einen Blick aus größerer Flughöhe.

Der historische Kontext

Ilker et al. berufen sich auf die Agilisten von Cockburn über Fowler bis Beck, die mahnen, man solle dringend Entwickler in einen Raum setzen. Das ist ein argumentum ad verecundiam, also ein Trick der Eristischen Dialektik, um den Gegner einzuschüchtern. Wer will sich schon mit seiner persönlichen, unmaßgeblichen Meinung gegen die Autoritäten wenden? Im Sinne eines rationalen oder gar wissenschaftlichen Dialogs, ist das aber natürlich zuwenig.

Damit will ich nicht sagen, dass Fowler et al. per se unrecht haben. Doch ich möchte ermuntern, auch sie als fehlbar bzw. in ihre Kontexte eingebunden zu sehen. Die Frage muss also erlaubt sein, warum Fowler et al. so sehr die Colocation befürworten?

Ich denke, die Antwort liefert der historische Kontext. Die Agilitätsbewegung ist als Gegenentwurf entstanden. Sie “lehnt sich auf” gegen eine Art der Softwareentwicklung, die auf formale Prozesse, lange Planung, strikte Rollentrennung und starre Kommunikationsmuster gesetzt hat. Früher – und auch heute noch, man soll es ja nicht glauben – wurde Softwareentwicklung als Tätigkeit angesehen, die man wie Bauprojekte generalstabsmäßig planen musste.

Das ist dann umso häufiger schief gegangne, je komplexer Software geworden ist. Und irgendwann haben die Agilisten dann gesagt: Schluss! Es kann nur besser werden, wenn sich etwas grundlegend ändert. Unter anderem war das die Kommunikation im Softwareteam.

Eine Ursache für aus dem Ruder gelaufene Softwareprojekte war für sie die inflexible, reglementierte Kommunikation zwischen Teammitgliedern (und auch mit dem Kunden). Informationsverlust, Wissensinseln, Verzögerungen und mehr können darauf zurückgeführt werden.

Deshalb  haben die Agilisten dem starren Reglement die Colocation entgegen gesetzt. Ein kompliziertes Regelwerk haben sie mit dem “Chaos” der Abwesenheit von Regeln bei breitbandiger ad hoc Kommunikation gekontert.

Eine gute Idee. Und so preisgünstig ;-)

Das Agile Manifest

image Anders als die Befürworter räumlicher Nähe in der aktuellen Diskussion nun meinen, steht die Colocation jedoch nicht im agilen Manifest. Bezug zu einem dessen vier Werte - Individuals and interactions over processes and tools - besteht zwar – doch die Colocation lässt sich da nicht wirklich herauslesen. Sie mag als Interpretation dieses Wertes angesehen werden, doch nicht als Wert selbst.

Die Menschen und ihre (ungeplanten) Interaktionen über Protokolle, Regeln, Werkzeuge zu stellen, ist sogar so allgemein in der Formulierung, dass sowohl räumliche Nähe wie räumliche Distanz von Teammitgliedern dadurch gestützt werden. Denn wenn räumliche Distanz den Menschen mehr dienen sollte als räumliche Nähe, dann wäre das ganz im Sinne dieses Wertes.

Das Offshoring

In den Kommentaren zu Golo findet sich immer wieder als Beweis gegen seine Behauptung, dass Offshoring Projekte so oft fehlgeschlagen seien, dass der Traum von der verteilten Entwicklung als ausgeträumt gelten sollte.

Dagegen ist einzuwenden, dass Golo gar nicht behauptet hat, dass Offshoring “so einfach” funktionieren würde. Er hat auf ein solches Szenario nicht mal angespielt. Offshoring bedeutet zwar verteilte Entwicklung; aber nicht jede verteilte Entwicklung muss mit den besonderen Problemen des Offshoring kämpfen (z.B. Zeitverschiebung, Kulturunterschiede, Problemdomänenferne).

Also: Offshoring-Niederlagen sind kein Argument gegen Golos These.

Die Allaussage

Golos These ist eine Existenzaussage. Er sagt: “Es gibt mindestens ein Projekt, dass verteilt gut bewältigt wird.” Um diese Aussage zu beweisen, muss er nur ein positives Beispiel bringen. Das tut er. Vom Standpunkt der Logik aus ist es unerheblich, dass das ein Beispiel ist, dass nicht für viele Teams gilt. Damit kann eine Diskussion darüber beginnen, wie es sein kann, dass überhaupt so ein Positivbeispiel existiert. Diese Frage aufzuwerfen und nach den Gründen zu suchen, das wäre eine spannende Aufgabe für die Community.

imageDie beharrt hingegen auf der Allaussage der Agilisten: “Alle Projekte, die gut bewältigt werden sollen, müssen colocated organisiert sein.” Leider hilft das aber nichts, denn eine solche Allaussage kann mit einem Gegenbeispiel wie von Golo vorgetragen entkräftet werden. Und wieder sollte das nicht Anlass zu Wehklagen sein, sondern die Frage nach den Gründen aufwerfen.

Wie kann es sein, dass Golo positive Erfahrungen mit verteilten Teams gemacht hat? Wie kann es sein, dass andere Projekte ebenfalls kein Problem haben mit verteilten Teams? Denn dass das so ist, sollte unzweifelhaft sein, auch wenn die Literatur davon nicht überquillt. Open Source Projekte im Allgemeinen und db4o im Speziellen fallen mir da ein. Ich weiß von Carl Rosenberger, dem Chefentwickler von db4o, dass er sehr zufrieden war und ist mit seinem verteilten Team. Schon vor Jahren schwärmte er von den verteilten Pair Programming Sitzungen und pries die Möglichkeit, Entwickler nach Kompetenz rekrutieren zu können, ohne auf ihren Wohnort achten zu müssen.

Die Empirie

Aber die Frage nach den Gründen für Projekterfolge mit Verteilung wird nicht wirklich gestellt. Stattdessen drohen die Colocation-Befürworter mit der Messungskeule. “Hast du denn überhaupt einen Vergleich”, fragt Ilker Golo. Wieso behauptet Golo, dass die verteilte Arbeit mit Peter gut sei, wenn er es in räumlicher Nähe Peter nie probiert habe? Das kann doch wohl nicht sein.

Doch, das kann sein, denn Golo hat nie behauptet, die Verteilung sei besser als die räumliche Nähe. Er hat nur gesagt, sie sei (auch) gut. Er empfinde kein Problem durch die räumliche Distanz. Golo hat keinen Leidensdruck und tritt damit keck als Gegenbeispiel zur Allaussage der Agilisten auf. So simpel ist das. Ich halte das für absolut legitim.

Ilker hingegen suggeriert, dass er gemessen habe, welchen Unterschied es mache. Oder wenn er schon nicht selber gemessen hat, dann doch “die Autoritäten”: “Diese Behauptung ist kein Wunschtraum, sondern messbar & belegbar.”

Ich bezweifle zwar aus mehreren Gründen, dass es solche Messungen im wissenschaftlichen Sinne gibt – aber sei es drum. (Man möge mich bitte auf Quellen, die solche Messungen anstellen hinweisen, falls ich mich irre.)

Selbst wenn es tatsächlich effektiver und effizienter wäre, ein Team in einem Raum unterzubringen als es zu verteilen… – was eigentlich weder Golo noch ich bestreiten – sollte es man es denn deshalb auch immer ganz dringend tun?

Ilker behauptet, ja, das sei dringend wichtig, denn: “Golo überspringt in seiner Betrachtung meines Erachtens die Interdisziplinarität agiler Teams.”

Nähe-Distanz-Wertesystem

Was soll das eigentlich alles mit der Colocation? Bessere Kommunikation sei das Ziel. Ich behaupte aber, das ist nur ein Proxy. Bessere Kommunikation ist kein Selbstzweck. Warum also bessere Kommunikation? Damit die Entwicklung effektiver und effizienter ist. Es sollen bessere Entscheidungen getroffen werden (Effektivität) und die Arbeit soll zügig vonstatten gehen (Effizienz). Der Kunde soll das, was er wirklich haben will, möglichst schnell bekommen. Hört sich auch gut an. Ist jedoch immer noch nicht der ultimative Zweck.

Colocation muss sich – wie alles andere auch – daran messen, wie es Einfluss auf den Profit eines Unternehmens nimmt.

So einfach ist das. Und so komplex.

Denn da ist die Frage angebracht, ob denn Colocation die einzige Möglichkeit ist, den Profit zu steigern. Woraus setzt der sich eigentlich zusammen? Colocation ist der Meinung, der Profit steige, wenn die Software funktional auf den Punkt, evolvierbar, korrekt und produktionseffizient entwickelt wird. Und mehr Effektivität und Effizienz können da nur hilfreich sein, oder?

Hört sich plausibel an. Finde ich aber zu kurz gesprungen. Profit ist die Differenz zwischen Umsatz und Kosten. Woran dreht denn da aber Colocation? Scheinbar sinken durch Colocation nur die Kosten und damit steigt der Profit, weil ja der Umsatz gleich bleibt.

Kurzfristig mag das so sein. Langfristig jedoch wird der Umsatz durch Colocation aber sinken, weil die Effektivitäts- und Effizienzvorteile (zum Teil) an den Kunden weitergegeben werden. Das ist natürlich kein spezieller Nachteil der Colocation. Das passiert vielmehr immer, wenn die Produktivität steigt. Aber Colocation sollte sich eben auch nichts in die Tasche lügen.

Sinken denn aber durch Colocation die Kosten immer? Ich glaube, das ist nicht der Fall. Solange Colocation als Proxy hochgehalten wird, ist das nur nicht zu erkennen. Denn dann ist die Tendenz stark, Probleme durch Colocation zu lösen, statt nach der wahren Ursache zu suchen.

Ich nenne zwei Probleme in bewusst etwas drastischer Sprache, die Colocation verschärft, statt sie zu lösen:

  • Motivation: Colocated Teams sind Zwangsveranstaltungen. Colocation bedeutet gleicher Raum und gleiche Zeit für alle. Das mag irgendwie der Kommunikation förderlich sein – aber fördert das auch die Motivation? Ich habe in Büros mit mehreren Entwicklern gearbeitet und es gehasst. Ein Team Room wie bei Martin Fowler kann die Hölle sein. Alle sind am Telefon, wenn einer es ist. Alle hören die Gespräche der anderen. Alle werden gestört, wenn der Chef reinplatzt. Es sei denn, man schützt sich durch einen Kopfhörer und Musik.
    Ich habe solche Zwangsgemeinschaften immer als Belastung empfunden. Sie haben meine Motivation gedämpft und damit meine Leistung verschlechtert.
    Geschieht das über längere Zeit, steigt die Fluktuation im Team. Und das (!) ist richtig teuer.
  • Kompetenz: Colocated Teams müssen mit den Kompetenzen leben, die sich an einem Ort sammeln lassen. Sind das aber wirklich die besten für das Projekt? Ist ein Team mit 10 schlechten colocated Entwicklern besser als ein Team mit guten 5 Entwicklern in räumlicher Distanz? Das bezweifle ich.
    Wer Colocation als so wichtig ansieht, verschenkt aber die Chance, ein verteiltes Team aus 5 guten Entwicklern zu geringeren Kosten einzusetzen. Er kann das nicht mal denken, denn räumliche Nähe ist ja “messbar & belegbar” besser.

image So einfach ist es also nicht mit dem Profit und der Colocation, würde ich sagen. Colocation “best practice” steht vielmehr der besten Lösung im Weg. Die findet nämlich nur, wer wahre Werte und keine Tools in den Blick nimmt.

Wie ist es also mit dem Profit? Wann ist der hoch? Es tragen u.a. bei zum Profit:

  • Flüssige Kommunikation
  • “Barrierefreiheit”, d.h. weitgehende Abwesenheit von Hierarchien als Kommunikationslenker
  • Hohe Motivation
  • Hohe Kompetenz
  • Hohe Kohärenz, d.h. die Ausrichtung der Kräfte auf ein gemeinsames Ziel

Diese Punkte schlage ich mal als Wertesystem hinter der Frage nach Nähe oder Distanz von Teammitglieder vor. Wem da Werte fehlen, mag sie gern vorschlagen.

Wer also über Colocation oder nicht entscheiden muss, sollte sich überlegen, inwiefern die Entscheidung dafür/dagegen diesen Werten dient.

Der Misserfolg von Offshoring ist mit diesen Werten zum Beispiel recht leicht zu erklären, würde ich sagen: Das typische “leichtfertig” Offshoring-Projekt widerspricht den Werten wie folgt:

  • Keine flüssige Kommunikation aufgrund von verschiedenen Zeitzonen, Kulturen und Sprachen
  • Mangelnde Barrierefreiheit, weil Teams in anderen Kulturen oft nicht genauso egalitär arbeiten wie Europäer
  • Mangelnde Motivation, weil Entwickler beim Auftraggeber oft die letzten Übriggebliebenen sind und Entwickler in der Ferne keinen Bezug zur Problemdomäne haben. Angst, Unsicherheit, zähe Kommunikation und Entfremdung sind schlechte Motivatoren.
  • Mangelnde Kompetenz, weil Entwickler in der Ferne bei allem Marketing der Offshore-Firmen eben doch nicht so kompetent sind, wie man es gern hätte. Davon abgesehen ist die Fluktuation bei Offshoring-Firmen oft sehr hoch, so dass kompetente Kollegen in der Ferne bald aus dem Projekt verschwinden.
  • Geringe Kohärenz, da Kulturunterschiede und Problemdomänenferne es schwer machen, Entwickler in Nah und Fern zu einem Team zusammenzuschweißen, das eine gemeinsame Vision hat.

Und bei der nachgebeteten Colocation ist es nicht viel anders. Auch sie läuft Gefahr, den Werten für mehr Profit zu widersprechen – trotz aller guten Intention. Das Ergebnis sieht dann schnell so aus – ohne, dass es jemand merken würde, weil ja keine Messung/kein Vergleich durchgeführt wird, da man sich ja im Einvernehmen mit den agilen Vordenkern sieht:

  • Flüssige Kommunikation im interdisziplinären Team. Super! Pluspunkt für Colocation.
  • Barrierefreiheit mag gegeben sein. Pluspunkt für Colocation.
  • Aber: Suboptimale Motivation, weil die Autonomie gering ist. Menschen, die viel leisten müssen, wollen viel Freiheit darüber haben, wie sie diese Leistung erbringen. Das gehört zur conditio humana. Je größer die Belastung, desto höher sollte die Autonomie sein, über die “Lösungsverhältnisse” zu bestimmen. Die ist bei Colocation per definitionem aber sehr gering.
  • Und: Suboptimale Kompetenz. Die Führung nimmt die Entwickler, die sich “zusammenpferchen lassen”, statt die besten für den Job zu suchen. Da Personalkosten der größte Kostenfaktor in der Softwareentwicklung ist und es Leistungsunterschied im Zehnerpotenzbereich gibt, sind die Kosten durch suboptimale Kompetenz womöglich ungeahnt groß.
    Wer auf Colocation setzt – aus welchen Gründen auch immer –, der verschenkt schlicht die Chance, bares Geld zu sparen mit Entwicklern, die nicht in der Nähe sind, aber deutlich besser. Und sei es auch nur für “Spezialaufträge”, die ab und an anfallen.
  • Schließlich: Munteres Geplauder im Team Room und am Kicker ist eine schöne Sache – kann allerdings auch darüber hinwegtäuschen, dass Kohärenz fehlt. Wo Kommunikation stetig so unbewusst abläuft wie bei der Colocation fehlt es womöglich an Signalen für Missverständnisse.

Ich will damit nicht sagen, dass Colocation schlecht sei. Mir geht es nur darum, sie nicht zu beweihräuchern. Colocation gehört nicht auf den Altar. Auch sie ist nur ein Tool, das man in bestimmten Situationen einsetzen sollte und in anderen eben nicht.

Darüber hinaus möchte ich aber auch motivieren, räumlich verteilte Teams als Ziel zu haben. Wenn das nicht erreicht werden kann, dann ist Colocation auch ok. Aber ich halte die räumliche Verteilung als default Modus eines Teams für unverzichtbar. Warum? Weil mit Motivation und Kompetenz so wichtig sind.

Wer schonmal gute Entwickler gesucht hat, der weiß, wie schwer es sit, gute Leute zu finden. Warum also die Suche erschweren dadurch, dass man neue Kollegen dazu zwingt, umzuziehen? Aber auch wenn das heute noch nicht genügend Grund für viele Manager sein wird, räumliche Distanz im Team zu akzeptieren… ich denke, in 5 oder 10 Jahren ist das anders. Der Fachkräftemangel wird nur größer, nicht kleiner. Keine Sorge. In 10 Jahren ist ein Team in München froh, einen Entwickler in Schwerin sitzen zu haben; und das Team in Paderborn ist froh über den Kollegen auf Madeira.

Voraussetzung für erfolgreiche Distanz

Entwickeln in räumlicher Distanz mag von mir aus aber sogar die zweitbeste Lösung sein. Am Ende hilft es nichts: Wir kommen nicht drumherum, die Verteilung “ins Programm aufzunehmen”. Entwickler werden sie fordern. Sie ist in vielen Fällen ökonomischer und ökologischer für alle Beteiligten. Und auf lange Sicht lassen sich benötigte Kompetenzen nicht mehr anders finden.

imageEs scheint mir daher angezeigt zu überlegen, was denn dann die Bedingungen für die Möglichkeit gelingender Teamentwicklung in Distanz sein kann. Statt dass wir uns in “Ich hab recht, du hast unrecht”-Zuwürfen ergehen oder im Lamentieren verharren, wie schlecht denn die letzte Erfahrung mit der räumlichen Distanz gewesen sei, sollten wir notwendige Kriterien für erfolgreiche verteilte Projekte finden. Hier meine Vorschläge:

  • Erreichbarkeit: Wer nicht im selben Raum sitzt, soll im selben Space sitzen. Alle Teammitglieder müssen einander erreichen können mit Chat, Email, Telefon, Desktopsharing und evtl auch Video und Wiki. Diese Medien müssen denkbar einfach zu bedienen sein. Diese Medien müssen ständig zur Verfügung stehen.
    Wo ein Teammitglied arbeitet, ist letztlich egal. Aber da wo es arbeitet, muss es erreichbar sein. Je nach Medium sollten die Antwortzeiten unterschiedlich, aber medienbezogen kurz sein.
  • Kommunikationskompetenz: Teammitglieder müssen die Medien nicht nur bedienen können, sondern auch wollen. Manche Menschen tun sich schwer mit der elektronischen Kommunikation – auch in unserem Job. Mit denen ist verteilte Arbeit schwierig. Bei der Besetzung eines verteilten Teams ist daher darauf zu achten, ob die Mitglieder “etwas anfangen können” mit den Medien. Lippenbekenntnisse dazu sorgen schnell für Friktionen. Also Obacht!
  • Verlässlichkeit: Wer technisch und charakterlich kompetent ist in der notwendigen elektronischen Kommunikation, der muss auch dem Erwartungsniveau entsprechen. Er muss verlässlich kommunizieren. Und er muss natürlich auch verlässlich arbeiten außerhalb der Kommunikation. Dazu braucht es sicher einige teamspezifische Regeln, auf jeden Fall aber klare Aufgabenzuordnungen.
  • Vertrauen: Aus Verlässlichkeit speist sich dann ganz natürlich Vertrauen. Wer verlässlich arbeitet, gewinn Vertrauen. Da braucht es gar keine Kickerturniere oder Hochseilgärten. Nichts ist teambildender als Verlässlichkeit in der Kommunikation und Arbeit.
    Aber wo das Management es bezahlen kann, da dürfen natürlich weitere vertrauensbildende Maßnahmen hinzukommen. Vor allem aber sollte alles unternommen werden, Vertrauen nicht zu zerstören durch künstliche Fluktuationen im Team.
  • Moduswechsel: Manches lässt sich dann doch einfacher im persönlichen Gespräch klären. Dafür mag es angezeigt sein, gelegentlich Teile eines Teams oder das ganze Team in einem Raum zu sammeln. Eine gesunde Balance zwischen Nähe und Distanz ist also nötig.
    Wann ist eine Colocation-Phase sinnvoll? Wenn einer der obigen Werte droht kompromittiert zu werden. Colocation könnte nötig sein, um doch mal die Motivation zu heben oder die Kohärenz zu stärken oder Kompetenz aufzubauen oder noch zügiger zu kommunizieren.

Soweit meine Vorschläge für eine Diskussion über Nähe vs Distanz für Entwicklerteams. Lasst uns nicht auf Autoritäten verweisen, lasst uns nicht Überkommenes wiederkäuen, sondern mit freiem Geist darüber nachdenken. Es gibt viel zu gewinnen durch eine Befreiung aus der unverbrüchlichen “best practice” Colocation. Denn ich glaube, es gibt nur wenige Entwickler, die mehr Autonomie bei der Wahl ihrer Arbeitszeit und ihres Arbeitsplatzes ablehnen. Oder?

Also sollten wir mehr persönliche Autonomie in den Blick nehmen und schauen, wie wir das Arbeitsumfeld, d.h. die Teamorganisation darauf ausrichten können. Der Mensch ist das Maß, nicht das Projekt.

Dienstag, 10. August 2010

Leiden am Domänenobjektmodell [OOP 2010]

Ist die Objektorientierung wirklich auf dem richtigen Weg? Grad habe ich wieder so eine Phase, wo ich mich das frage – und immer auf die Antwort komme: Es ist etwas grundsätzlich falsch mit der Objektorientierung.

Halt! Stopp! Ich meine nicht, dass wir keine Klassen mehr haben sollten. Auch die Vererbung muss nicht abgeschafft werden. Ebensowenig Polymorphie oder Interfaces. Alles wunderbar. Diese Mittel zur Formulierung von Code dürfen gern erhalten bleiben. (Und andere dürfen gern hinzu kommen.) Das meine ich auch mit Objektorientierung nicht.

Mit geht es um, tja, die Anwendung dieser Mittel. Ich habe sozusagen ein Problem mit ihrer Auslegung. Die Antwort auf “Was sollen wir mit diesen Mitteln tun?” ist für mich in einigen Bereichen kontraproduktiv.

Gibt es dafür einen Begriff? Muss ich dann fragen “Ist OOAD wirklich auf dem richtigen Weg?” Ja, vielleicht wäre das präziser. Denn die armen Objekte bzw. Klassen können ja nichts dafür, wenn man sie suboptimal einsetzt.

Also: Mein wachsendes Gefühl ist, dass OOAD auf dem falschen Weg ist. Und darüber hinaus scheinen mir sogar bekannte Leute wie Martin Fowler, Eric Evans oder Jimmy Nilsson mal auf der falschen Fährte. Ja, ich erlaube mir heute mal – es regnet in Hamburg – Kritik an Gurus zu üben.

Hier mein Stein des Anstoßes: Domänenobjektmodell. Darüber stolpere ich immer wieder. Ich kann mich nicht mit der Defintion der Gurus anfreunden. Vielleicht spüren Sie, was ich fühle, wenn Sie mal über diese Frage nachdenken:

Wann haben Sie zuletzt einen Kunden gefragt, ob sein Kreditlimit ok sei?

Oder diese Frage:

Wann haben Sie zuletzt einen Auftrag (bzw. den beauftragenden Kunden) gefragt, ob er in dieser Größenordnung ok ist?

imageEs mag meiner begrenzten Erfahrung anzulasten sein, aber ich habe soetwas noch nie einen Kunden (oder Auftrag) gefragt. Lassen Sie es mich wissen, wenn Sie es anders kennen und der Kunde selbst zu seiner Bonität Auskunft geben darf.

Ahnen Sie, worauf ich hinaus will? Mir gehts um die Geschäftslogik oder allgemeiner die Domänenlogik. Wo ist die am besten aufgehohen. Guru Jimmy Nilsson hat in seinem vielbeachteten Buch “Applying Domain-Driven Design and Patterns: With Examples in C# and .NET” dazu diesen Vorschlag gemacht. Es ist seine Skizze eines Domänenobjektmodells für eine kleine Liste von Anforderungen:

image

Sehen Sie dort die Fragen, die ich gestellt habe? Die Klasse Customer hat eine Funktion HasOkCreditLimit() und die Klasse Order eine Funktion IsOkAccordingToSize(). Die entsprechen meinen Fragen, denn Customer und Order repräsentieren Kunde und Auftrag in der realen Welt. Das ist ja gewollt. Das macht OOAD und auch Domänenobjektmodelle aus.

Die Welt könnte also in Ordnung sein. Und für viele Entwickler ist sie so auch in Ordnung. Nur für mich nicht mehr. Ich kann so nicht mehr denken. Echt. Mir wird dann schlecht.

Martin Fowler hingegen findet das wunderbar. Für ihn ist das Ausdruck der “basic idea of object-oriented design; which is to combine data and process together.” (Er sagt das allerdings im Zusammenhang mit dem anämischen Domänenmodell. Aber dazu unten mehr.)

Gegen diese Grundidee des objektorientierten Designs habe ich auch nichts. Ich liebe die Möglichkeit, Daten und Operationen auf ihnen zusammenfassen zu können. Einen Stack so bedienen zu können, ist unschätzbar:

var s = new Stack<int>();
s.Push(99);
s.Push(42);
Assert.AreEqual(2, s.Pop());

Wie umständlich ist dagegen die prozedurale Programmierung wie mit Pascal oder C. Brrrr, da schüttelt es mich auch.

Dass wir Operationen und Daten nun allerdings zu Klassen zusammenfassen können, bedeutet nicht automatisch, dass jede solche Zusammenfassung auch eine gute ist. Wir brauchen also Kriterien, um die Qualität eines Objektmodells beurteilen zu können.

Beim Stack sind wir uns einig. Er nutzt die objektorientierten Mittel angemessen, um einen abstrakten Datentyp zu implementieren. Wie steht es aber mit Jimmy Nilssons Domänenmodell?

Bevor ich näher auf diese Frage eingehe, scheint es mir angezeigt, zum Begriff Domänenobjektmodell etwas zu sagen und es abzugrenzen gegen andere

Objektmodelle

image Das Ergebnis eines objektorientierten Designs ist immer ein Objektmodell. Es beschreibt Klassen mit ihren Zusammenhängen (Beziehungen). Ob das Customer und Address wie bei Jimmy sind oder Dictionary<> und KeyValuePair<> oder Form und TextBox bzw. Control ist, das ist einerlei. Wir können also gar nicht anders, als mit Objektmodellen zu arbeiten.

Domänenobjektmodelle

Wir können auch nicht anders, als Domänen zu modellieren. Wenn der Begriff im Zusammenhang mit Domänenobjektmodell gebraucht wird, dann bezieht er sich allerdings vor allem auf das Geschäftsfeld eines Kunden. In dem kommen Customer und Address vor, nicht aber Form und TextBox.

Wenn wir es genau nehmen, dann stellen allerdings auch Form und TextBox ein Domänenobjektmodell dar. Nur ist es keines aus der Geschäftswelt, sondern eines aus der Infrastrukturwelt. WinForms ist eine Infrastruktur zur Darstellung von Formularen. Also ist es das Domänenobjektmodell für Infrastrukturentwickler.

Was die Domäne ist, ist mithin relativ. In Bezug auf eine ganze Anwendung ist allerdings die Problemdomäne der Anwendung gemeint. Das mag bei Jimmy Nilsson eine Warenwirtschaft sein.

Wenn in der Anwendung weitere Objektmodelle vorkommen, z.B. für die Security oder Caching oder oder oder, dann sind das keine Domänenobjektmodelle im engeren Sinn.

Diese Unterscheidung wird interessant, wenn wir das DataSet in den Blick nehmen. Es ist unzweifelhaft ein Objektmodell bestehend aus DataSet, DataTablee, DataRow, DataColumn usw. Aber es ist kein Domänenobjektmodell, auch – und das ist wichtig! – wenn es benutzt wird, um Domänendaten zu halten.

In einem DataSet können Sie genauso Kunden, Aufträge, Auftragspositionen, Adressen usw. ablegen wie in einem Domänenobjektmodell. Allerdings ist dieses Objektmodell unspezifisch. Es enthält ja keine Klassen, die sich auf die Domäne beziehen. Deshalb nennt man es nicht Domänenobjektmodell. Der Begriff ist Objektmodellen vorbehalten, die eben aus ganz eindeutig domänenbezogenen Klassen bestehen wie Customer, Address, Order usw.

Anämische Domänenobjektmodelle

Den Gurus ist es nun nicht genug, dass Sie zur Verwaltung von Domänendaten ein spezifisches Objektmodell definieren, statt ein generisches zu verwenden. Sie sagen, ein Objektmodell sei nur wahrhaft ein Domänenobjektmodell, wenn die Objekte nicht nur Daten enthalten, sondern auch domänenrelevante Operationen auf diesen Daten anbieten.

Fehlen solche Operationen, dann spricht Martin Fowler von einem anämischen, d.h. blutarmen Domänenobjektmodell. Ich denke, er könnte auch sagen, dann sei das Objektmodell ein reines Datenobjektmodell.

Es gilt also: Domänenobjektmodelle sind auch Datenobjektmodelle; aber Objektmodelle sind nicht unbedingt Domänenobjektmodelle, auch wenn sie aus domänenspezifischen Klassen bestehen.

Kritik des Domänenobjektmodells

Was habe ich nun an dem ganzen auszusetzen? Mein Einwand ist prinzipieller Natur: Aus meiner Sicht widerspricht ein Domänenobjektmodell wie das obige von Jimmy mehreren Prinzipien der Softwareentwicklung.

Widerspruch gegen SRP

Wenn Sie nur einen ganz flüchtigen Blick auf das Domänenobjektmodell werfen, was sehen Sie da? Sie sehen Kästchen und Linien, d.h. Klassen und Beziehungen. Sie sehen also ein Datenmodell.

Datenmodell zu sein bzw. Daten zu halten, ist eine Verantwortlichkeit. Ich hoffe, da stimmen Sie mir zu. Verändert sich die Daten(beziehungen) einer Domäne – z.B. könnte jeder Kunde mehrere Adressen haben –, dann ist die Klasse Customer zu verändern.

Customer muss sich aber auch verändern, wenn sich die Daten nicht verändern. Es könnte z.B. sein, dass nicht nur aufgrund eines Kreditlimits entschieden werden soll, ob ein Kunde ok ist, sondern es soll auch entschieden werden, ob einem Kunden Sonderangebote gemacht werden können. Eine Funktion EligbleForPromotionalOfferings() schiene im Sinne des Nilssonschen Verständnis von Domänenobjektmodellierung doch angezeigt, oder?

Wenn Sie zustimmen, dass stimmen Sie einem Widerspruch gegen das SRP zu. Denn Sonderangebote haben nichts mit den Daten zu tun, die im Domänenobjektmodell zu sehen sind. Sonderangebote wären ein zweiter Grund für Veränderungen an der Klasse Customer. So wie auch schon die Kreditlimitprüfung.

image

Nochmal: Ich bin für Methoden auf Datenklassen. AddOrderLine() ist z.B. eine Methode, die ich absolut passend finde für das Domänenobjektmodell. So wie Push() und Pull() angemessene Methoden für die Problemdomäne Stack sind.

Methoden sollen aber zur Verantwortlichkeit passen. Die ist bei dem Objektmodell eindeutig “Datenhaltung”. Sonst gäbe es keine Properties und keine Beziehungen. Jimmys Objektmodell ist in jedem Fall ein Datenobjektmodell. Wunderbar. Kein Problem. Dann sollte es sich aber auch darauf konzentrieren.

Methoden, die helfen, die Daten konsistent zu halten, sind willkommen. Methoden, die darüber hinaus gehen, halte ich für falsch.

Natürlich soll die Anwendung entscheiden, ob ein Kunde ein ausreichendes Kreditlimit hat. Doch diese Entscheidung sollte nicht dem Kunden in Form eines Customer-Objektes überlassen werden. Das ist nicht intuitiv. Und es widersprich dem SRP. Und es macht auch noch eine “arme Datenklasse” abhängig von einem Service:

image

Mit Verlaub: Das ist Mist. Da mag Martin Fowler noch so lang die Objektorientierung beschwören, die das möglich macht. Nicht alles, was möglich ist, ist halt auch angemessen.

Zwar gehört ein Kreditlimit zum Kunden. Das bedeutet jedoch nicht, dass seine Prüfung aber auch über den Kunden stattfinden muss. Wie gesagt, im realen Leben tut das niemand. Warum sollten wir damit anfangen, wenn wir Software schreiben? Nur weil das überhaupt geht? Quatsch.

Da wird auch nichts einfacher testbar. Und es wird nichts evolvierbarer. Die Prüfung ist eh in einen Service ausgelagert. Ich behaupte, dass im Gegenteil die Evolvierbarkeit sinkt, weil eine “dumme” Datenklassen ohne Not aufgepeppt wird mit Funktionalität, die nichts mit ihrem primären Zweck zu tun hat.

Wenn die Validierungen auf Customer und Order wegfielen, ja, dann würde das Objektmodell weniger Operationen enthalten. Ist es deshalb aber blutarm? Sollte es in dieser Weise als krank diagnostiziert werden? Ich könnte dagegen halten und es “fokussiert”.

Aus meiner Sicht bleibt Martin Fowler in seiner Kritik anämischer Domänenmodelle den Beweis eines unausweichlichen pathologischen Effekts auch schuldig. Da schwadroniert er vom Widerspruch zu Grundlagen der Objektorientierung. Da beschwört er eine faulige Nähe zur prozeduralen Programmierung. Da warnt er vor Aufwand mit geringem Nutzen für die Persistenz.

Beweise sind das aus meiner Sicht aber nicht. Vor allem fehlt mir einer der Hauptnutzen eins Domänenobjektmodells: die Repräsentation der Domäne im Code. Ein Domänenobjektmodell macht auch ohne großartige Domänenfunktionen Code lesbarer und sicherer als ein generisches Objektmodell wie das DataSet.

Außerdem ist auch ein anämisches Domänenobjektmodell mit heutigen Tools recht schnell aufgesetzt und persistiert.

Und schließlich: Sein Argument, es würde Domänenlogik ganz unsinnig in prozedurale Services rausgezogen, führt er selbst ad absurdum, indem er lobende Worte für Jimmys Buch findet, das trotz aller Domänenobjektmodellhaftigkeit eben genau das tut. Denn was ist denn sein CreditService, den Customer referenziert? Selbst Martin Fowler würde doch nicht behaupten wollen, dass der Code dieses Service in der Klasse Customer stehen sollte, oder?

Widerspruch gegen Law of Demeter

Nach dem Law of Demeter (LoD) sind “train wrecks” ein code smell, z.B.

someOrder.OrderLines[n].Product.Description

Wieso haben wir dann aber überhaupt Mittel in der Objektorientierung, um solche offensichtlichen Stinkereien zu produzieren? Properties laden dazu ja ein.

Das Law of Demeter ist halt differenziert zu verstehen. Was ist sein Zweck? Es will exzessive Abhängigkeiten vermeiden. Ein sehr löbliches Ansinnen. Aber eines, das im Widerspruch dazu steht, was Datenobjektmodelle wollen. Die wollen nämlich gerade Abhängigkeiten repräsentieren. Mithin gilt das Law of Demeter nicht für Datenmodelle.

Ist ein Objektmodell ein Datenobjektmodell, dann sind “train wrecks” ok, ja geradezu unvermeidbar.

Indem nun jedoch Jimmy Nilsson sich weigert, ein reines Datenobjektmodell zu definieren, widerspricht er dem LoD. Er entscheidet sich nicht für ein Datenobjektmodell, weil er ja der Domänenobjektmodelllehre folgen will – aber er legt trotzdem die Beziehungen offen, so dass man “train wrecks” erzeugen kann.

Das bedeutet im Umkehrschluss: Domänenobjektmodelle dürfen eben keine Datenobjektmodelle sein. Wo “hochwertige” Domänenlogik definiert ist, dürfen keine Datenbeziehungen herauslecken.

Zwischenstand

Die in prominenten Werken wie

image image

beschrieben Domänenobjektmodelle sehen plausibel aus. In ihnen scheint sich Objektorientierung pur und zum Besten zu manifestieren.

Leider hilft das nichts, denn es hilft mir nicht. Objektorientierung ist kein Selbstzweck. Wenn mit eine bestimmte Nutzung objektorientierter Mittel es nicht leicht macht, meine Software zu entwerfen und zu pflegen, dann besteht nicht nur die Möglichkeit, dass ich es immer noch falsch mache (Inkompetenz), sondern auch die Möglichkeit, dass die Empfehlungen suboptimal sind.

Nach jahrelangem Bemühen bin ich nun an einem Punkt, wo ich meine, dass ich so fürchterlich inkompetent nicht sein kann. Also erlaube ich mir, die andere Alternative in Betracht zu ziehen. Vielleicht ist das mit den Domänenobjektmodellen doch nicht so pauschal eine gute Idee wie Fowler, Nilsson & Co es darstellen?

Ich habe viel übrig für Objektorientierung. Abstrakte Datentypen mit ihren Mitteln zu bauen, ist wunderbar. Die sind jedoch in sich abgeschlossen. Ihre Operationen beziehen sich auf sich selbst. Das wünsche ich auch jedem Datenobjektmodell. Es soll nicht blutleer sein. Operationen, die die Konsistenz von Daten im Sinne der expliziten Beziehungen sicherstellen, dürfen, nein, sollen auf den Klassen des Datenobjektmodells definiert sein.

imageJimmys Domänenobjektmodell geht darüber jedoch weit hinaus. Da beginnt dann mein Schmerz. Aber was tun? Eine Idee habe ich. Mal schauen, dass ich die in einem weiteren Blogartikel darstelle. Meine Interpretation von Domain Driven Design (DDD) ist anders. Eric Evans hält sich ja auch bedeckt, was die Übersetzung seiner Modelle in Klassen angeht. Da ist Interpretationsspielraum. Jimmy hat Evans´ Metamodell in einer Weise interpretiert. Ich tue es in einer anderen Weise.

Dienstag, 3. August 2010

Was treibt uns an?

Mit der Softwarequalität steht´s nicht zum Besten. Könnte das aber nicht nur an den ewig suboptimalen Programmiersprachen, unzulänglichen Werkzeugen, imperfekten Konzepten und inkompetenten Entwicklern liegen, sondern womöglich daran, dass viele die Arbeit als nicht so befriedigend empfinden, wie sie sein sollte, um wirklich gute Ergebnisse abzuliefern?

imageIn einem Artikel meiner Sandbox-Reihe in der dotnetpro habe ich mir darüber einmal Gedanken gemacht. Den gibt es heute exklusiv vorab zu lesen. Auch für die, die keine dotnetpro-Abonnenten sind.

Ich glaube, dass wir einiges an unseren Produkten und auch in unserem (Arbeits)Leben verbessern können, wenn wir uns Gedanken machen, warum wir eigentlich arbeiten. Was treibt uns da wirklich an?

Wen meine Überlegungen dazu interessieren, der kann ja mal in meinen Artikel reinschauen. Und dann diskutieren wir hier. Würde mich freuen.

Den Artikel zu lesen, kostet nichts – außer eine Tweet. Wie ist das? Ein fairer Preis?

Sonntag, 18. Juli 2010

Quando, quando, quando? Vom Zeitpunkt für automatisierte Tests

Grad lese ich Thomas Bandts öffentliche Gedanken zur Frage, ob und wann TDD denn sinnvoll sei. Die treibt ja viele um. Immer wieder. Und so glaube ich, dass irgendwas an ihr noch nicht stimmt. Irgendwie ist die Perspektive falsch.

Wenn es in einer Partnerschaft krieselt und sie klagt “Immer bringst du den Müll nicht runter!” worauf er fragt, “Wann soll ich den Müll denn runterbringen? Wenn die Tüte richtig voll ist oder früher?”, dann ist das auch die falsche Frage. Sie nimmt nur ein Symptom in den Blick, nicht das Wurzelproblem. Das könnte nämlich darin bestehen, dass sie sich in ihrem Bemühen um eine schöne Wohnung nicht anerkannt fühlt.

Also: Was könnte falsch an der Perspektive hinter der ewigen Frage nach “Soll ich eigentlich immer (!) mit TDD arbeiten?” falsch sein?

Widerstand gegen die Silberkugel

Ich glaube, die Frage ist Ausdruck einer großen Skepsis. Entwickler sind zurecht skeptisch, dass es eine Praktik wie TDD gibt, die “alles” besser machen könnte. Aber insofern ist die Frage auch Ausdruck eines Missverständnisses. TDD will gar nicht “alles” besser machen. Nur manches.

Die Frage ist damit falsch gestellt, weil sie suggeriert, TDD würde einen Absolutheitsanspruch formulieren. TDD ist “nur” eine Methode, um ein Ziel zu erreichen: korrekten Code mit einer hohen Testabdeckung und gut evolvierbarem Design.

Evolvierbares Design ist die Voraussetzung für langlebige Software.

Hohe Testabdeckung ist eine gute Grundlage dafür, dass Bugs sich nicht verkriechen können in ungetesteten Code. Verkriechen ist aber nur möglich bei Code, der entweder nicht trivial ist oder der nur sehr selten ausgeführt wird. Ist beides nicht gegeben, dann stolpert man bei Integrations- oder Akzeptanztests sehr schnell über den Bug. Und sehr wahrscheinlich ist dann auch sofort klar, wo und warum es geknallt hat.

Beispiele für solchen Code sind Getter/Setter oder Mappings. Die funktionieren entweder -- oder eben nicht.

Wer also Code schreibt, der nicht evolvierbar sein muss oder der weitgehend aus Trivialitäten besteht… der muss TDD nicht einsetzen. Forms-over-Data Szenarien fallen für mich z.B. in diesen Bereich.

Widerstand gegen Veränderung

Entwickler widersetzen sich aber nicht nur Silberkugeln, sondern auch Veränderungsansinnen. Und nichts anderes ist TDD. TDD will den Entwickler verändern. Und zwar sehr tiefgreifend. Alte Gewohnheiten sollen abgestreift werden, ja geradezu Gewohnheitsrecht soll aufgegeben werden.

Bisher war “Softwareentwicklung ist Spaß beim codieren von spannender Funktionalität codieren”, nun soll aber sein “Softwareentwicklung ist nicht nur spannende Funktionalität programmieren, sondern auch langweilige Tests, die das eigene Werk in Frage stellen und langsam machen.” Das ist ein tiefgehender Eingriff. Denn erstens wird dem Entwickler der Spaß an der Programmierung vergellt. Zweitens setzt er ihn noch mehr unter Druck, denn der Chef ist gewohnt, schnell rausgeschossene Funktionalität zu sehen. Mit TDD kann die Feuerfrequenz nur sinken.

Das Recht auf Spaß und Spannung, das Recht auf Entlastung durch Fokus auf Funktionalität soll mit TDD beschnitten werden. TDD bedeutet also einige Veränderung. Und die macht nie Freude. Deshalb setzt sich der Entwickler zur Wehr wie jeder es tut, dem ungewollte Veränderungen ins Haus stehen.

Doch auch hier lauern Missverständnisse. Erstens ist das Schreiben von Tests nicht pure Langeweile. Im Gegenteil! Es kann sogar eine größere intellektuelle Herausforderung darstellen, als Funktionalität zu programmieren. Code testbar machen, ist oft keine Kleinigkeit. Wer also was auf sich hält als Entwickler, der sollte mehr Tests schreiben.

Zweitens verlängern automatisierte Tests die Softwareentwicklung nicht per se. Es kommt vielmehr auf die Perspektive an. Wer nur auf diesen Moment schaut, der sieht natürlich die Zeit dahin rinnen, wenn er nicht nur 3 Zeilen Funktionalität, sondern auch 6 Zeilen Test schreiben muss.

Diese Perspektive ist jedoch kurzsichtig. Sie folgt dem Gedanken eines Rechnungswesens, das immer nur auf die Kosten stiert. Und dahinter steht die Vorstellung, dass ein optimales Gesamtergebnis erzielt wird, wenn jeder Einzelschritt optimal ist.

Das stimmt aber nicht. So funktioniert die Welt nicht. Oder nur in besonderen Situationen tut sie das.

Diese Perspektive verliert schlicht den Gesamtprozess und auch die Zeit im Großen aus dem Blick. Denn nicht nur die Zeit eines Entwicklers ist wichtig. Viel wichtiger ist der Gesamtaufwand an Zeit über längere Zeit.

Wer nur auf hier und jetzt schaut, kann das nicht sehen. Wirtschaftlichkeit bedeutet nicht, jetzt etwas in einer bestimmten kurzen Zeit zu produzieren, sondern langfristig zu überleben.

Tests heute beim Entwickeln dienen nun dieser Wirtschaftlichkeit. Denn sie verringern die Wahrscheinlichkeit, dass Bugs in der Zukunft die Arbeit behindern. Das tun sie nämlich in zweierlei Hinsicht: Erstens halten Sie davon ab, Funktionalität zu produzieren. Zweitens stören Sie den Fokus des Teams. Beides ist kontraproduktiv.

Auf längere Zeit gesehen, sind automatisierte Tests (bzw. TDD) also wirtschaftlicher. Das ergibt sich auch ganz leicht aus einer simplen Rechnung: Nach jeder Veränderungen am Code müssen alle relevanten Tests wiederholt werden, um sicherzustellen, dass nichts kaputtgegangen ist. Das sind umso mehr, je verquarzter Code ist. Damit ist es umso teurer, je mehr manuelle Tests gefahren werden müssen. Die Ausführung von automatischen Tests hingegen kostet nichts.

Umgekehrt bedeutet das allerdings: Wer sich um längere Zeit nicht scheren muss, der kann auf autom. Tests/TDD verzichten.

Allerdings würde ich immer hinterfragen wollen, ob einer, der aus dem Grunde TDD von der Hand weist, wirklich eine Ahnung davon hat, was in der Zukunft mit seiner Software passiert. Aus so mancher Software, die für einmaligen Gebrauch gedacht war, ist ja schon eine unternehmenskritische Anwendung geworden.

Niemand will ja das Brownfield. Das ist, glaube ich, unzweifelhaft. Aber warum ist es dann überall? Warum gibt es solche Massen an unwartbarem Code, dessen Korrektheit niemand beweisen kann. Nach jeder Änderungen hoffen und bangen alle nur, dass nichts vormals Korrektes nun kaputt ist.

Widerstand gegen Angriffspunkt

Nicht nur gegen Veränderungen und Silberkugeln wehren sich Entwickler mit der kritischen Frage, ob TDD denn wirklich sein müsse. Sie wehren sich auch dagegen, angreifbar zu werden. Sie wehren sich dagegen, sich erklären zu müssen.

imageWer bisher keine Tests geschrieben hat und nun damit anfängt und schon deshalb, weil er ein Anfänger ist, länger braucht, der sieht sich Stirnrunzeln ausgesetzt. “Muss das mit diesen neumodischen Tests denn sein?” fragt der Chef?

Darauf dann selbstbewusst mit “Ja, das muss sein” zu reagieren, fällt vielen schwer. Sie können sich auf keinen Berufsethos zurückziehen. Sie haben keine Branche hinter sich, in der es alle so tun. Die Ausbildung steht nicht hinter ihnen. Sie sind auf sich gestellt. Ihre Überzeugung steht gegen die Weisungsmacht des Chefs, der von Softwareentwicklung allzuoft nichts versteht und Experimente so gar nicht mag.

Das sind verständlicherweise unschöne Aussichten. Also ist der Reflex die sehr skeptische Frage, ob denn TDD überhaupt etwas brächte.

Fazit

Natürlich hat die Frage, ob und wann TDD denn sein müsse, eine Sachebene. Die Frage ist sachlich berechtigt. Und die Antwort sollte nicht pauschal sein, sondern differenziert. Entscheidungskriterien sind gefragt.

Lebensdauer und Kompliziertheit und Art von Code gehören für mich als Aspekte in die Antwort. Nicht für jeden Bau sind statische Berechnungen im selben Umfang nötig. Nicht für jedes Unternehmen ist ein Rechnungswesen im selben Umfang nötig. Deshalb: Nicht für jedes Softwareprojekt sind automatisierte Tests im selben Umfang nötig.

Kontraproduktiv im Sinne einer differenzierten Antwort finde ich es jedoch, sich über die wahren Beweggründe der Frage nicht klar zu sein. Jeder, der so fragt, prüfe sich da also am besten erstmal selbst. Das ist schwierig – aber wenn man es nicht tut, läuft man immer wieder gegen Wände aus Missverständnissen. Und nichts ist lähmender als Konflikte, deren Ursachen man nicht kennt bzw. verschleiert.

Ebenfalls kontraproduktiv empfinde ich es, ganz grundsätzlich den Nutzen von automatisierten Tests zu hinterfragen. Das widerspricht aus meiner Sicht der Zivilisationsgeschichte der letzten 1000 Jahre, die ganz klar zeigt, dass Automatisierung wo immer es geht, Vorteile bringt. Menschen ersparen sich langweilige, fehlerträchtige, gefährliche Arbeit, indem sie automatisieren. Warum sollte das nicht auch für Tests gelten?

Nein, da ist für mich die Grenze erreicht. Wer Testautomatisierung pauschal in Frage stellt, der widerspricht grundlegenden Prinzipien. Und dazu sagten schon die Lateiner: contra principia negantem disputari non potest (Mit dem, der die gemeinsamen Prinzipien leugnet, ist nicht diskutieren.) Den Diskussionsaufwand spare ich mir dann.

Also: Wenn die Frage sachlich gestellt ist und nicht als Rationalisierung, d.h. als Abwehrmaßnahme dient, wenn ernstlicher Erkenntnisgewinn gewollt ist und die grundsätzliche Möglichkeit der Beiträge von automatisierten Tests/TDD zu Produktionseffizienz, Korrektheit und Evolvierbarkeit anerkannt sind… dann lässt sich auch eine Antwort für jedes Projekt finden. Und die lautet nicht immer “Ja! TDD musst du machen auf Teufel komm raus.”

Jedes Projekt hat sein Mix an automatisierten Tests wie Akzeptanztests, Integrationstests, Performancetests, Lasttests, Unit Tests. Und ob die nach TDD oder anders geschrieben sind… das ist durchaus auch immer wieder anders.

Nur eines halte ich für unverbrüchlich: Den Willen zur Automatisierung. Denn alles, was automatisiert getan wird, das ist quasi kostenlos wiederholbar, das ist nachvollziehbar, das ist von keinem bestimmten Teammitglied abhängig, das ist schneller als von Hand getan. Wer wollte diese Vorteile der Automatisierung ausschlagen?

Die Rückkehr der Täter – EBC-Prozesse übersetzen

Einen Moment das objektorientierte Denken auszusetzen, darum hatte ich Sie in ???meinem Blogartikel über die nächste Entwicklungsstufe von EBCs gebeten. Ich weiß, das ist eine große Bitte gewesen. Deshalb möchte ich Ihr Wohlwollen auch nicht länger strapazieren. Kommen Sie zurück in die Welt der Objekte bzw. Klassen und der Komponenten.

In diesem Artikel möchte ich Ihnen zeigen, wo Klassen ihren Platz auch in einer Welt der Featureprozesse haben, d.h. der Prozesse, die ein Anwendungsfeature realisieren.

Übersetzung in Klassen

Bisher habe ich vorgeschlagen, Anforderungen in Features zu zerlegen, von denen jedes durch einen Prozess “hergestellt” wird. Hier nochmal das Feature “Funktion plotten” als EBC-Prozess:

image

Initiator des Prozesses ist das GUI, Empfänger für das Resultat ebenfalls. Ich denke, soweit bereitet Ihnen das Verständnis keine Probleme.

Allerdings werden Sie sich fragen, wie das Prozessdiagramm in Code übersetzt werden kann. Abgesehen vom GUI, d.h. einer Formularklasse, ist darin ja noch keine Klasse zu sehen. Beschrieben sind lediglich Taten, aber keine Täter. Das ist natürlich gewollt, denn sonst wäre das Diagramm kein Modell. Modelle sollten nämlich nicht mit dem Vokabular der Implementation beschrieben werden.

Also: Wo sind die Klassen?

Aus meiner Sicht ist der Default, jede Tat, jede Aktivität, jeden Prozesschritt in eine Klasse zu übersetzen. Das sähe dann so aus:

image

Damit machen Sie nichts falsch. Das entspräche den bisherigen Übersetzungsregeln für EBC-Bauteile und –Platinen. Bitte beachten Sie jedoch: Auch wenn Sie Aktionen in Klassen übersetzen, werden daraus nicht gleich Täter. Sie würden den Prozessschritt “In Symbole zerlegen” nicht als Klasse Scanner implementieren. Wenn Klassen Aktivitäten repräsentieren, dann sollten sie auch so benannt sein:

class Funktion_in_Symbole_zerlegen
{
    public void In_Process(string funktion)
    {…}

    public event Action<Symbol[]> Out_Symbole;
}

Da nun jedoch die “Kästchen” in den EBC-Diagrammen nicht mehr durch Substantive beschrieben werden, da sie keine Täter, sondenr Taten sind, liegt es für mich sehr viel näher als bisher, allemal die atomaren Aktionen nicht in Klassen, sondern in Methoden zu übersetzen. Klassen weisen dann mehrere solcher Methoden-Taten zu einem Täter zusammen:

image

Eine Klasse könnte dann so aussehen:

class Funktionsanalyse
{
    public Symbol[] In_Symbole_zerlegen(string funktion)
    {…}

    public AST Auf_syntaktische_Korrektheit_prüfen(Symbol[] symbole)
    {…}
}

Wann sollten Prozessschritte Methoden werden, wann Klassen? Hier sind verschiedene Prinzipien in Anschlag zu bringen. Wenn die Methoden sehr überschaubar sind und nicht in viele weitere zerfallen, um dem SLA-Prinzip zu dienen, dann würde ich dazu raten. (Im vorliegenden Beispiel des Funktionsplotters ist das eher nicht der Fall. Gerade ein Scanner kann schon sehr umfangreich werden.) Auch könnte für Methoden sprechen, dass sie gemeinsamen Zustand haben – wobei anzumerken ist, dass der eine Abhängigkeit darstellen würde, die man aus einem Prozessdiagramm nicht herauslesen kann. Das wäre der Evolvierbarkeit abträglich.

Oder lassen Sie es mich anders sagen: Methoden sind absolut ok, um Ihnen die Arbeit etwas leichter zu machen, wenn Ihnen kein Prinzip entgegensteht. Vor allem könnten das SRP und SoC sein.

Täter durch Abstraktion

Vergleichen Sie nun die Klassennamen der Übersetzung in Klassen und der Übersetzung in Methoden. Sehen Sie den Unterschied? Bei einer Übersetzung in Klassen beschreibt der Klassenname die Aktivität, die Klasse ist eine Tat. Anders ist es bei der Klassen, die Prozessschritte als Methoden implementiert. Ihr Name beschreibt einen Verantwortlichen, sie ist ein Täter.

Dieser Wechsel in der Semantik ist nötig, da ein Menge von Methoden, d.h. Taten nicht ohne weiteres zusammen eine “Großtat” ergeben. Wer sie zusammenfasst, ist also keine Tat, sondern tut viele Taten und ist somit ein Täter. Ein Substantiv als Name ist angezeigt.

Mit der reinen Objektorientierung können Sie quasi nicht anders: Sie suchen sofort nach Tätern. Sie hätten sich gefragt, welche Verantwortlichen an einem Funktionsplot beteiligt sein könnten. Und da wären Ihnen sofort Scanner, Parser, Codegenerator, Plotter usw. Dann hätten Sie die in irgendeiner Form in Abhängigkeiten zueinander gesetzt (Klassendiagramm, Schichtendiagramm o.ä.).

Mit EBC-Prozessdiagrammen jedoch müssen Sie sich nicht so anstrengen. Sie listen einfach die Tätigkeiten, die ein Feature ausmachen. Das ist ein viel kleinerer Schritt, wenn Sie von den Anforderungen ausgehen.

Und erst danach schauen Sie, ob Sie mehrere Prozessschritte demselben Täter zuordnen können. EBC-Prozessdiagramme sind also ein Synthesewerkzeug. Statt top-down vorzugehen und mit Abstraktionen zu beginnen, die Sie nicht kennen, starten Sie bottom-up. Sie beginnen bei relativ leicht identifizierbaren Aktivitäten und abstrahieren dann von ihnen. So kommen Sie zu Klassen, wenn Ihre Prozessschritte Methoden sind. Aber was, wenn sich für eine Übersetzung von Aktivitäten in Klassen entschieden haben?

Komponenten als Großtäter

Klassen enthalten Methoden. Assemblies enthalten Klassen. Für alle Klassen – egal, ob die Taten oder Täter darstellen – müssen Sie sich entscheiden, wie Sie sie zu Assemblies zusammenfassen.

Bei EBC-Schaltplänen habe ich noch die Auffassung vertreten, jede EBC-Komponente (allemal Bauteile) wirklich als Komponente zu implementieren. Für EBC-Prozessdiagramme sehe ich das nun anders.

Wie Sie Taten als Klassen oder Taten als Methoden in Täter-Klassen zu Komponenten zusammenfassen, ist völlig getrennt vom Prozessdiagramm zu entscheiden. Eine automatische Übersetzung von Diagrammelementen in Komponenten (d.h. Assemblies mit separatem Kontrakt) gibt es für mich nicht mehr.

Komponenten sind insofern darauf reduziert, Einheiten für die starke Entkopplung und eine isolierte bzw. parallele Entwicklung zu sein. Fragen Sie sich bei der Entscheidung für die Zusammenfassung z.B.

  • …ob Klassen eine genügend hohe Kohäsion haben, um im selben VS-Projekt realisiert zu werden? Das könnte z.B. der Fall sein, wenn sie in Bezug auf die Problemdomäne eng beieinander liegen oder auf derselben Technologie basieren.
  • …ob die Implementierung einer Klasse risikoreich ist und daher parallel zur Implementation anderer stattfinden sollte, um deren Umsetzung nicht zu behindern?
  • …ob für die Implementation von Klassen besondere Kompetenz nötig ist und sie daher parallel zu anderen stattfinden sollte?

Hohe Kohäsion sollten Sie durch Zusammenfassung in einem VS-Projekt, d.h. in einer Komponente ausdrücken. Hohes Risiko, spezielle Kompetenz oder auch lange Entwicklungsdauer sollten hingegen eher dazu führen, dass Klassen getrennt von anderen in Komponenten realisiert werden.

Dasselbe gilt auch für die Art ihrer Herstellung. So kann es sein, dass zusammengesetzte Aktivitäten (die früheren Platinen) generiert werden. Damit kommen sie ohnehin in anderen Assemblies zu liegen als manuell zu entwickelnde atomare Aktivitäten. Ich fasse deshalb alle zusammengesetzten Aktivitäten (z.B. “Funktion berechnen”) in einem von allen anderen VS-Projekten getrennten zusammen.

Unterm Strich sind Komponenten heute für mich “nur noch” Container, die Sie nach sehr pragmatischen Gesichtspunkten füllen. Sie sollen ihre Inhalte von anderen physisch und inhaltlich isolieren. Nur weil Sie in einem Diagramm aber irgendwelche Kästchen zeichnen, entstehen noch keine Komponenten. Ihre Bildung ist orthogonal zur Formulierung eines Lösungsmodells. Machen Sie Komponenten in EBC-Prozessdiagrammen ruhig kenntlich. Aber sehen Sie sie als das, was sie sind: als Einheiten der Codeorganisation und nicht als Bestandteile des Lösungsraumes.

image

Diese Auffassung unterscheidet sich deutlich von meiner bisherigen. Und sie widerspricht eigentlich auch der Bezeichnung Event-Based Components, denn die primären Elemente von Prozessdiagrammen sind keine Komponenten mehr. Es tut mir leid, wenn ich Sie damit verwirren sollte. Aber es hilft nichts: Es fühlt sich besser an, Komponenten in dieser Weise zu benutzen. Und ich glaube auch, dass es leichter zu erklären ist.

Komponenten sind Strukturierungsmittel für Code zur Entwicklungszeit. Sie sind damit genausowenig funktional lösungsregelavant wie VS Solutions oder Klassendateien. Fassen Sie in ihnen Klassen für Täter und Taten schlicht zu “Großtätern” zusammen, wie es für die Evolvierbarkeit und Produktionseffizienz günstig ist.

Komponenten entstehen mithin wie Täter-Klassen durch Abstraktion. Versuchen Sie nicht, sie durch Analyse der Anforderungen zu entdecken. Lassen Sie sie entstehen durch Zusammenlegung von Klassen, die nach einem Gesichtspunkt wie Risiko oder Entwicklungsdauer oder Technologie zusammen gehören.

Komponenten elastisch

Das bedeutet aus meiner Sicht sogar im Umkehrschluss, dass Komponenten in ihrem Inhalt variabel sein können. Was heute in bestimmter Hinsicht günstig zu einer Komponente zusammengefasst werden sollte, kann morgen anders zusammengefasst werden. Hier ist die Softwareentwicklung im Vorteil gegenüber der Elektrotechnik oder dem Maschinenbau. Deren Komponenten sind fix. Wir können Komponenten jedoch “atmen” lassen. Wir können sie elastisch gestalten.

Einzig wichtig für eine Software sind die Aktivitäten. Weder ist wichtig, ob sie als Klassen oder Methoden realisiert werden. Noch ist wichtig, welche Methoden-Prozessschritte in welchen Klassen zusammengefasst sind, noch ist es interessant, in welchen Komponenten Klassen gemeinsam liegen.

Nun mag es schwierig sein, Aktivitäten von Klasse auf Methode umzustellen oder Methoden-Prozessschritte aus Klassen herauszutrennen. Täter- bzw. Tat-Klassen (mit ihren “Anhängseln”) jedoch aus der einen in eine andere Komponente zu verschieben, sie heute so und morgen anders zu gruppieren, scheint mir relativ leicht. Das sollten wir als Chance begreifen und ausnutzen.

Denken Sie also in elastischen Komponenten. Fühlen Sie sich recht frei, sie mit Aktivitäten zu füllen, um ihren Produktionsprozess zu optimieren. Komponenten mit ihren Komponentenwerkbänken schaffen beste Voraussetzungen für zügige Parallelentwicklung am Code. Im CCD Praktikum sind wir durch sie in fünf Tagen über nicht einen Mergekonflikt gestolpert.

Namensräume

Klassen und Komponenten sind Mittel zur physischen Strukturierung. Die Beweggründe zu Trennung oder Zusammenfassung von Code sind vielfältig. Sie können mit der Arbeitsorganisation zu tun haben, mit technologischen Abhängigkeiten oder einem Implementationsdetail geschuldet sein.

EBC-Prozesshierarchien stellen demgegenüber eine funktionale Strukturierung dar. Aktivitäten sind geschachtelt oder nachgeschaltet, um eine Anforderung zu erfüllen.

Physische Nähe und funktionale Nähe drücken aber noch nicht unbedingt inhaltliche Nähe aus. Von der physischen Nähe ist das zwar eher anzunehmen, aber es ist auch nicht zangsläufig. Vor allem kann sich die physische Struktur ändern, ohne dass deshalb die inhaltliche, logische Struktur auch ändern sollte. Wie drücken Sie im Code also inhaltliche, logische, konzeptionelle Nähe aus? Wie fassen Sie etwas logisch zusammen, auch wenn es physisch getrennt z.B. in verschiedenen Komponenten liegt?

Das Mittel für die logische Zusammenfassung von Klassen sind Namensräume:

image

Ohne Ansehen des Definitionsortes (physische Struktur) und des Einsatzortes (funktionale Struktur) verbinden Namensräume Klassen. Sie schaffen damit eine weitere “Großtäterdimension”.

image

Zwischenfazit

Für mich fühlen sich EBC jetzt stimmiger an. Klare Regeln für das, wie ein EBC-Diagramm aufgebaut ist, leiten meine Gedanken bei der Ableitung des Entwurfs aus den Anforderungen:

  1. Finde Quelle und Senken für den Prozess. Diese Verantwortlichen sind vergleichsweise einfach zu bestimmen.
  2. Zerlege den Prozess in Aktivitäten; zerlege die Aktivitäten falls nötig in weitere Aktivitäten. Diese Ableitung von Schritten aus einer Anforderung ist vergleichsweise naheliegend; man beginnt mit dem Konkreten, statt mit Abstraktem/Unbekanntem.

Und klare Übersetzungsoptionen helfen mir beim Codieren des entworfenen Modells. Die Möglichkeiten moderner Sprachen werden ausgenutzt.

  • Übersetze Aktivtitäten in Klassen (oder auch Methoden); finde die Täter hinter den Taten; abstrahiere vom Bekannten, suche Muster
  • Finde Namensräume, um Klassen logisch/inhaltlich in die Nähe zu rücken
  • Organisiere Klassen in Komponenten, um ihre Implementation effizient betreiben zu können – aber auch, um “Übergriffe” zu vermeiden. Komponenten dienen der Stabilisierung der funktionalen Strukturen; es sind Bollwerke gegen die Tendenz zu steigender Code-Entropie

Unterschiedliche Plattformfeatures dienen unterschiedlichen Zwecken:

  • Klassen, Methoden: funktionale Strukturierung
  • Namensräume: logische Strukturierung
  • Assemblies: Strukturierung der Code-Produktion

Da schaut konzeptionell sauber aus, oder? Alles hat seinen Zweck.

Fragts sich abschließend nur, was mit den “normalen” Klassen/Objekten ist? Gibt´s die noch? Ja, die gibt´s noch. Sozusagen in den Zwischenräumen ;-) Davon mehr beim nächsten Mal.

Freitag, 16. Juli 2010

Prozesse klammern

Prozesse, Tätigkeitsfolgen, Herstellungsschritte stehen für mich derzeit am Anfang des Softwareentwurfs, wie ich hier beschrieben habe. Aus Anforderungen lassen sich nur schwer Klassen als Verantwortliche einer Lösung ableiten. Viel einfacher ist es, die Tätigkeiten zu ermitteln, die in Summe den Wunsch eines Anwenders erfüllen.

Wenn Sie ins Restaurant gehen, dann wollen Sie ein leckeres Essen. Dafür ist es wichtig, dass ein Prozess reibungslos abläuft:

  1. Sie wollen einen Tisch zugewiesen bekommen
  2. Sie möchten die Karte gereicht bekommen
  3. Sie möchten eine Bestellung aufgeben
  4. Sie möchten, dass das Gericht schmackhaft und zügig zubereitet wird
  5. Sie möchten, dass das Gericht serviert wird
  6. Sie möchten am Ende reibungslos bezahlen

Aus der Anforderung “Lecker essen” lassen sich ganz simpel “Herstellungsschritte” ableiten. Es ist klar, was (!) getan werden muss, um Ihren Wunsch zu erfüllen. Wer das tut, ist hingegen nicht herauszulesen. Ihnen als Gast ist es letztlich egal, ob die Tischzuweisung durch Sie selbst geschieht oder Restaurantpersonal. Ihnen ist auch egal, ob ein Kellner Ihnen die Karte bringt oder die schon auf dem Tisch liegt. Und wer das Gericht zubereitet, ist auch einerlei, solange es Ihren Qualitätsanforderungen entspricht. Am Ende müssen Sie auch nicht bei einem Kellner bezahlen, sondern wären womöglich bereit, automatisch bei Verlassen des Restaurants zu bezahlen.

Wenn Sie ein Programm für die Anforderung hätten entwerfen sollen, dann wären Sie jedoch nach OOP-Manier sehr schnell darauf gekommen, einen Kellern, einen Koch, einen Restaurantchef zu modellieren. Denen hätten Sie Aufgaben/Dienstleistungen zugeordnet.

Objektorientierung legt es mir Klassen/Objekten einfach nahe zu überlegen, wer (!) an einer Lösung beteiligt sein kann. Ein Prozessdenken, wie ich es hier beschreibe und derzeit übe, stellt hingegen das Was (!) an den Anfang. Was tun? Nicht: Wer tut?

image

Spüren Sie den Vorteil der Prozessdenke schon bei diesem Beispiel? Wenn Sie in Objekten denken, also in Verantwortlichen, dann beschränken Sie den Lösungsraum. Wenn Sie Kellern, Koch, Restaurantchef an den Anfang stellen, kommen Sie nicht auf ein Konzept wie es z.B. hinter der Restaurantkette Vapiano steht. Dort gibt es nämlich keinen Kellner. Sie interagieren direkt mit dem Koch. Und sie bezahlen beim Rausgehen das, was auf einer “Kreditkarte” registriert wurde. Dass dort noch jemand steht und kassiert, hat keinen Nutzen in Bezug auf die Bezahlung, sondern ist nur noch eine Freundlichkeitsgeste.

In einer reinen Prozessbeschreibung zur Erfüllung der Anforderung “Lecker essen” ist das Vapiano-Konzept erhalten. Es ist eine funktionierende Ausprägung. Prozessschritte sind invariant. Sie müssen sich immer setzen, bestellen, Essen muss zubereitet werden, für Bestelltes muss man bezahlen. Sitzen, bestellen, zubereiten, bezahlen. Ohne diese Aktivitäten geht es nicht.

image
Wer dafür jedoch verantwortlich ist… das ist eine ganz andere Frage. Es geht ohne Kellner, es geht ohne Koch, es geht ohne Restaurantchef, es geht ohne Kassierer. Die Hotelkette MotelOne macht es auch vor: Sie durchlaufen beim MotelOne einen Prozesse wie in jedem anderen Hotel – aber die Verantwortlichen sind anders verteilt. Alle “Herstellungsschritte” für das Produkt “Angenehme Übernachtung” werden erbracht, nur anders, von anderen als üblich.

Quellen und Senken für Prozesse

Am Anfang des Softwareentwurfs sehe ich derzeit also ganz klar die Aktivitäten. Was muss für jedes Feature geleistet werden? Das beginnt beim allerkleinsten Feature und hat nach oben keine Grenze. Ob die Farbe einer Zahl verändert werden soll, jenachdem ob ihr Wert positiv oder negativ ist, oder eine Funktion zu plotten ist… immer steht dahinter ein Prozesse bestehend aus Aktivitäten. Eine Tat nach der anderen muss vollbracht werden. Von wem? Erstmal egal.

image

Im Entwicklungsprozess steht also zunächst die Identifikation eines Features und die Bestimmung von Input/Trigger sowie Output/Zustandsänderung.

image

Diese Features leiten Sie aus den Anforderungen ab. Das ist natürlich eine Kunst für sich. Sie sollen so groß sein, dass sie einen Anwendernutzen darstellen. Andererseits sollen sie so klein sein, dass sie sich schnell umsetzen lassen. Nur so können Sie in einen kontinuierlichen Feedbackprozess mit dem Anwender treten.

Diese Feature sind kleine System-Umwelt-Diagramme. Sie benennen einen Prozess, der durch die Umwelt gestartet wird und auf die Umwelt wirkt. Deshalb sind noch Quellen und Senken zu bestimmen. Wer initiiert einen Prozess? Wer nimmt die Produkte des Prozesses entgegen? Quellen und Senken sind Verantwortliche. Bei ihnen geht es um das Wer.

image

Typischer Initiator, typische Quelle für den Prozess eines Feature ist natürlich das UI. Ebenso ist es typische Senke, typischer Empfänger. Aber auch ein Webservice-Stub kann Prozessinitiator sein oder ein Timer. Und Ressourcen wie eine Datenbank können Senke sein.

Im Augenblick sehe ich also die Verantwortlichen Quelle/Initiator und Senke/Empfänger als Klammern um featureproduzierenden Prozesse. Ganz einfach, ganz systematisch.

Dieses Denken hat uns jedenfalls im CCD Praktikum sehr geholfen. Hier ein Tafelbild von unserer Planungsarbeit:

image

Die Prozesse für zwei Features des Kinokassenprogramms, an dem wir zwei Tage gearbeitet haben, sind grün hervorgehoben. Die Verantwortlichen für ihre Initiierung bzw. Ergebnisverarbeitung sind rot markiert. Für das obere Feature ist sogar das untere eine Senke; der obere Prozess liefert Daten an den unteren.

Initiatoren und Senken als Klassen umzusetzen, liegt natürlich nah. Aus meiner Sicht sind sie EBC-Komponenten, wie ich sie bisher gedacht hatte. Denn bisher ging es in EBC-Schaltplänen eigentlich immer um “Wer tut etwas?”

Diese Frage lässt sich natürlich mir einigem Aufwand beantworten – doch aus heutiger Sicht ist die Diskussion um Request/Response-Drähte ein Symptom dafür, dass das Denken in Verantwortlichen, die per Events kommunizieren, falsch ist.

Wenn ein Verantwortlicher eine Query absetzt, dann will er zurecht eine Antwort. Wenn aus einer Aktivität jedoch eine Query hinausfließt, dann will die keine Antwort. Die Antwort fließt vielmehr in eine nachgeschaltete weitere Aktivität. Mit Prozessdenken verliert also das Request/Response-Denken an Dringlichkeit. Das fühlt sich für mich gut an.

Zwischenstand

Das Vorgehen bei der Modellierung sieht bisher für mich also wie folgt aus:

  1. Aus den Anforderungen Features ableiten; Features haben immer Wert für den Anwender
  2. Für die Features Input/Trigger und Output/Zustandsänderungen bestimmen
  3. Bestimmen, wer triggert und wer am Ende Ergebnisse entgegennimmt
  4. Ausgehend vom Trigger den Prozess formulieren, der seinen Input in Output bzw. Zustandsänderungen transformiert. Der Prozess besteht aus Aktivitäten, d.h. er dreht sich um das, was zu tun ist, nicht darum, wer es tut.

Donnerstag, 15. Juli 2010

Code als Fabrik – Event-Based Components weitergedacht

Ein Gedanke, der mich gerade nicht loslassen will ist, dass Code eigentlich eine Fabrik ist. Seit EVA wissen wir es eigentlich: “Eingaben verarbeiten zu Ausgaben” ist die Aufgabe von Code. Im Laufe der Objektorientierung scheint mir das jedoch in Vergessenheit geraten zu sein.

Statt um einen Fluss von Datenquelle zu Datensenke geht es der OOP um Zustand. Kochtopf statt Durchlauferhitzer. Und das hörte sich irgendwie auch plausibel an, da die Welt doch aus Objekte zu bestehen scheint. Vom Atom bis zum Galaxienhaufen alles Objekte mit einem Zustand. Oder?

image Eine Zeit lang und für einige Anwendungsszenarien hat dieses Denken funktioniert. Ziel dieser Programmierung ist es, gesunde Klassen mit roten Bäckchen zu entwerfen. Klassen mit wenig Funktionalität auf ihrem Zustand gelten als kränklich, als anämisch.

Doch mir kommen immer mehr Zweifel, ob das der beste Modellierungsansatz für Problemlösungen mit Software ist.

Nicht, dass ich OOP verwerfen wollte. Nein, nein. Ich mag OOP-Sprachen wir C# sehr. Keine Frage. Alles wunderbar. Ihre Möglichkeiten möchte ich nicht missen. Selbst Vererbung kann nützlich sein. Zurück zu prozeduralen Sprachen möchte ich nicht.

Doch ich möchte von diesen Möglichkeiten nicht auch noch eine bestimmte Modellierung aufgezwungen bekommen. In gewisser Weise ist das natürlich unvermeidbar. Ob man mit Stahl, Stein oder Holz eine Brücke baut, macht auch einen Unterschied für die grundsätzliche Bauweise. “Form follows Material”, würde ich mal sagen. Bei Software möchte ich mich jedoch weniger eingeschränkt fühlen.

Und so mache ich mich mal frei von der Objektorientierung und der von-Neumann-Maschine. Das ist nicht leicht, auch mir Sitzt die prozedurale und objektorientierte Prägung im Stammhirn. Aber wenn ich mich recke, meine ich ein Bild von jenseits des Tellerrandes erhaschen zu können.

Nach meinen bisherigen Experimenten mit Event-Based Components (EBC) habe ich mich deshalb in der letzten Woche nochmal auf die Zehenspitzen gestellt. Im CCD Praktikum habe ich EBC bewusst in neuer Weise eingesetzt. Ich wollte ausprobieren, wie es sich anfühlt, ganz bewusst “unobjektorientiert” zu entwerfen. Selbst die bisherige Sichtweise auf EBC-Komponenten als “Dinger, die etwas tun” haben wir daher aufgegeben. Stattdessen haben wir Software als Fabriken angesehen. Fabriken, deren Produkte Daten sind. Fabriken, die Feature-Wünsche des Kunden erfüllen.

Verantwortlichkeiten als Phantasieprodukte

Lassen Sie sich doch einmal darauf ein. Vergessen Sie die Objektorientierung für einen Moment. Wenn ich Ihnen nun sage, dass wir einen Taschenrechner programmieren wollen, woran denken Sie bei einem Lösungsansatz als erstes?

Sie denken an eine Funktionseinheit, die etwas tut. Nennen Sie sie Rechenwerk. Der Anwender gibt zwei Zahlen ein, wählt eine Rechenoperation und das Rechenwerk stellt aus einem Tripel wie (2, 3, +) das Ergebnis 5 her.

image

Jetzt erweitern wir die Aufgaben des Taschenrechners. Er soll Ergebnisse zwischenspeichern können. Welche Funktionseinheit ist dafür verantwortlich? Vielleicht ein Zwischenspeicher? Dann brauchen wir zwei Funktionen darauf: Wir wollen Ergebnisse speichern und auch wieder auslesen können.

 image

Vielleicht wollen wir sie sogar beim Speichern hinzuaddieren (akkumulieren)?

image

Fühlt sich das für Sie ok an? Wir haben zwar noch nicht von Klassen gesprochen, aber Sie denken natürlich reflexartig, dass Rechenwerk und Zwischenspeicher als Klassen implementiert werden können.

Dass dies schon kleine EBC-Schaltpläne waren, ist Ihnen nicht so sehr ins Auge gestochen. Aber woran liegt das? Ich glaube, das liegt daran, dass die Kästchen mit Substantiven bezeichnet sind. Sie heißen Rechenwerk und Zwischenspeicher. Es sind Verantwortliche, es sind Arbeiter, nun, es sind Objekte.

Jetzt festhalten:

Mein Gefühl ist, dass wir damit schon zu kompliziert denken.

Wenn die Aufgabe lautet: “Entwerfe ein Programm, dass addiert und Zwischenergebnisse zwischenspeichern und akkumulieren kann”, dann ist es ein recht großer mentaler Aufwand, aus der Anforderungen Verantwortliche, Arbeiter, Objekte abzuleiten. In der Anforderung steckt kein Rechenwerk drin. Auch kein Zwischenspeicher ist darin beschrieben. Wir phantasieren sie nur reflexartig hinein. Weil wir so OOP-trainiert sind. Denn einer muss die Aufgaben ja am Ende übernehmen, oder? Einer muss addieren, einer muss zwischenspeichern. Na, dann nennen wir den doch Addierer oder allgemeiner Rechenwerk und Zwischenspeicher. Liegt das nicht auf der Hand?

In diesem Fall ist das sehr naheliegend, weil die Anforderungen trivial sind. Wenn es aber komplizierter wird… was dann? Sind unsere Reflexe dann auch verlässlich im Sinne evolvierbarer Strukturen?

Ich glaube, unsere Reflexe sind umso kontraproduktiver, je unbekannter die Problemdomäne ist. Verantwortliche lassen sich schlecht durch Analyse finden. Stattdessen sollten Objekte/Klassen als Abstraktionen durch Synthese entstehen. Klassen und Komponenten sind Orte der Verantwortlichkeit, die etwas zusammenfassen. Was das jedoch ist, das (!) muss erstmal ermittelt werden. Damit sollte der Entwurf beginnen.

Solange wir also durch die Objektorientierung getrieben sind, Verantwortliche zu finden, weil wir nur mit Klassen Aufhänger für Methoden haben, solange produzieren wir leichtfertig Phantasiegebilde. Wir wollen dann den zweiten Schritt vor dem ersten tun.

Am Anfang steht der Prozess

Bisher haben noch keine Klassen gesehen, sondern nur erahnt. Jetzt nehme ich Ihnen aber auch noch die Verantwortlichen weg. Denken Sie nicht mehr in Substantiven. Fragen Sie nicht mehr “Wer soll das tun?”

Machen Sie sich leer von jeglicher OOP und sehen Sie nur die Anforderungen. Anforderungen können sich nur um die Problemdomäne drehen. In ihnen kann per definitionem nichts über die Lösungsdomäne, d.h. den Programmcode stehen. Darüber kann und soll sich ein Kunde keine Gedanken machen.

Ich halte es daher für einen ungeheuren Aufwand, aus Anforderungen irgendeinen im Code Verantwortlichen ableiten zu wollen. Anforderungen können nur über Daten und Prozesse sprechen. Es geht nicht anders. Selbst wenn in den Anforderungen ein Verantwortlicher (in der realen Welt) vorkommt, kann das ja nicht automatisch bedeuten, dass der auch im Code 1:1 so repräsentiert werden sollte. Das (!) ist vielmehr die große, kontraproduktive Suggestion der Objektorientierung.

Versuchen wir es daher einmal anders. Nehmen wir die Anforderungen genau so, wie sie formuliert sind und übersetzen sie in ein Modell. Es ergibt sich:

image

Es setzt die Anforderungen sehr viel einfacher, direkter um. Wir haben nichts hinzugedichtet. Kein Rechenwerk mussten wir ersinnen, keinen Zwischenspeicher. Wir haben lediglich die Prozesse in den Anforderungen aufgedeckt und modelliert.

Ja, ich nenne das Addieren und zwischenspeichern mal Prozess. Denn es geht immer um eine Tätigkeit. Es wird etwas verarbeitet. Mal sind es Daten, mal nur ein Signal wie beim Auslesen. Mal ist das Ergebnis ein Output, mal eine Zustandsänderung. Wessen Zustand geändert wird? Keine Ahnung. Das ist erstmal nicht wichtig. Der Zustandsbehaftete steht in den Anforerungen nicht drin. Und selbst wenn… ob wir gut daran täten, die Vorstellung des Kunden zu übernehmen, steht auf einem ganz anderen Blatt.

Ich behaupte nicht, dass wir Hinweise auf Verantwortliche im Code ausschlagen oder bewusst ignoieren sollten. Ich möchte nur zur Vorsicht mahnen, sie gutgläubig zu übernehmen. Wenn wir bei unserer Modellierung selbst auf sie kommen, dann ist es gut. Ansonsten auch.

Und diese Modellierung beginnt eben nicht mit der Suche nach Verantwortlichen, sondern mit einer Modellierung von Produktions- und Transformationsprozessen. Immer wenn der Anwender einen Knopf drückt, einen Menüpunkt aufruft, ein Zeichen in ein Feld eingibt, kurz: Immer wenn der Anwender einen Event auslöst, kann potenziell ein kurzer oder langer, ein kleiner oder großer Prozess ablaufen.

Mit Prozess meine ich hier schlicht eine Reihe von Verarbeitungsschritten. Erst wird das eine getan, dann das andere, dann noch etwas usw.

Aus dieser Perspektive besteht Software zunächst einmal aus einer großen Anzahl von Prozessen. Die werden getriggert durch Events im Frontend – sei das eine Benutzerinteraktion oder eine Nachricht von einer anderen Software auf einem Kommunikationskanal.

Software ist also eine Fabrik mit vielen Prozessen, die jeder für ein anderes Produkt stehen. Ein Tastendruck stößt die Produktion einer Summe an, ein Mausklick stößt die Transformation einer Zahl in einen Speicherzustand an usw. Nennen wir das einmal Feature-Prozesse, weil sie Feature der Anforderungen realisieren:

image

Auf die Struktur der Prozesse möchte ich hier noch nicht näher eingehen. Aber soviel sei gesagt: Natürlich lassen sie sich schachteln. Eine Tätigkeit wie Akkumulieren ist nur auf einer gewissen Abstraktionsebene eine einzige Tätigkeit. Wir können in sie hineinzoomen. Sie könnte z.B. so aussehen:

image

Ha! Was erkennen wir? Akkumulieren ist eine Tätigkeit, die sich durch andere Tätigkeiten, die wir schon modelliert haben, ausdrücken lässt. Wiederverwendung winkt! Und das, obwohl wir immer noch nicht an Objekte oder Verantwortliche gedacht haben. Wir haben nur Tätigkeiten, Verben, Prozessschritte im Blick. Wir kümmern uns nur um Verarbeitung und Transformation, d.h. um das Was, nicht um das Wo oder Wer.

Dafür ist ein Umdenken nötig. Wir müssen aufhören, reflexhaft über rotbäckige Verantwortliche zu phantasieren. Doch mir scheint, das lohnt sich. Wenn wir an den Anfang unserer Modellierung eine ganz schlichte featureorientierte Modellierung von Prozessen setzen, die selbstverständlich Durchstiche im Sinne eines Schichtenmodells darstellen, dann müssen wir weniger Energie aufwenden.

Damit meine ich nicht, dass wir in Flowcharts programmieren sollen. Die obigen Diagramme sehen auch nicht so aus, würde ich sagen. Auch Windows Workflows (WF) sehe ich noch nicht als Realisierungstechnologie. BPMN-Diagramme oder UML Aktivitätsdiagramme liegen da schon näher. Aber so ganz sicher bin ich mir noch nicht, wohin es führt, Software ganz konsequent als Fabrik bestehend aus vielen Prozesse anzusehen.

Aber vielleicht fällt uns zusammen ja etwas ein. Lassen Sie diese Gedanken erstmal auf sich wirken: Tätigkeiten statt Täter, Prozessschritte statt Verantwortliche. Der Unterschied mag ihnen akademisch erscheinen – ich glaube aber, dass er fundamental ist.

Beim nächsten Mal holen ich Sie dann aus der “objektfreien” Welt zurück. Denn es hilft ja nichts, die Verben der Prozesse müssen ja mit Mitteln von C# umgesetzt werden.