Gedanken zum perfekten Software-Design

Written on December 03, 2008

Urban Cavity

Image by night86mare via Flickr

Nachdem Peter und Norbert ihre Gedanken zum "perfekten Software-Design" geäußert haben, möchte ich jetzt noch meine Sicht der Dinge beitragen ;-)

Was war -- was wird

Bis vor ca. einem Jahr hätte ich Norbert in seiner Argumentation sicher weitestgehend zugestimmt, ohne etwas hinzuzufügen. Peters Ansichten hätte ich vermutlich mit den Worten "wenn man will, geht es auch mit gutem Design von Beginn an" widersprochen.

Allerdings bin damals ich durch Albert während einer Messenger-Unterhaltung über das MVC-/MVP-Pattern auf die englischsprachige (und in Deutschland zu diesem Zeitpunkt gerade entstehende) ALT.NET-Bewegung aufmerksam geworden.

Beim Launch Event von Visual Studio 2008 in Frankfurt traf ich dann unter anderem Stefan Lieser, einen der "Gründer" der ALT.NET-Bewegung in Deutschland, persönlich und bekam so einen tieferen Einblick in die Denkweisen und Prinzipien von ALT.NET.

Besser gehts nicht -- oder doch?

Zunächst möchte ich kurz zitieren, was ALT.NET ist:

  • ALT.NET Entwickler verwenden was funktioniert und suchen ständig nach neuen, besseren Lösungen.
  • ALT.NET Entwickler bewegen sich auch außerhalb des Mainstream und verwenden Lösungen, Konzepte, Ideen aus allen Bereichen (Open Source, Agile, Java, Ruby, etc.).
  • ALT.NET Entwickler geben sich mit dem status quo nicht zufrieden und suchen ständig nach Möglichkeiten sich in ihrem Code besser, einfacher und eleganter auszudrücken.
  • ALT.NET Entwickler halten Tools für wichtig, wirklich wichtig sind aber fundierte Kenntnisse und Prinzipien. Die besten Tools sind solche, die einen bei der Anwendung der Prinzipien unterstützen.
  • ALT.NET ist nicht kontra Microsoft und auch nicht alternativ. Es geht uns darum aus den möglichen Alternativen die jeweils beste auszuwählen. Das schließt Lösungen von Microsoft ausdrücklich mit ein, genauso wie Open Source und Third Party Hersteller.

Doch was bedeutet dies in der Praxis für einen .NET-Entwickler?

Im konkreten Fall (d.h. bei mir) bedeutete es: Lernen, lernen und nochmals lernen.

Vermutlich bedingt durch meinen Weg als Quereinsteiger (beruflich betrachtet - Assembler, Turbo Pascal u.ä. in der Jugend) über HTML, Classic ASP und später ASP.NET mittels learning by doing waren mir viele Prinzipien der Software-Entwicklung nicht bekannt.

Hierbei meine ich explizit nicht die GoF-Patterns oder Mehrschichten-Architekturen, sondern z.B. die S.O.L.I.D.-Principles:

  • SRP: Single Responsibility Principle

"Es sollte niemals mehr als ein Grund für Änderungen an einer Klasse existieren" (Beispiel 1, Beispiel 2)

  • **OCP: Open Closed Principle

**"Entitäten (Klassen, Module, Funktionen etc.) sollten offen für Erweiterungen und geschlossen gegen Modifikationen sein" (Beispiel 1, Beispiel 2)

  • LSP: Liskov Substitution Principle

"Subtypen müssen sich verhalten wie ihr Basistyp" (Beispiel)

  • ISP: Interface Segregation Principle

"Clients sollen nicht gezwungen werden von Interfaces abhängig zu sein, die sie nicht benötigen" (Beispiel)

  • DIP: Dependency Inversion Principle

"High-Level-Klassen sollen nicht von Low-Level-Klassen abhängig sein. Beide sollen von Interfaces abhängig sein"

"Interfaces sollen nicht von Details abhängig sein. Details sollen von Interfaces abhängig sein" (Beispiel)

Auf dem Weg zur Erleuchtung

"Erleuchtung" ist sicher etwas hoch gegriffen, das eine oder andere mehr oder weniger heftige Aha-Erlebnis ist aber sicher vorprogrammiert, wenn man den oben beschrittenen Weg weitergeht und sich dann als logische Konsequenz daraus mit Test-Driven-Development (TDD) und auch Domain-Driven-Design (DDD) befasst.

mutopia

Image by john curley via Flickr

Das Mantra von TDD: Red. Green. Refactor.

buddhist mantra

Image by alles-schlumpf via Flickr

Das grundlegende Prinzip von TDD besteht darin, in kleinen Iterationen Unit-Tests und die damit zu testenden (Code-)Einheiten zu entwickeln.

Hierzu wird immer wie folgt vorgegangen:

  • Man schreibt einen Test, der die zu implementierende Funktionalität prüft. Dieser Test soll bewußt fehlschlagen, weshalb die zu testenden Funktionalität die Test-Bedingungen nicht erfüllen bzw. erst gar nicht implementieren soll (=Red (Red, da in den grafischen Oberflächen der Unit-Testframeworks fehlgeschlagene Tests rot markiert werden)).
  • Man ändert die Implementierung der gewünschten Funktionalität so, dass der zuerst geschriebene Test erfolgreich durchläuft (=Green (erfolgreiche Tests werden Grün markiert)).
  • Aufräumen (=Refactoring) des geschriebenen Codes, der die Funktionalität implementiert. Hierzu zählt z.B. das entfernen von doppeltem Code (DRY, Smell), oder das Einführen von Abstraktionen.

Domain Driven Design -- des Pudels Kern

Die Kernaussage bei Domain Driven Design ist, dass komplexe Problemlösungen auf einem Model basieren sollten und dass bei (den meisten) Software-Projekten die Problem-Domäne und die Domänen-Logik im Mittelpunkt der Lösungen stehen sollten (und nicht wie häufig üblich nur einen eben auch notwendigen Teil der Implementierung darstellen).

Hierdurch rückt die Implementierung von Infrastruktur-Komponenten wie Persistenz, Logging usw. in den Hintergrund, da diese praktisch zuhauf in Form von OR/M-Mappern wie z.B. NHibernate oder Log4Net bereits implementiert sind und in sehr vielen Fällen einfach wiederverwendet werden können.

Häufig beginnen aber viele Software-Projekte (wie auch meine bisherigen) mit dem Design der Datenbank-Struktur. Dies führt allerdings oft -- um nicht zu sagen: immer - dazu, dass man sich aufgrund der Struktur von Datenbanken und deren Objekten in der Implementierung der Domänen-Logik von Beginn an einschränkt bzw daran ausrichtet und so Möglichkeiten für künftige Anpassungen (die IMMER passieren -- die Frage ist nicht "ob", sondern "wann") verbaut.

Ein weiterer Grund ist schlichtweg, dass man bei einem Software-Design niemals alle Eventualitäten vorab berücksichtigen kann. Was man aber tun kann, ist die Domänen-Logik und die gesamte Implementierung soweit als möglich für Änderungen offen zu halten.

Deshalb ist es meiner Meinung nach auch nicht sinnvoll, mit großem Aufwand ein Design zu Beginn (BDUF) Entwicklung zu erstellen, sondern vielmehr dieses den sich ändernden Anforderungen anzupassen.

Zurück zum Anfang

Exposed gnarly roots in Fall River Park

Image by Martin LaBar (going on hiatus) via Flickr

Um nun wieder den Bezug zu Norbert's und Peter's Postings herzustellen:

In Anbetracht von TDD und DDD stellt sich für mich die Frage nach der Architektur zu Beginn der Entwicklung nicht mehr zwingend, da sich diese nach Bedarf ergibt bzw. sich ändert (und auch ändern können muss).

Deshalb bin ich für mich von Visio als Architektur-Design-Hilfe ebenso ebenso abgekommen (stattdessen benutze ich jetzt ein Moleskine Skizzenbuch, in das ich mir mit einem Bleistift meine Entwürfe "kritzle") wie von klassischen Wasserfall-Entwicklungs-Methoden.

Die von Peter zitierte Aussage von Phil Haack

"If a project doesn't have a time constraint, it will never get finished."

würde ich nun eher so definieren:

"Ein Software-Projekt ist niemals fertig, da es immer wieder Veränderungen gibt."

Just my 2 cents ;-)