Porträt Software-Design

17.09.2006 Permalink

Zusammenfassung:
Dieser Text beleuchtet die wichtigsten Aspekte von Software-Design, derer sich jeder, der professionell mit Software-Entwicklung zu tun hat, bewusst sein sollte. Design und Design-Entscheidungen sind allgegenwärtig, ein breites, gelebtes Verständnis, was Design ist und wie man es herstellt, hat sich in realen Projekten aber kaum etabliert. Der Autor gibt einige klare Richtlinien zur Tätigkeit "Entwerfen" und dem Umgang mit dem Ergebnis.

Einleitung

Jeder im Java-Umfeld beherrscht objekt-orientiertes Design und Design-Patterns. Zumindest wird das immer erwartet und häufig als Fähigkeit angegeben. Wenn es aber ganz praktisch und konkret um die Erstellung, Dokumentation und Bewertung von Design geht, betritt man -- so meine Wahrnehmung -- scheinbar ungesichertes Terrain. Und jetzt mal Hand auf's Herz: wer von uns hat denn schon mal ein gutes Design in einem Projekt gesehen oder sogar selbst erstellt? Allzuviele Beispiele für einen bewussten Software-Entwurf sind mir leider nicht begegnet...

Woran liegt das? Vielleicht daran, dass es im praktischen Gebrauch keinen fundierten Konsens gibt, was Design ist und wie man eines herstellt. Der Body of Knowledge ist vorhanden, aber versprengt über viele lesenswerte Bücher. Die typische Informatiker-Ausbildung berührt dieses Thema selten in ausreichender Tiefe. Informatiker und Quereinsteiger lernen aus der Praxis, in der -- wie oben angedeutet -- gerne auf bewusstes Entwerfen verzichtet wird. Das führt zu Systemen mit schlechtem Design, die dadurch schwer zu verstehen und noch viel schwerer zu ändern sind. Sozusagen: kaum fertiggestellt, schon Legacy. Keine Spur mehr agil.

Die gehypten technologischen Ansätze der letzten Jahre wie Komponentenmodell-Implementierungen, MDA und SOA können eine Verbesserung bewirken. Ich meine aber: Grundlagenkenntnisse, wie man verständliche und wartbare Software-Systeme baut, sind ein Schlüssel, um vielversprechenden Ansätzen zu ihrer vollen Wirkung zu verhelfen. Denn ein Design zeigt uns einen Systemaufbau unter Weglassung bestimmter Details. Wir sind in der Lage, ihn zu diskutieren und zu verbessern, ohne durch tausende von Einzelaspekten abgelenkt zu werden.

Genug der warmen Worte. Hier meine persönliche Porträtierung:

Was ist Design?

Design ist die Zerlegung eines Systems in Subsysteme (Komponenten, Services, Pakete, Klassen), die über Schnittstellen ihren Verantwortlichkeiten entsprechend zusammenspielen, um Anwendungsfälle zu realisisieren.

Ein Subsystem (Komponente, Service, Paket, Klasse) besitzt eine Schnittstelle, die die Verantwortlichkeit widerspiegelt. Sie besitzt ein Innenleben, das für Verwender verborgen bleibt.

Jedes System (oder eines seiner Bestandteile) besitzt ein Design, ob auf Papier dokumentiert, in generierungsfähigen UML Modellen oder ausschließlich im Code realisiert. In einem System stecken immer viele hundert Design-Entscheidungen, egal auf welcher Ebene getroffen und ob bewusst oder unbewusst.

Hierarchie

Die Fähigkeit von Menschen, das Zusammenspiel verschiedener Teile zu verstehen, ist begrenzt. Übersteigt die Anzahl der Subsysteme (Komponenten, Services, Pakete, Klassen) oder deren Abhängigkeiten eine bestimmte Grenze, so werden die Möglichkeiten schnell stark eingeschränkt, das System zu verstehen, zu kommunizieren und zu verändern. Um das zu vermeiden, verwenden Menschen das Prinzip Divide-and-Conquer, um verschiedene Abstraktionsebenen mit jeweils überschaubarer Komplexität zu schaffen.

Deshalb ist Design hierarchisch. Jedes Subsystem (Komponente, Service, Paket, Klasse) besitzt seinerseits ein Design, das geht hinunter bis zu den kleinsten Building-Blocks eines Systems.

Wirkende Kräfte auf ein Design

Die Schnittstelle eines Systems (Subsystem, Komponente, Service, Paket, Klasse) orientiert sich an seinen Verwendern. Die Verwender sind Menschen oder andere Systeme (Subsysteme, Komponenten, Services, Pakete). Die wirkende Kraft für einen geeigneten Entwurf der Schnittstelle sind die Aufgaben, die die Verwender mithilfe des Systems (Subsystems, Komponente, Service, Paket, Klasse) zu erledigen haben. Aus diesen Aufgaben ergeben sich die funktionalen Anforderungen, die durch Anwendungsfallbeschreibungen ausdetailliert werden.

Zwei äußerlich wahrnehmbar (= funktional) identisch arbeitende Systeme können vollständig unterschiedlich aufgebaut sein. Die formenden Kräfte für den Entwurf des inneren Aufbaus sind also die nicht-funktionalen Anforderungen.

Durch das Durchspielen von Anwendungsfällen der jeweiligen Ebene findet man Verantwortlichkeiten, die es zu verteilen gilt. Jede Verantwortlichkeit wird einem bekannten (oder neuen) Element des Designs zugeordnet. Die nicht-funktionalen Anforderungen zusammen mit den Prinzipien für Kopplung, Kohäsion, Minimalität und Vollständigkeit zeigen uns, ob eine gefundene Zerlegung akzeptabel ist oder weiter verändert werden muss.

Die Erstellung von Code, um die Verwendung der Schnittstelle oder versatzstückweise die Lösung von Problemen zu eruieren, ist ein Bestandteil des Design-Prozesses.

Design-Dokumente

Dem Code ist nur äußerst schwer zu entnehmen, ob ein Design den wirkenden Kräften gerecht wird. Man benötigt für nicht-triviale Lösungen eine Dokumentation, die dies belegt und daraufhin prüfbar ist.

Ein Design-Dokument hat immer ein System (Subsystem, Komponente, Service, Paket, Klasse) zum Gegenstand. In einem Design-Dokument befinden sich zwei Sichten auf den Gegenstand: Außensicht für Verwender, Innensicht für Entwicklung und Wartung. Die Außensicht erläutert den Gebrauch des Systems (Subsystems, Komponente, Service, Paket, Klasse) anhand von Anwendungsfällen und ggf. Codebeispielen, die Innensicht zeigt, wie die Funktionalität unter Berücksichtigung der nicht-funktionalen Anforderungen realisiert wird. Die Innensicht kann insbesondere eine weitere Zerlegung enthalten.

Es gibt zwei Sichten-Kategorien auf ein Design: die statische und die dynamische. Statische Sichten zeigen die Zerlegung und die Beziehungen der Elemente zueinander. Dynamische Sichten zeigen, wie die in der Zerlegung beschriebenen Elemente miteinander die Anwendungsfälle realisieren.

Die Menge aller Design-Dokumente ist ebenso hierarchisch geordnet wie die Bestandteile des Systems.

Ob Design-Dokumente unter Einbindung von Diagrammen mit einer Textverarbeitung erstellt oder ausschließlich durch Pakete in einem Modellierungswerkzeug repräsentiert werden, ist abhängig von der Verbreitung des Modellierungswerkzeugs und dessen Fähigkeiten. Es ist sehr wichtig, die Design-Dokumentation mit wenig Aufwand ändern zu können, ansonsten wird eine Anpassung unterlassen und die Dokumentation verliert ihren Nutzen. Ein Entwicklungsprozess, in dem viel Code-Struktur generiert wird (MDA, MDSD), hilft, das Modell zum natürlichen Teil der Entwicklung werden zu lassen.

Qualität von Design

Seien A und B zwei Subsysteme (Komponenten, Services, Pakete, Klassen), und sei B abhängig von A. Wenn A von B verwendet wird, muss man, um B zu verstehen, auch (die Außensicht von) A kennen. Viele Abhängigkeiten erschweren das Verständnis. Reduziere die Abhängigkeiten!

Ein Subsystem (Komponente, Service, Paket, Klasse), das mehr als eine Verantwortung wahrnimmt, spielt kontextabhängig unterschiedliche Rollen. Das erschwert das Verständnis sowohl der Verwendung als auch des inneren Aufbaus des Elements. Bilde kohäsive Elemente!

Wenn die Abhängigkeiten auf jeder Ebene überwacht werden, wird das System insgesamt besser änderbar und preiswerter zu warten sein. Verwende zur Visualisierung und Überwachung Werkzeuge!

Design Qualität ist teilweise messbar. Design-Metriken sind leicht zu errechnen und zu bewerten. Verwende Metriken wie z.B. Stabilität, Abstraktheit, Anzahl Zyklen, Anzahl Kapselungsverletzungen und Anzahl Klassen pro Paket, um qualitativ bedenkliche Stellen zu entdecken!

Design und andere Knowledge Areas des Software-Engineering

Große Projekte benötigen noch vor der eigentlichen Anforderungs- und Analysearbeit eine Zerlegung, um Analyse-Teams zu bilden. Das ist bereits Design.

Teams werden dem Design entsprechend gebildet. Ein Design mit möglichst wenig technischen Abhängigkeiten führt zu Teams mit wenigen organisatorischen Abhängigkeiten. Die Teams werden ihre Systemteile schneller entwickeln können.

Design zerlegt ein Ganzes in seine Teile. Eine Work-Breakdown-Structure, die essentiell für die Projektsteuerung ist, basiert auf einem Design.

Ein guter Entwurf ermöglicht gute automatisierbare Unittests. Schlechte Testbarkeit ist ein Zeichen eines schlechten Entwurfs. Wird die Qualität des Systems nicht auf der Ebene automatisierter Unittests gesichert, dann werden alle folgenden Teststufen teuer, langwierig und schmerzhaft sein.

Design in der Literatur

Es gibt gute Bücher, die das vorangegangene Kurzporträt konkret und mit vielen Beispielen ausführen. Einige davon sind:

Darüberhinaus gibt es seit Design Patterns (von Gamma et al, Addison-Wesley, 1995) eine stetig wachsende Zahl von Design-Mustern, die in unregelmässigen Abständen in Büchern zusammengefasst werden, z.B.: