Methodik des Softwarebaus

Mehr Regeln wagen

Wie beim Hausbau braucht auch die Entwicklung von Software Konventionen, um Qualität erreichen zu können. Leider gibt es derzeit davon zu wenig. Mehr Konventionen würden den Entwickler besser leiten, ihm Arbeit und Entscheidungen abnehmen sowie mehr Zeit verschaffen, sich um das eigentliche Problemfeld der Software zu kümmern.

Auf einen Blick
center

Ralf Westphal ist freier Softwaretechnologievermittler im Bereich .NET-Softwarearchitektur. Er arbeitet als Fachautor, Coach/Berater und Referent auf Entwicklerevents. Sie erreichen ihn über www.ralfw.de.

Inhalt:

  • Beim Entwickeln von Software gibt es zu wenig allgemeingültige Regeln.
  • Das kostet Zeit und Geld.
  • Objektorientierung allein reicht nicht aus.
  • Die Schachtelung von Komponenten würde weiterhelfen.
dotnetpro.code: A0806Kolumne



Stellen Sie sich vor, zwei einander unbekannte Softwareentwickler kommen über ihre Projekte ins Gespräch. Was würden sie wohl für Gemeinsamkeiten in der Realisierung ihrer Applikationen feststellen? Beide könnten für dieselbe Plattform entwickeln: Windows und .NET Framework; beide könnten an einer Anwendung derselben Art arbeiten, zum Beispiel eine Desktop- oder Webanwendung; beide könnten Daten in einer relationalen Datenbank speichern.

Und weiter? Sicherlich unterscheiden sich die Problemdomänen, an denen die Entwickler arbeiten. Der eine könnte eine CRM-Software und der andere Immobilienverwaltung schreiben. Sind also nicht mehr Gemeinsamkeiten bei zwei Softwareanwendungen zu erwarten?

Ein paar mögen noch unterhalb der Bewusstseinsschwelle liegen. Sie sind so natürlich, dass die beiden Softwareentwickler sie nicht für erwähnenswert halten. Zum Beispiel programmieren sie beide objektorientiert, zeichnen UML-Diagramme und halten die Normalisierung einer Datenbank für wichtig. Und natürlich haben ihre Anwendungen dieselbe Grundstruktur: Sie bestehen aus einer Benutzeroberfläche, greifen auf Infrastruktur zu und implementieren eine problemdomänenspezifische Logik.

Auf der Oberfläche der Anwendungen würde ein Laie sicherlich grundsätzlich dieselben Steuerelemente finden: Eingabefelder, Menüs, Schaltflächen, Tabellen. Aber darunter? Ist jenseits von Objektorientierung und einer groben Dreigliederung des Codes keine strukturelle Gemeinsamkeit mehr zu erwarten? Vermutlich nicht. Die beiden mögen vielleicht hier und da noch dieselben Entwurfsmuster einsetzen -doch darüber hinaus wird es kaum mehr Gemeinsamkeiten geben.

Jenseits der Werkzeuge und der durch sie vertretenen Konzepte herrscht wenig Konsens in der Softwareentwicklung. Compiler schaffen Gemeinsamkeit bei der Sprache mit ihrem fundamentalen Programmierparadigma. IDEs schaffen Gemeinsamkeit beim Aufbau von Benutzeroberflächen und der Quellcodegrundstruktur. Datenbanken schaffen Gemeinsamkeit bei der Datenorganisation.

Wo Werkzeuge oder Konzepte unbekannt oder nicht weit verbreitet sind, herrscht deshalb im Umkehrschluss keine Gemeinsamkeit; weder in der Vorgehensweise noch in Bezug auf die Produktion oder in Architekturfragen. Die Agilitätsbewegung bemüht sich zwar; Team Foundation Server & Co bemühen sich auch; aber es ist unwahrscheinlich, dass die beiden Entwickler in ihrem Gespräch hier schon viele Gemeinsamkeiten entdecken würden. Und noch unwahrscheinlicher ist es in Bezug auf den grundsätzlichen Aufbau ihrer Anwendungen, also ihre Architektur.

Diese Abwesenheit von Gemeinsamkeiten halte ich für überraschend und kontraproduktiv. Nach 50 Jahren Softwareentwicklungspraxis ist die Zahl der Gemeinsamkeiten zwischen zwei Anwendungen immer noch an einer Hand abzählbar. Das gibt es in keiner anderen Branche. Der Ruf nach Standards ist allerorten zu hören, von den Programmiersprachen über die Kommunikation bis zu Office-Dokumentenformaten; was den inneren Aufbau einer Anwendung angeht, verhallt er ungehört.

Das Resultat: geringe Produktivität. Denn wo kein Konsens herrscht, ist immer erst ein Weg zu bahnen. Das kostet Ressourcen.

Kreativer und produktiver mit Regeln

Mehr Konsens und mehr Systematik tun Not. Softwareentwicklung sollte in mehr Aspekten klaren Regeln und typischen Lösungen folgen, die in mindestens 80 Prozent der Fälle zu ausreichenden Implementierungen führen. Die Normalisierungsregeln für relationale Datenbanken sind dafür vielleicht das beste Beispiel: Sie machen klare Vorgaben, sind leicht umzusetzen, führen zu verständlichen Strukturen und sorgen für Wartbarkeit. In den meisten Fällen sind die sich daraus ergebenden Datenbanken auch effektiv genug. Falls nicht, ist es auch kein Verrat, eine Regel auch mal zu brechen. Den grundsätzlichen Konsens beeinträchtigt das nicht. Er hilft, den Kopf für das Wesentliche, die Problemdomäne, freizuhalten.

Ähnliches gilt auch für die Objektorientierung. Sie ist Konsens, was das allgemeine Programmierparadigma angeht. Dass zwei Entwickler für ein Problem unabhängig voneinander eine objektorientierte Sprache wählen, ist sehr wahrscheinlich. So wird es einfach, produktive Teams zusammenzustellen.

Entwurfsmuster haben das Ziel, Gleiches auf einer höheren Abstraktionsstufe zu wiederholen. Sie definieren ein Vokabular für wiederkehrende Probleme mit probaten Lösungen. MVC und Schichten sind vielleicht die bekanntesten Beispiele. Beide sind übersichtlich und leicht verständlich, beide haben ein klares Anwendungsgebiet. Beim Vorgehensmodell könnte Scrum [1] das Zeug dazu haben, ein konsensfähiges Minimalmuster zu werden.

Konventionen und Regeln bedeuten allerdings nicht sklavisches Befolgen. Sie sollen zunächst nur den Blick leiten und Ordnung in das Chaos bringen. Die schnelle 80-Prozent-Lösung ist ihr Zweck. Sie können selbstverständlich gebrochen werden. Denormalisierung ist nicht nur erlaubt, sondern manchmal einfach geboten. Wer allerdings eine Regel bricht, muss sich erklären. Und das ist gut so. Das schafft Bewusstsein und Verantwortungsgefühl für das, was man tut.

Mehr Regeln machen das Leben leichter, weil sie uns Entscheidungen abnehmen. Sie schaffen sogar Spielräume. Fragen Sie einen Künstler: Der wird Ihnen sagen, dass Kreativität von Beschränkung profitiert, ja, sie geradezu braucht.

Regeln sind auch konform mit der Philosophie der Lean Production und der Agilitätsbewegung, Entscheidungen so spät wie möglich zu treffen. Denn wer nach einer Regel handelt, der muss eigentlich noch keine "richtige" Entscheidung treffen. Er vertagt sie vielmehr, bis es ein Problem mit der Regel gibt. Da das aber qua definitionem selten der Fall ist - sonst wäre eine Regel ja keine Regel, allemal keine gute -, spart er in den meisten Fällen Zeit und Nerven.

Regeln für den Anwendungsbau

Zurück zu den beiden Softwareentwicklern im Gespräch. Mit welcher Konvention würde es dem einen leichter fallen, im Team des anderen mitzuarbeiten? Mehr Konsens in Bezug auf den grundlegenden Aufbau von Software wäre da sicherlich eine große Hilfe.

Eine "Konsensklammer" gibt es schon (Abbildung 1): Plattform und fundamentales Programmierparadigma. Beide Entwickler sind sich auch einig über die Art der Applikation und die Codekategorisierung durch Schichten. Benutzerschnittstelle, Domänenlogik oder Infrastrukturzugriff sind so allgemein, dass diese grobe Einteilung von Code für jede Applikation gelten kann.

center

[Abb. 1] Konsens herrscht im Fundamentalen und sehr Allgemeinen. Dazwischen liegt allerdings eine weitgehend konventionsfreie Zone.

Konsens im Fundamentalen, Konsens im Allgemeinen. Aber dazwischen klafft eine Lücke, die ressourcenfressende Entscheidungen fordert, weil Konventionen fehlen. Sie zu schließen, würde vielen Entwicklern helfen, sich wieder mehr auf das Wesentliche konzentrieren zu können und produktiver zu arbeiten.

Es geht um einen vergleichsweise kleinen Schritt über die Objektorientierung hinaus. Um etwas, das auf der Objektorientierung aufbaut, deren Werte also nicht negiert; was in Theorie und Praxis verständlich ist. Die Praxis ist sogar sehr wichtig - in Form von Konventionen für die Codierung, weil ansonsten der mühevolle Transfer theoretischer Überlegungen in die Coderealität wieder die Produktivität senkt.

Regeln, welche die festgestellte Lücke verkleinern, sollen einerseits anleiten-, also Unsicherheit nehmen -, andererseits Eigenverantwortung betonen. Konzepte sollten dabei von Werkzeugen unterstützt werden, wo es geht, andererseits aber natürlich weitgehend herstellerneutral sein.

Als Analogie mag der Hausbau dienen. Da gibt es nicht nur grundlegende Konventionen, was die Baumaterialien angeht, und allgemeinen Konsens über die Einteilung eines Hauses in Fundament, Wände und Dach. Die Gemeinsamkeiten zwischen Häusern - auch ganz unterschiedlicher Art - gehen viel weiter. Jedes Haus hat Wasseranschluss und führt Rohre in verschiedene Räume; jedes Haus hat Anschluss ans Elektrizitätsnetz und führt elektrische Leitungen in alle Räume; jedes Haus hat eine Heizungsanlage und führt Heizungsrohre in alle Räume. Ab einer bestimmten Höhe hat jedes Haus Aufzüge, die alle Stockwerke bedienen und dazu ihre Schächte und den Antrieb.

So viele Gemeinsamkeiten, so viele Konventionen - und doch so viel Freiheit. Denn was Sie in den einzelnen Räumen tun, ist Ihnen freigestellt; welche Geräte Sie an die elektrischen Leitungen anschließen, entscheiden Sie. Die Gemeinsamkeiten im Hausbau betreffen eine Infrastruktur, die dazu dient, sich in den Räumen zu entfalten. Sie sollen sich keine Gedanken mehr darüber machen müssen, wie Sie es hell, warm, sauber und bequem haben könnten.

Die Konventionen sorgen dafür, dass ein Haus Bedürfnisse verschiedener Kategorien erfüllen kann. Sie schaffen sozusagen schon die Bedingungen für die Möglichkeit von Dienstleistungen, die zum Beispiel mit Wasser oder Elektrizität zusammenhängen. Welche Dienstleistungen das dann sind, ist egal. Strom und Wasser kommen über Standardanschlüsse ins Haus. Aus den Leistungen, die diese Standards unterstützen, können Sie dann frei wählen.

Genau darum geht es beim Plädoyer für mehr Konventionen bei der Softwareentwicklung: Sie sollen es leichter haben, "Dienstleistungen" in Ihrem "Softwarehaus" zu installieren und zu verbinden. Oder sogar noch grundlegender: Sie sollen überhaupt erst einmal in Dienstleistungen denken - Services statt Verkabelung. Welche Services jedoch, ist egal. Wählen Sie nach Belieben und machen Sie sich keine Sorgen über die Verbindungen. Für die Verkabelung und die Anschlussfähigkeit sorgen die Prinzipien und Technologien des erweiterten Konsens.

Insofern geht es mir nicht um ein Application Framework mit Konventionen für Persistenz, Sicherheit, Benutzerschnittstelle und so weiter. Das wären schon konkrete Dienstleistungen. So verständlich es ist, Arbeit sparen zu wollen, indem Sie sich auf solche Vorgaben einlassen - der Unterschied zwischen Konvention und Korsett ist fließend. Und je höher das Abstraktionsniveau einer Konvention, desto eher stören Sie sich an ihren Beschränkungen. Kompensieren können Sie das dann nur durch eine Einengung Ihrer Bedürfnisse, zum Beispiel im Hinblick auf die Art der Anwendung oder des Bedienmodells.

Wenn Sie also flexibel bleiben und dennoch Produktivitätsgewinn aus mehr Regeln ziehen wollen, dann müssen Sie die Konventionenlücke aus Abbildung 1 schrittweise von unten her schließen.

Objektorientierung skaliert nicht

Objektorientierung im Verein mit Entwurfsmustern und UML ist in weiten Bereichen der Softwareentwicklung der kleinste gemeinsame Nenner. Konzepte und Werkzeuge sind in den Lehrplänen der Ausbildungsinstitute angekommen. Das ist gut so. Allerdings ist es nicht genug. Auf die Objektorientierung will natürlich niemand mehr verzichten. Da machen selbst dynamische Sprachen oder funktionale Programmierung keinen Unterschied. Auch die haben sich die Objektorientierung zu eigen gemacht. Objektorientierung leidet jedoch fundamental unter einem Defizit: Sie bietet kein Mittel zur Beschreibung von Software auf mehreren Abstraktionsebenen. Objekte und Klassen sind ihre einzigen Strukturelemente. Das ist so, als hätten Sie nur Legosteine, um ein Haus zu bauen, und nur Bilder von Lego-steinen, um das Haus vorher zu planen.

Kompliziertes und allemal Komplexes ist aber nur zu meistern, wenn dazu nicht nur Bausteine von nur einer Granularität zur Verfügung stehen. In Abbildung 2 [2] sehen Sie ein - ja was? Ein Auto. Aber das erkennen Sie vor allem an den Reifen. Ohne die Reifen würden Sie lange rätseln, was all die kleinen Bausteinchen zusammen wohl ergeben würden. Da helfen auch die erkennbaren Beziehungen zwischen ihnen nichts.

center

[Abb. 2] Das Ganze verliert sich im Wald der Details.

Die Explosionszeichnung enthält nur eine Beschreibungsebene - und zwar die niedrigst mögliche, die der unteilbaren Bauteile. Auf demselben Niveau bewegt sich eine Softwarebeschreibung mittels Klassendiagramm. Auch dort sind alle Strukturelemente auf demselben Niveau -nur sind deren Beziehungen noch komplizierter. Da gibt es nicht nur Assoziationen und Aggregationen wie in Abbildung 2, sondern auch noch Spezialisierungen. Außerdem sind die Beziehungen womöglich noch mit Rollen und Aritäten annotiert. Nicht zu vernachlässigen ist auch, dass alle Klassen/Objekte in Softwarediagrammen grundsätzlich gleich aussehen; Sie können also nicht einmal aus ihrer Form Rückschlüsse auf den Zweck ziehen, wie dies Abbildung 2 noch erlaubt.

Etwas Komplexeres als ein Auto beschreiben wir also nur mit demselben, überkommenen Mittel. Wie wenig hilfreich das ist, zeigt Abbildung 3 [3]. Selbst der Hinweis, dass es sich dabei um eine Persistenzbibliothek handelt, macht die Sache nicht viel klarer.

center

[Abb. 3] Die Software verliert sich im Wald der Klassen.

Wenn ein Ganzes unbekannt ist, dann nützt es nichts, seine Details zu zeigen. Denn das Ganze ist mehr als die Summe seiner Teile. Erst im Lichte des Zwecks des Ganzen ergeben Teile nämlich Sinn. Sehen Sie nur die Teile mit ihrem Teilzweck, dann ist es mühsam bis unmöglich, aus den vielen Teilen den Sinn hinter ihrer Existenz abzuleiten.

Kompliziertes und Komplexes auf mehreren Ebenen zu beschreiben, von denen die Bauteilebene nur die unterste ist, hat sich deshalb schon lange eingebürgert. Vom Allgemeinen zum Speziellen, vom Ganzen zum Teil, vom Großen zum Kleinen, top-down: So funktioniert verständliche, übersichtliche Darstellung. Dabei ist die wichtigste Beziehung zunächst einmal die Enthalten-Beziehung. Strukturelemente auf Ebene n enthalten die Strukturelemente auf Ebene n+1. Nur so können Details wahrhaft verborgen werden. Solange Sie sie nicht sehen wollen, sind sie in einem Ganzen versteckt. Die natürliche Darstellung für solche Ebenenvielfalt zeigt Abbildung 4; für viele Ebenen und Details ist dies nur leider umständlich, ja sogar verwirrend. Als probate Alternative hat sich deshalb ein Baum wie in Abbildung 5 erwiesen.

center

[Abb. 4] Darstellung eines Systems und seiner unterschiedlichen Abstraktionsgrade.

center

[Abb. 5] Die übersichtliche Baumdarstellung eines Systems mit mehreren Abstraktionsebenen.

Durch ihn können Sie horizontal Schnitte legen (Abbildung 6), um ein System auf unterschiedlichen Abstraktionsebenen zu beschreiben. Dazu bedarf es allerdings physischer Schachtelung, sonst ist die Darstellung nicht wahrhaftig.

center

[Abb. 6] Horizontale Schnitte durch das geschachtelte System aus Abbildung 5.

Die Objektorientierung bietet nun aber eben nicht mehr als das, was Abbildung 3 zeigt. Punkt. Physische Schachtelung gibt es nicht, auch nicht beim .NET Framework. Private Klassen sollen ein Sonderfall bleiben und Namensraumdefinitionen sind nur "syntactic sugar". Dem mögen Sie nun entgegenhalten, dass es doch Kompositionsbeziehungen gebe. Sie kennzeichnen Objekte als enthalten in einem anderen.

In Abbildung 7 enthält ein Kunde- Objekt Adresse-Objekte, es besteht also - unter anderem - aus ihnen; Adressen haben ohne Kunde keine Existenzgrundlage.

center

[Abb. 7] In Ermangelung echter Schachtelungsmöglichkeit müssen Teil-Objekte über Kompositi onsbeziehungen zu einem Ganzes-Objekt zusam mengesetzt werden.

Diese Denkweise ist jedoch recht künstlich und nur dem Mangel an weiteren Struktur- und physischen Abstraktionsebenen geschuldet. So laufen Modell und Coderealität schnell auseinander. Denn im Code lässt sich diese Beziehung ja nicht physisch ausdrücken. Einer Definition wie der folgenden sieht man die gewünschte Schachtelung nicht mehr an:

class Kunde
{
List adressen;
}

class Adresse { }

In der Objektorientierung fehlt also die Möglichkeit, zwei solche Klassen wirklich in einen "Sack" stecken zu können, der sie tatsächlich "enthält" (Abbildung 8). Das ist misslich, denn solange das nicht geht, gibt es nur Besitz-Beziehungen - also eine Assoziation wie "Ein Kunde hat Adressen" -und keine echten Kompositionen oder Aggregate wie "Ein Kunde besteht aus Kopfdaten und Adressen". Wo aber keine Schachtelung möglich ist, da wird das Denken auf unterschiedlichen Abstraktionsebenen erschwert.

center

[Abb. 8] Echte Abstraktion von den Details einer Entität durch Schachtelung.

Das bedeutet, je umfangreicher oder komplizierter ein System wird, desto schwieriger wird die Modellierung mit den auf ein Abstraktionsniveau festgenagelten Strukturelementen. Mit einem Wort: Ska-lierbarkeit ist keine Sache der Objektorientierung.

Geschachtelte Komponenten

Software muss auf beliebig vielenAbstraktionsebenen darstellbar sein. Die Modellierungselemente müssen daher schachtelbar sein. Modelle laufen immer Gefahr, von der Realität überholt zu werden. Das führt zu Pflegeaufwand beim Modell, weil es immer wieder der Implementierung nachgeführt werden muss. Wenn allerdings die Modellierungselemente gleichzeitig auch Implementierungsartefakte sind, ist dieser Gefahr zu begegnen. Denn dann kann das Modell aus der Coderealität bei Bedarf generiert werden. Die Klassendiagramme in Visual Studio sind dafür ein Beweis.

Um die Anforderung nach Schachtelbar-keit und Modell-Codeartefakt-Dualität zu erfüllen, schlage ich vor, die Konventionenlücke von unten mit Komponentenorientierung zu füllen. Komponente definiert sich für diesen Zweck minimal als:

Das sind die unverbrüchlichen Eigenschaften von Komponenten. Abbildung 9 stellt die ersten drei dar. An dieser Stelle nur stichpunktartig die Argumente für eine Trennung von Kontrakt und Implementierung:

center

[Abb. 9] Komponenten bestehen aus zwei Assemblies - eine für ihren Kontrakt und eine für die Implementierung des Kontrakts.

Die Realisierung solcher Komponenten erfolgt getrennt nach Kontrakt und Implementierung. Für jeden Kontrakt legen Sie ein eigenes Visual-Studio-Projekt an, für jede Implementierung eine eigene Visual-Studio-Projektmappe. Diese Trennung ist wichtig, um einer schleichenden Zunahme der Entropie in einer Anwendung Widerstand entgegenzusetzen. Denn die Entropie, also die Unordnung nimmt immer dann zu, wenn Beziehungen zwischen Codeteilen hergestellt werden. Jede Beziehung - von der Assemblyreferenz über die Instanzierung einer Klasse bis zum Zugriff auf eine statische globale Variable - macht es nämlich schwieriger, Code zu verstehen. Beziehungen, die nicht im Architekturmodell vorgesehen sind, gilt es daher zu vermeiden. Solange große Teile des Codes aber in einer Projektmappe liegen, wie es in vielen Projekten noch der Fall ist, steht dem jedoch nichts im Wege. Deshalb ist es so wichtig, die Trennung von Kontrakt und Implementierung auch in der Codeorganisation zu spiegeln.

Die Schachtelung von Komponenten ist wichtig, um die logischen Abstraktionsebenen eins zu eins in Codeartefakte überführen zu können. Prinzipiell sieht dann die Architektur jeder Anwendung wie in Abbildung 10 aus. Auf drei grundsätzlich verschiedenen Ebenen beschreiben Sie die Struktur Ihrer Software: