Follow my new blog

Dienstag, 6. September 2011

Bausteine für die Softwareevolution

Wenn wir Software naturnah entwickeln wollen, also als evolvierbares System, wie sollte sie denn dann aufgebaut sein? In meinem vorherigen Artikel habe ich mir Gedanken über eher organisatorische Voraussetzungen für naturnahe Entwicklung gemacht. Diesmal will ich die technische Seite angehen. Oder besser: wieder angehen, denn vor Jahren schon hatte ich in diese Richtung spekuliert [1]. Damals waren meine Idee eher im Konzeptionellen verblieben - heute habe ich eine Vorstellung von ihrer Implementation.

Voraussetzung Selbstähnlichkeit

Ich glaube immer noch, dass Software nur wirklich gut evolvieren kann, wenn ihre Struktur selbstähnlich ist. Das heißt erstens, dass es in Software mehrere Ebenen gibt. Und zweitens bedeutet es, dass der grundlegende Aufbau aller Ebenen gleich ist.

Ich möchte diese Ebenen jedoch nicht Schicht/Layer nennen. Der Begriff ist überladen und deutet deshalb in die falsche Richtung. Stattdessen wähle ich Stratum; damit lehne ich mich an Abelson/Sussman an, die von Stratified Design sprechen.

Selbstähnlichkeit hat den Vorteil, dass Erfahrungen und Denken nicht ebenenspezifisch sind. Man kann auf einer beliebigen Ebene beginnen und sich sowohl nach oben wie nach unten vorarbeiten, ohne einen konzeptionellen Bruch zu erleiden.

Methoden sind etwas ganz anderes als Klassen, die wiederum etwas anderes sind als Assemblies, die wiederum ganz anders sind als Prozesse. Wenn wir mit diese Mitteln Software in unserem mentalen Modell repräsentieren, dann ist das kompliziert, weil immer wieder anders.

Eine Reduktion auf Klassen allein jedoch ist nicht möglich, da Klassen konzeptionell nichts enthalten außer Methoden. Klassen sind nicht schachtelbar. Sie sind dafür gedacht, Zustand und Funktionalität zusammenzufassen. Das war´s. [2]

Selbstähnlichkeit ist für mich die Voraussetzung, dass Systeme sich entwickeln können. Sie können in der Breite wachsen (innerhalb einer Ebene) oder sie können nach oben wachsen (Abstraktion, Aggregation) oder sie können nach unten wachsen (Verfeinerung, Spezialisierung).

Voraussetzung Unabhängigkeit

Dann glaube ich, dass die Bausteine in den Strata einer Software, unabhängig von einander sein sollten. Abhängigkeiten – statische wie dynamische – sind der Anfang allen Unwartbarkeitsübels. Dependency Injection zum Zwecke der Bereitstellung von Dienstleistungen ist keine Lösung, sondern perpetuiert das fundamentale Problem.

In der Natur finden sich keine Dienstleistungsabhängigkeiten in der Form, wie sie in Software allerorten existieren. Keinem Organismus wird eine Abhängigkeit injiziert. (Nur Parasiten injizieren sich – wir denken an Sacculina, Juwelwespe oder Ridley Scotts Alien.) Organismen brauchen einander, aber nicht in Form von “Referenzen” oder “Handles”. Sie sind vielmehr auf ihre jeweils autonom hergestellten “Outputs” angewiesen. Das ist gelebte Entkopplung.

Dasselbe gilt für den Maschinenbau oder die Elektrotechnik. Einem Motor wird kein Benzintank injiziert, auch wenn er ohne den Treibstoff nicht arbeiten kann. Benzintank und Motor werden lediglich verbunden; es gibt Berührungspunkte, aber keine Abhängigkeiten.

Allemal gibt es keine so breiten Abhängigkeiten wie die durch Interfaces üblicherweise eingegangenen. Ein Motor ist nicht von einem Chassis oder einen kompletten Restauto abhängig. Er hat verschiedene Input-Kanäle, die nur irgendwie gespeist werden müssen. In einem Motorprüfstand wird deutlich, dass das auch ganz anders als mit einem Auto geschehen kann. Dasselbe gilt für einen menschlichen Körper, der ganz ohne Herz und Lunge auskommen kann, wenn an die Gefäße eine Herz-Lungen-Maschine angeschlossen ist.

Abhängigkeiten machen Systeme starr. Evolvierbarkeit setzt daher maximale Unabhängigkeit voraus.

Voraussetzung Nachrichtenorientierung

Die Verbindung zwischen Lebewesen ist immer “nachrichtenorientiert”. Wichtiger ist aber die Nachrichtenorientierung. “Daten” fließen unidirektional von einem Lebewesen zu einem anderen, gezielt oder diffus. Folgt man dem Konstruktivismus, dann ist die Welt (und damit andere Lebewesen) gar nicht direkt erfahrbar. Wir haben lediglich eine begrenzte Wahrnehmungsbreite und empfangen Signale. Die Ausgangspunkte dieser Signale selbst haben wir nie in der Hand. Wir konstruieren sie uns aus den Signalen.

Wenn das für die Evolution der Natur so zentral ist, dann scheint es mir vorteilhaft für Software, die ebenfalls hochgradig evolvieren muss. Ihre Bausteine sollten ebenfalls nur über Nachrichten kommunizieren. Das heißt, es gibt kein Call/Response, sondern nur unidirektional fließende Datenpakete. (Inwiefern die Referenzen auf den Zustand von Bausteinen enthalten können/dürfen, lasse ich hier mal dahingestellt.)

Softwarebausteine verstehen bestimmte Nachrichten, die sie von der Umwelt empfangen. Und sie senden eine bestimmte Menge von Nachrichten an ihre Umwelt. Sendung und Empfang sind in der Natur asynchron. Zwischen Softwarebausteinen würde ich Asynchronizität jedoch nicht zwingend voraussetzen.

Die Form evolvierbarer Software

Wenn ich diese Voraussetzungen zu einer Form für Softwarebausteine zusammenfasse, dann kommt das folgende Bild heraus:

image

Ich könnte diesen Softwarebaustein “Objekt” nennen, da er zustandsbehaftet ist und über Nachrichten kommuniziert. Aber “Objekt” ist so belastet, dass ich zumindest heute auf einen neutralen Begriff ausweichen möchte. Ich nenne ihn deshalb “Holon”. Das passt zur Selbstähnlichkeit; Holons sind “Dinger”, die gleichzeitig Teil und Ganzes sind.

Jedes Holon nimmt die Umgebung durch einen “Kanal” wahr und sendet über einen weiteren “Kanal” Signale an seine Umgebung, die dann andere Holons wahrnehmen können. Wahrnehmungen (Input) verarbeitet das Holon zu Zustand und/oder Signalen (Output).

Natürlich kann jedes Holon nur eine bestimmte Menge von Signalen verarbeiten und erzeugen. Signale bzw. Nachrichten bestehen daher nicht nur aus Daten, sondern haben auch noch einen Typ oder eine Bedeutung. Die Zahl 42 mag einmal die Bedeutung einer Antwort auf die ultimative Frage haben und einmal schlicht das Alter einer Person sein.

Aus Holons lassen sich nun Verarbeitungsstrukturen auf mehreren Ebenen (Strata) bilden:

image

Meine These ist, dass Software viel besser evolvierbar wird, wenn wir sie aus Holons aufgebaut denken. Die scheinen merkwürdig eingeschränkt gegenüber unseren heutigen OOP-Objekten. Ich behaupte aber mal, dass einen eventuelle Einschränkung mehr als kompensiert wird durch das, was wir bekommen. Und das wir nur bekommen, was wir wollen – viel mehr Evolvierbarkeit –, wenn wir uns einschränken.

Wir denken mit Holons nicht mehr über Abhängigkeitsverhaue nach. Wir haben einen ganz regelmäßigen Aufbau von Software von der kleinsten Codeeinheit bis zur größten. Wir können ganz natürlich Stratified Design betreiben. Und wir bekommen Bausteine, die sich von Hause aus für asynchrone/parallel Verarbeitung eignen.

Klar, das bedeutet, wir müssen unsere Denkstrukturen ändern und Gewohntes über Bord werfen. Der in Aussicht gestellte Gewinn scheint mir jedoch groß genug, um es damit zu probieren. Und so schwer ist es ja auch nicht, es auszuprobieren. Hier das universelle Interface für Holons:

interface IHolon {
  void Process(IHolonMessage input, Action<IHolonMessage> output);
}

Im Grunde reicht sogar ein Delegat, um jeden Baustein in jedem Stratum zu beschreiben:

delegate void Holon(IHolonMessage input, Action<IHolonMessage> output);

Und die Nachrichten, die zwischen den Holons fließen, sind auch ganz einfach:

interface IHolonMessage {
    string Type {get;};
    object Data {get;};
}

Kaum zu glauben, aber ich denke, dass mit solch einfachen Mitteln sich grundsätzlich alle Softwarelösungen ausdrücken lassen. [3] Diese einfachen Mittel stellen für mich das Rückgrat evolvierbarer Software dar.

PS: Wenn in der Softwareentwicklung Eleganz wirklich einen hohen Stellenwert hat, dann wage ich mal keck zu hoffen, dass eine Softwarestruktur dieser Art doch einigen Appeal hat. Ich zumindest finde sie in ihrer Einfachheit elegant.

PPS: Ich weiß, die Holons sehen aus wie Funktionseinheiten des Flow-Designs. Natürlich gibt es da auch einen Zusammenhang. An dieser Stelle überlege ich jedoch grundlegender. Deshalb haben Holons auch nur einen Eingang und einen Ausgang.

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

Fußnoten

[1] s. ältere Artikel in der Rubrik “Software als System” in diesem Blog, insbesondere diesen und diesen Artikel.

[2] Das heißt nicht, Objekte seien nutzlos für evolvierbare Software. Nein, ich denke, wir müssen keine neue Programmiersprache erfinden, sondern die vorhandenen nur besser einsetzen.

[3] Natürlich meine ich damit nicht, dass Software bis hinunter zu Ausdrücken aus Holons aufgebaut werden sollte. Mir geht es um das Herunterbrechen von Funktionalität bis zu einer Granularität, dass man begründet zuversichtlich sein kann, ein Blatt-Holon mit wenigen Zeilen konventionellen Codes umsetzen zu können.

Kommentare:

Markus hat gesagt…

> void Process(IHolonMessage msg, Action msg)

Das geht so aber nicht ;-)

Thomas Gey hat gesagt…

Ich denke das Schichten zu eindimensional sind. Ich finde eine Teil - Ganzes Beziehung (in Anlehnung an das Kompositum Entwurfsmuster) als strukturelle Beziehung zwischen Stratum und Holon interessant. Daduch werden unterschiedliche Abstraktionsbenen erzeugt. Also z.B. Zellen bilden Organe, Organe bilden Organismen, Organismen bilden Gesellschaften oder Bauteile bilden Platinen, Platinen bilden Systeme. Innerhalb jedes Stratums kann in einer eigenen Sprache kommuniziert werden. Also die Zellen besitzen Funktionalität und stehen im Verbund mit den anderen Zellen des Organs. Diese Zellverbund realisiert die Funktionsweise des Organs. Wenn jetzt aber Organe miteinander "kommunizieren" werden diese nicht mit den Zellen des anderen Organs kommunizieren sonder nur mit der Organfunktionalität (im Prinzip eine Art "Fassade").

Bei einer Kommunikation über Botschaften ist natürlich die Definition der Botschaften und des Protokolls wichtig. Aus meiner Arbeit kenne ich den CAN - Bus als eine allgemeine Kommunikationsschnittstelle.
(Der CAN Bus ist ein Broadcast System. Jeder Teilnehmer, kann Botschaften Senden und jeder Teilnehmer erhält auch alle Botschaften. Nun liegt es an jedem Teilnehmer selber auf welche Botschaften er "hört"
und wie er sie dann weiter verarbeitet). Eines der wichtigsten Teilaufgaben bei der Entwicklung eines solchen Kommunikationsverbundes liegt in der Definition der Kommunikationsmatrix (K-Matrix). Sie enthält welche Teilnehmer welche Botschaften sendet und welcher Teilnehmer auf welche Botschaften angewiesen ist
und wie die Struktur(Protokoll) der Botschaften ist und in welchem zyklus der Nachrichtenaustausch erfolgt. Die Entwicklung der einzelnen Steuergerät erfolgt dann sehr unabhängig voneinander meist sogar durch unterschiedliche Firmen. Ich denke schon dass die Abhängigkeit in der Entwicklung reduziert wird aber der Entwicklungsfokus muss auf die ausgetauschten Botschaften gelegt werden. Eine Ähnliche Architektur habe ich schon mal in einem Programm gesehen, als schwierig empfand ich dieses Programm mit den heutigen mitteln zu debuggen.

Gruß Thomas

Ralf Westphal - One Man Think Tank hat gesagt…

@Thomas: Hört sich gut an. Ich sehe das auch so mit der Hierarchie (genauer: Holarchie). Aus Organellen werden Zellen, aus Zellen Organe, aus Organen Lebewesen, aus Lebewesen Organisationen/Gesellschaften.

Die Ebenen der Holarchie bezeichne ich mit Stratum.

Dann findet Kommunikation vordringlich zwischen Holons im selben Stratum statt: Organe mit Organen, Zellen mit Zellen usw. Zumindest konzeptionell. In realita kommunizieren natürlich immer nur die Holons im untersten Stratum miteinander, die Blätter des Holarchiebaums. Woanders findet ja nichts statt :-)

Einen Bus sehe ich allerdings nicht durchgängig als das beste Kommunikationsmedium an. Wenn es konkrete Verarbeitungssequenzen gibt, dann verschwimmen die in der K-Matrix. Das finde ich nicht gut. Eine Matrix bzw. das Abonnieren von Nachrichten ist aus meiner Sicht deshalb eher etwas für entferntere und flexiblere Verbindungen zwischen Holons; da, wo die Kopplung besonders lose sein soll. "Im Nahbereich" ziehe ich konkrete Flows vor.

Thomas Gey hat gesagt…

Hallo Ralf,
jetzt stellen sich mir meherer Fragen, damit ich dich besser verstehen kann würde ich mich freuen wenn du diese beantworten könntest. Wie du geschrieben hast "In realita kommunizieren natürlich immer nur die Holons im untersten Stratum miteinander, die Blätter des Holarchiebaums. Woanders findet ja nichts statt" Wie kann ich mir die Aufgabe der Strata vorstellen?
Einfach nur als Teil - Ganzes Hirarchie und als Gruppierung von Funktionalität? Im Endeffekt als Grundlage um die gruppierte Funktionalität in anderen Projekte wieder zu verwenden? Um die Übersicht die ich auf dem Papier erzeuge im Quellcode wiederzufinden?
Und meine 2. Frage ist
Wie funktioniert die Transformation der Funktionalität auf eine abstraktere Ebene? Ich wähle mal ein schlechtes Beispiel. Zellen filter Nährstoofe und Sauerstoff aus dem Blut und bilden Energie. Das Herz welches aus Zellen aufgebaut ist pumpt das Blut.
Die Zellfunktion ist "aus Nährstoffen und Sauerstoff Energie zu bilden"
Die Organfunktion ist "mit Energie das Blut zu pumpen"
Liegt die Transformation dann in der Ausgetauschten Nachricht (DTO)? Ich baue mir eine Nachricht,villeicht auch schön nach OOD Ansatz.
z.B. Nachricht 1 => Blut besteht aus Nährstoffen und Sauerstoff. Nachricht 2 => Energie. Dadurch können die Zellen etwas
damit Anfangen, sie verarbeiten die Information Nährstoffe und Sauerstoff zu Energie und mit dem DTO Blut kann die höhere Strata
etwas anfangen, sie pumpt Blut? (aber wo liegt die Funktion "Blut pumpen")
Ist es so angedacht oder denke ich damit noch in eine andere Richtung ?

Gruß Thomas

Ralf Westphal - One Man Think Tank hat gesagt…

@Thomas: Strata haben keine Aufgabe. Es nur Abstraktionsebenen. In einem Stratum (auf einer Ebene) liegen einfach Funktionseinheiten mit einem gewissen ähnlichen Abstraktionsgrad.

Tun tun nur Funktionseinheiten auf der untersten Ebene, die Blätter. Keine Funktionseinheit auf anderer Ebene fängt irgendwas mit Daten an.

Anonym hat gesagt…

In Wahrheit ist das Prinzip uralt: Eingabe -> Verarbeitung -> Ausgabe. Kurz EVA. Kennt jeder Gymnasiast ... naja, zumindest sollte er das :-)

JensG hat gesagt…

PS: Das macht es natürlich nicht unsympathischer. Ich wollte das nur mal (völlig wertungsfrei) einwerfen. Nicht daß uns noch über den ganzen Holons und Strati der Blick für den wesentlichen Kern der Sache verlorengeht.

Anonym hat gesagt…

Hallo Ralf,

aus welchem Grund wählst Du im IHolonMessage-Interface einen string als Datentyp für den Nachrichtentyp?
Wo/wie werden die vercshiedenen Nachrichtentypen verwaltet? Hast Du dafür auch einen Vorschlag (-> mit Sicherheit hast Du den)?

Ich würd mich freuen, wenn Du Deine Ausführungen dazu noch ein wenig erweiterst und vielleicht ein kleines Beispiel bringen könntest.

Gruß
Mirko Schneider

Ralf Westphal - One Man Think Tank hat gesagt…

@Mirko: Der string ist eine ganz allgemeingültige Möglichkeit, einen Nachrichtentypen zu beschreiben, d.h. den Nachrichtendaten Semantik zu geben. In den string kannst du ein Wort reinschreiben oder eine URI. Wie du magst.

Die Typen verwalten? Dazu habe ich keinen Vorschlag. Was immer angemessen ist. In Excel? Oder in einer Datenbank? Kommt auf die Anwendung an. Aber erstmal nicht so groß denken. Von unten rantasten. Ausprobieren. Nicht schon am Anfang jedes Detail kennen und gelöst haben wollen. Spielerisch entwickeln.

Anonym hat gesagt…

Moin Ralf,

da muss ich doch glatt ein wenig schmunzeln - also, hmm, mir ist schon klar, wofür ein string benutzt werden kann, und das das "eine allgemeingültige Möglichkeit, einen Nachrichtentyp zu beschreiben" ist ;-).

Ich hatte meine Frage unklar formuliert. Was die Verwaltung der Nachrichtentypen angeht, wollte ich auf folgendes abzielen...

Jeder Nachrichtenempfänger muss bei Erhalt einer Nachricht den Nachrichtentypen auswerten, um einen entsprechenden Cast auf die übermittelten Daten ausführen zu können. Diese werden ja schließlich als object übergeben.

Nun ist's recht unschön (gelinde ausgedrückt) den übermittelten Typ mit hartcodierten Strings zu vergleichen, so dass es sich natürlich anbietet, die Begriffe für die Nachrichten zumindest in einer Klasse als Konstanten zu hinterlegen, sprich in einer Klasse zu verwalten.

Jedes Holon der Anwendung benutzt vermutlich nur einen eben für dieses Holon relevanten Satz der Konstanten in der Klasse.
Macht es Sinn ein Holon mit der Möglichkeit auszustatten, Nachrichten zu senden, deren Kontext gar nicht zum Holon passt (denn es hat ja Zugriff auf alle Nachrichtentypen)?

"Aber erstmal nicht so groß denken. Von unten rantasten. Ausprobieren. Nicht schon am Anfang jedes Detail kennen und gelöst haben wollen. Spielerisch entwickeln."

Beeindruckend - da triffst Du genau den Nerv - das ist regelmäßig mein größter Stolperstein.
Drum Folge ich jetzt Deinem Appell und spiele damit rum ;-)

Gruß
Mirko Schneider

Ralf Westphal - One Man Think Tank hat gesagt…

@Mirko: Die Auswertung des Typs muss nicht unbedingt die Funktionseinheit selbst machen. Das ist eher eine Frage der Infrastruktur, die drumrum sitzt.

Ich habe nur die aus meiner Sicht kleinstmögliche und allgemeingültigste Form von Flow Funktionseinheiten beschrieben.

Damit will ich nicht behaupten, dass eine Implementation für dich nicht so einfach aussehen kann wie:

string[] Laden_Zeilen_aus_Datei(string dateiname) {...}