Follow my new blog

Donnerstag, 23. Juni 2011

Flow-Design Motivation - Ein XING-Auszug

Warum ist Flow-Design eigentlich so gegen den üblichen Programmierstrich gebürstet? Dazu habe ich anlässlich einer Diskussion im CCD XING-Forum etwas geschrieben, das ich auch hier für mitteilenswert halte.

Anlass war ein Diskussionsbeitrag von Michael van Fondern:

Ich habe allerdings doch noch eine Frage, die du mir sicher beantworten kannst. Ich bin bisher bei der AppKata im Wesentlichen so vorgegangen

1. Datenabstraktionen bilden
(z.B. in Iteration 1: CsvDataTable/CsvDataRecord als Abstraktion für eine Tabelle)
Einfache Operationen, die sich unmittelbar auf diesen Daten ausführen lassen, die aber ansonsten keine spezielle Sicht implizieren, habe ich direkt bei diesen Klassen platziert (z.B. die CSV-Zerlegung direkt im Konstruktor des CsvDataRecord, oder die Spaltenbreitenberechnung).

2. Abstraktionen für einzelne Prozessschritte bilden (die zudem auf den Daten aus Schritt 1 arbeiten)
- als groben Prozessschritt habe ich die z.B. formatierte Sicht auf eine bestimmte Seite der Tabelle identifiziert und daraus eine Klasse gemacht; die einzelnen Teilschritte spielen sich dann als Funktionen in dieser Klasse ab. Wenn ich bei der Entwicklung / Weiterentwicklung irgendwann merke dass bei einzelnen Funktionen das SRP-Prinzip oder das SLA-Prinzip verletzt werden, refaktorisiere ich diese aus, platziere diese entweder in der Klasse selbst, oder bei einer Datenklasse, oder bilde ggf. auch neue Klassen dafür.

Meine Frage in Bezug auf EBC: was ist mit "Schritt 1 - Datenabstraktionen bilden" (auch in Form von "Objekten" der klassischen Objektorientierung, und ggf. unter Zuhilfenahme von Klassendiagrammen). Diesen Modellierungsschritt habe ich in deinen Artikeln, die du über EBC geschrieben hast, bislang nicht mehr gesehen, aber der ist doch immer noch sinnvoll, oder? Gerade, weil EB-Komponenten nur Nachrichten mit genau einem Parameter verschicken / empfangen. Oder hab ich das was nicht verstanden?

Die beiden Schritte, die er zur Lösung der CSV Viewer AppKata getan hat, scheinen mir typisch. Deshalb hier meine Antwort in Gänze (mit einigen Hervorherbungen und Bonusbildern):

Gut, dass du fragst :-) Du hast einen zentralen Punkt von Flow-Design identifiziert.

Dein Vorgehen:
1. Identifiziere Daten und unmittelbar auf ihnen ansiedelbare Operationen
2. Identifiziere sonstige Operationen

Das entspricht dem üblichen Vorgehen, würde ich sagen. So lehrt es die traditionelle Objektorientierung. Sie fokussiert auf deinem Schritt 1 - Schritt 2 ist eher ein Anhängsel, ein notwendiges Übel.

Objektorientierung traditionell bedeutet für mich Fokus auf Daten und Zuordnung von Funktionalität zu diesen Daten. Das Ergebnis sind Klassen als Blaupausen für Objekte.

Flow-Design stellt das auf den Kopf. Mit voller Absicht. Weil der OO-Ansatz zu den Ergebnissen geführt hat, die wir heute allerorten sehen.

Das Missverständnis des OO-Ansatzes ist es, dass Software soetwas ist wie eine Maschine. Maschinen bestehen aus Teilen, Kolben, Zündkerzen, Lichtmaschinen, Transistoren, ICs, Widerständen, Zeilentransformatoren, Tastaturen usw. usf.
Wenn man eine Maschine bauen will, dann überlegt man sich, wie die Bauteile aussehen sollen. Man denkt in distinkten Funktionseinheiten, die Zustand haben und mehr oder weniger tun. Eher mehr. Allemal bei Software, da man dort quasi immer bei Null anfängt.

Ein Elektrotechniker hat es einfacher: Der sitzt vor einem Kasten mit Standardbausteinen, die er "nur noch" zu etwas Neuem "verrühren" muss.

Softwareentwicklung kennt solche Standardbausteine im Grunde nicht (lassen wir ein paar Bibliotheken und Steuerelemente mal außen vor). Jedes Projekt erfindet sie daher neu in Form von Objekten. Dabei schießt man schnell über das Ziel hinaus. Die Standardbausteine sind keine Standardbausteine, weil sie einfach so groß werden. Deshalb immer wieder das Gejammer über mangelnde Reusability. Man möchte in die Position eines Elektrotechnikers kommen.

Wenn wir uns aber von dem Missverständnis verabschieden, dass Software eine Maschine ist, dann wird alles leichter. Es ist müßig, nach "Standardbausteinen" zu suchen. Der Setzkasten ist leer. Unsere Aufgabe sollte nicht sein, ihn erst zu füllen und dann mal zu schauen, was wir mit unseren eigenen Bausteinen bauen können.

Also geben wir den Fokus auf Datenstrukturen mit Funktionalitätsanhängseln auf. Weg mit dem Objektfokus. Weg mit dem Bauteildenken. (Dass auch im Flow-Design noch von Platinen und Bauteilen die Rede ist, ist ein Fehler, der korrigiert werden wird.)

Flow-Design hat ein anderes Softwarebild. Für FD ist Software keine Maschine, sondern eine Ansammlung von Prozessen, oder - weil der Begriff Prozess schon so besetzt ist - eine Ansammlung von Verhaltensweisen.

Bei FD beginnst du deshalb mit der Identifikation von Verhaltensweisen statt Daten, mit Verben statt Substantiven.

Was soll ein CSV Viewer leisten? Er soll eine CSV Datei seitenweise anzeigen.
Irgendwie werden also mal ganz grundsätzlich Textzeilen eines bestimmten Formats in Seiten eines bestimmten Formats transformiert.

Für denn OOPler stecken da natürlich hübsche Daten drin: Textdatei, Zeilen, Seiten. An die hängt er geistig schnell die ablesbaren Funktionen: lesen, formatieren.

Aber da beginnt schon das große Rätselraten: Wozu soll denn eine Funktionalität wie das Auseinandernehmen eines CSV Textzeile gehören? Ist das eine Aufgabe des Textdateiadapterobjektes? Es liefert CSV-Datensätze zurück, die aus Spaltenwerten bestehen? Oder soll sich der Adapter darauf beschränken, Textzeilen zu liefern und die Formatierung bricht sie auf? Hm...

FD ist da viel pragmatischer, direkter, natürlicher. Man fängt einfach mal an, die obige Anforderung zu formalisieren:

(run) -> (Dateiname von Kommandozeile holen)
      -(dateiname)-> (Erste Seite aus CSV Datei lesen)
      -(seite)-> [Seite anzeigen].

image

Damit ist ein Programm beschrieben, das schonmal ein Feature der ersten Iteration realisiert. Das Abstraktionsniveau ist sehr hoch, klar, aber das ist ja gerade der Trick. Wir haben das Programm formulieren können, obwohl wir nur eine grobe Ahnung von der Lösung haben.

Die Anforderungen sagen mir vor allem, was zu tun (!) ist. Welche Transformation erwartet der Benutzer? Denn um Transformationen geht es immer. Input wird in Output transformiert. So ist das Softwareleben. Immer. Unzweifelhaft.

Essenziell dreht sich Software damit um Funktion und nicht Daten. In der Mitte von EVA stehen nicht Daten, sondern Transformation.

Natürlich, ohne Daten geht es nicht. Aber wir dürfen uns nicht ins Bockshorn jagen lassen, nur weil Daten am Anfang und am Ende von EVA stehen. Die Daten sind nicht der Grund, warum wir V entwickeln sollen. Die Daten sind vielmehr schon da. Die Vorstellung davon, wie Input und Output aussehen, ist selbst beim Kunden verhältnismäßig klar. Er mag die nicht formalisieren können, aber er hat Daten und will andere Daten bekommen, von denen er weiß, wie sie aussehen sollen. Sonst hätte er nicht den Wunsch nach einer Software, der ihm genau diese Daten erzeugt.

Das, was dem Kunden aber viel, viel unklarer ist (und uns erstmal auch), das ist, wie die Transformation aussieht. Wie macht man das, den Input in den Output zu überführen? Das (!) herauszufinden, ist unsere Aufgabe.

V ist also unklar und wird nicht klarer, indem wir mit OOP lange über E und A grübeln. Wir müssen sofort, wenn wir Anforderungen sehen, V in den Blick nehmen. Zunächst ganz grob, dann immer detaillierter, am Ende indem wir Code schreiben.

Obiger Fluss ist ein grobes V für ein Feature des CSV Viewers. Noch gröber, aber recht uninteressant wäre:

(run) -> (Lade und zeige die erste Seite der CSV Datei an).

image

Im Zweifelsfall kannst du aber gern so beginnen. Dann wäre die Schrittfolge:

1. (run) -> (Lade und zeige die erste Seite der CSV Datei an).

image

2. (run) -> (Dateiname von Kommandoleise holen)
        -(dateiname)-> (Erste Seite aus CSV Datei lesen)
        -(seite)-> [Seite anzeigen].


image

und vielleicht folgende Verfeinerungen:

3.1 Erste Seite aus CSV Datei lesen {
    (in) -(dateiname)-> (Lese Textdatei zeilenweise)
         -(string*)-> (Zerlege CSV Textzeilen in Werte)
         -(CSVRecord*)-> (Sammle Records für erste Seite)
         -(seite)-> (out)
   }


image

3.2. Seite anzeigen {
     (in) -(seite)-> (Normal. Seite auf max Spaltenbreiten)
          -(seite)-> (Formatiere Seite als Tabelle)
          -(string*)-> [Tabelle anzeigen]
   }

image

Jetzt überlegst du, ob du für jede Operation im Flow schon eine konkrete Idee zur Umsetzung hast. Und ob die Umsetzung wahrscheinlich nicht umfangreicher als vielleicht 50 LOC.

Wenn ja, fang an mit dem Codieren, gern nach TDD. Wenn nein, verfeinere weiter, was noch zu kompliziert/unübersichtlich ist.

Wenn du das nicht kannst, dann ist das nicht ein Signal dafür, mit TDD zu beginnen, um Lücken zu schließen, sondern ein Zeichen dafür, dass du das Problem und damit seine mögliche Lösung noch nicht gut genug verstanden hast. Oder vielleicht hast du auch ein Problem mit deinen Technologien. Eine Spike Solution könnte angezeigt sein.

Ein Problem nicht zu verstehen oder keine rechte Idee von der Lösung zu haben, sollte aber allemal ein Warnsignal sein, nicht (!) zu codieren.

Und was ist mit den Daten? Achja... da war doch noch was :-)

Das FD Modell enthält natürlich Daten. Da gibt es Seiten und CSVRecords. Die musst du natürlich auch detaillieren und formalisieren. Aber dazu braucht es nicht mehr als z.B. ein simples Krähenfußdiagramm.

Und was ist mit Funktionalität, die direkt an den Daten hängt? Meine Meinung: die ist überbewertet, weit überbewertet :-)

Dass wir Daten und Funktionen in Klassen zusammenfassen können, ist schön. Das will ich nicht missen. Aber eher nicht für das, was zwischen den Operationen fließt. Das sind Datendaten :-) Datenstrukturen, die im Wesentlichen funktionsfrei bleiben sollten. (ADTs machen da eine Ausnahme.)

Wenn Operationen Zustand haben, dann ist es aber sehr schön, dass ich beides zusammenfassen kann.

Nun hast du zwei ganz einfache Modelle:

1. Ein ganz einfaches Flussmodell für die so wichtige Transformation (V).
2. Und ein ganz einfaches Datenmodell für E und A.


Das nenne ich natürlich, direkt, einfach, verständlich. Kein Rätselraten, sondern ablesen, was in den Anforderungen steht, um es simpelst zu formalisieren.

Verstehst du, was mich motiviert, OO-Technik mit der FD-Methode anzugehen und nicht mit der überkommenen OO-Methode und wie vorteilhaft FD ist?

Mitteilenswert finde ich das unterschiedliche Softwarebild: Maschine vs Verhalten. Denn daraus folgt ein anderer Analyseansatz und eine andere Modellierung.

Und warum ein so anderes Softwarebild? Weil Software sich eben als so volatil erwiesen hat. Maschinen sind statisch, Prozesse hingegen sind (im doppelten Sinn) immer im Fluss. Die Agilität hat das in puncto Vorgehen bei der Softwareentwicklung schon verstanden. Das Softwarebild hinkt mit dem OO-Fokus aber noch hinterher. FP ist ein Lichtblick, doch (noch lange) keine Option für viele Entwickler. Und warum auch dringend Technik (F# statt C#) und Methode (FD statt OOAD) ändern, wenn es (erstmal) reicht, nur die Methode zu ändern? Denn mit FD lässt sich sehr bequem “flüssige Software” entwickeln auf der Basis dessen, was wir gut kennen: OO-Technik.

Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat…

Kommentare:

Laurin Stoll hat gesagt…

Hallo Ralf,

Immer wenn ich etwas Zeit finde befasse ich mich wieder mit dem Thema. Sehr spannend.
Ich habe dazu noch eine Frage. Das war sehr motivieremd für FD.
Mich bechäftigt die Frage wo denn die Untereilung in physische Komponenten dann ihren Stellenwert hat? Damit meine ich die identifizierung von Komponenten und Assemblies. Das wird ja dann eigentlich völlig unwichtig.
Noch immer befassen wir uns beim komponentenorientierten Progammieren mit OO extrem viel mit dem Identifizieren von Komponenten und mit dem anlegen von assemblies welche diese Komponenten zusammenfassen. Wenn wir aber davon sprechen nur noch Prozesse abzubilden gibt es doch eigentlich keine gründe mehr diese Komponenen und assemblies zu identifizieren?
Wie siehst du das? Natürlich hat das ganze seine Berechtigung wenn man mal merkt das die übrsichtlichkeit etwas hinkt...aber sonst....

Lg
Laurin

Ralf Westphal - One Man Think Tank hat gesagt…

@Laurin: Komponenten sind aus meiner Sicht keine Modellierungselemente mehr. Sie kommen im Entwurf nicht mehr vor.

Stattdessen benutze ich sie "nur" noch in der Arbeitsorganisation (oder evtl. noch mal wenn es wirklich um Wiederverwendung geht).

Komponenten sind Codeeinheiten, an denen getrennt im Team gearbeitet werden kann, ohne sich ins Gehege zu geraten. That´s it.

Das Ergebnis der Modellierung wird auf Komponenten verteilt, wie es halt passt. Erstes Kriterium für "Passen" ist natürlich der innere Zusammenhang von Modellierungseinheiten.

Wie gesagt: das passiert nach (!) der Modellierung, nicht am Anfang. Ich setze mich also nicht hin und frage mich, "Hm... aus welchen Komponenten könnte die Anwendung bestehen?"

chrkon hat gesagt…

Hallo Ralf,

Du schreibst:
"Also geben wir den Fokus auf Datenstrukturen mit Funktionalitätsanhängseln auf. Weg mit dem Objektfokus. Weg mit dem Bauteildenken. (Dass auch im Flow-Design noch von Platinen und Bauteilen die Rede ist, ist ein Fehler, der korrigiert werden wird.)"

Bis zum "Weg mit dem Objektfokus" stimme ich voll zu.

Aber warum weg mit dem Bauteildenken? Warum weg von den Begriffen Platine und Bauteil?

Wenn man sich Elektronik Bauteile ansieht, dann ist das doch genau das, was gemeint ist. Jedes elektronische Bauteil führt eine Transformation aus. Es gibt Eingänge und Ausgänge und dazwischen wird etwas verändert.

Beim Bau einer Maschine, besser einer elektronischen Schaltung für eine Maschine, fragt man nicht "wie soll sie aussehen", sondern "was soll sie machen". Und das ist doch genau die Frage die wir uns auch beim Flow Design stellen, oder?

Viele Grüße,
Christof Konstantinopoulos

Ralf Westphal - One Man Think Tank hat gesagt…

@chrkon: Bautail und Platine mögen Begriffe einer Implementation von Modellen sein. Aber ich halte sie für zu konkret für Begriffe der Modellierungssprache. FD != EBC.

FD ist eine "Denke", EBC ist eine Implementation von Modellen, die mit dieser Denke entworfen wurden.

Platine und Bauteil haben aus meiner Sicht nichts in einer "Denke" zu suchen. Da kann man von zusammengesetzten und atomaren Funktionseinheiten sprechen oder von Flows und Operationen oder so.