Designprozesse

Der dynamikrobuste Softwareentwurf

Seite: 3/3

Firmen zum Thema

Prinzipien, Praktiken und Muster für einen dynamikrobusten Softwareentwurf

Single Responsibility Principle (SRP): Jede Softwareeinheit (synonym: Modul, Klasse, Prozedur, Methode, Funktion, …) sollte nur exakt eine und klar definierte Verantwortlichkeit haben.

Durch Beachtung des SRP erreicht man gleich mehrere Ziele:

Bildergalerie
Bildergalerie mit 5 Bildern
  • Zum einen sind Softwareeinheiten, wie beispielsweise Klassen, die nur exakt eine Verantwortlichkeit haben, kleiner, und somit auch einfacher zu verstehen und zu verändern, als große „Gemischtwarenläden“. Der fachliche Zusammenhalt, die sogenannte Kohäsion, ist in ihnen zumeist sehr stark.
  • Kleinere Softwareeinheiten mit exakt einer Verantwortlichkeit haben für gewöhnlich auch eine schmale, öffentliche Schnittstelle (API), wodurch sich weniger andere Bausteine abhängig machen. Das fördert die lose Kopplung.
  • Kleinere Softwareeinheiten sind viel leichter zu testen, und leichte Testbarkeit ist gleichbedeutend mit leichter (Wieder-)Verwendbarkeit.

Information Hiding (Geheimnisprinzip): Dieses Prinzip wurde bereits 1972 von David L. Parnas als wichtige Leitlinie für eine gut gelungene Modularisierung formuliert[Parnas72].

Es besagt, dass ein Softwarebaustein gegenüber seinen Verwendern nur so wenig wie möglich, idealerweise gar nichts, über seine interne Implementierung preisgeben darf. Machen sich nämlich Softwarebausteine von Implementierungsdetails eines anderen Bausteins abhängig, so sind diese abhängigen Bausteine auch von Änderungen in der Implementierung des unabhängigen Bausteins unmittelbar betroffen. Die Folge wäre, dass sich selbst kleinste Änderungen in der Implementierung entlang der Abhängigkeiten im Entwurf fortpflanzen und zu einer Kaskade weiterer Änderungen führen können.

Acyclic Dependency Principle (ADP): Der Abhängigkeitsgraph zwischen Modulen oder Komponenten sollte keine Zyklen haben.

Zyklische Abhängigkeiten entstehen unter anderem, wenn sich Softwareeinheiten, beispielsweise zwei Klassen, wechselseitig voneinander abhängig machen (siehe Bild 4 in der Bildergalerie).

Zyklische Abhängigkeiten sollten unter allen Umständen aufgelöst werden. Die so miteinander verbundenen Softwarebausteine sind weder autark verwendbar, noch können sie gut getestet werden. Besonders schmerzhaft sind derartige Zyklen, wenn die so miteinander verbundenen Bausteine in unterschiedlichen Komponenten bzw. Subsystemen liegen, oder wenn die Abhängigkeiten beispielsweise über Schichtengrenzen hinweg gehen.

Zyklische Abhängigkeiten können durch Berücksichtigung des Dependency Inversion Principles vermieden werden.

Dependency Inversion Principle (DIP):

  • Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhängen. Beide sollten von Abstraktionen abhängen.
  • Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.

Mit Hilfe von DIP kann man die Richtung von Abhängigkeiten steuern bzw. beeinflussen, und dadurch beispielsweise auch zyklische Abhängigkeiten vermeiden bzw. brechen.

Dependency Injection (DI):

Mit Hilfe des Objekt-orientierten Entwurfsmusters Dependency Injection (DI) werden Abhängigkeiten externalisiert und erst zur Laufzeit aufgelöst („injiziert“). Das heißt, dass die konkreten Abhängigkeiten an einem zentralen Ort hinterlegt sind, und eine zentrale Komponente, Assembler genannt, die alleinige Verantwortlichkeit für den Aufbau des Abhängigkeitsnetzes zwischen den Objekten hat.

Durch den Einsatz von DI kann ein besonders hohes Maß an loser Kopplung zwischen den Softwarebausteinen erreicht werden. Der Assembler kennt sozusagen einen „Bauplan“ für das zu erzeugende Objektnetzwerk, und steckt das Softwaresystem, beispielsweise bei Programmstart, entsprechend zusammen.

Test-Driven Development (TDD):

Test-Driven Development (TDD), ein Test-First-Ansatz, ist ein vielseitiges Werkzeug, welches u.a. einen deutlich bewussteren Umgang mit Abhängigkeiten fördert. Mit Hilfe von TDD wird ein Softwaremodul (z.B. eine Klasse) in kleinen, kurzen Inkrementen entwickelt, indem die Anforderungen an dieses Modul, eine nach der anderen, in Form von ausführbaren Testfällen manifestiert werden, um anschließend gerade so viel Code des Softwaremoduls zu schreiben, dass dieser neue Test, und alle vorangegangenen Tests bestanden werden.

Der Entwickler wird während des mehrfachen Durchlaufens des TDD-Zyklus (siehe Bild 5 in der Bildergalerie) immer wieder vor bewusste Entwurfsentscheidungen gestellt und kann dabei Abhängigkeiten explizit so designen, dass sie die wenigsten Probleme bereiten.

Da mit Hilfe von TDD ein Design for Testability förmlich erzwungen wird (Augenmerk auf lose Kopplung, die Verwendung von Abstraktionen für Testattrappen (Mock-Objekte) und dergleichen), entsteht auch zumeist eine sehr günstige und flexible Abhängigkeitssituation. Das mit Hilfe von TDD das entwickelte Softwaremodul auch eine 100%tige Testabdeckung erhält, ist ein erfreulicher Nebeneffekt und ermöglicht zudem bei Bedarf ein angstfreies Refactoring, sowohl des Produktionscodes, als auch des Testcodes.

Quellen

[Wohland2012] Gerhard Wohland, Matthias Wiemeyer: Denkwerkzeuge der Höchstleister – Wie dynamikrobuste Unternehmen Marktdruck erzeugen. 3. Auflage. UNIBUCH Verlag, Lüneburg, 2012.

[Manifest2001] Kent Beck et. al.: Manifest für Agile Softwareentwicklung. http://agilemanifesto.org/iso/de/. Zuletzt eingesehen am 10.10.2015.

[Parnas72] David Lorge Parnas: On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM, Vol. 15, No. 12, 1972.


* Stephan Roth ist Trainer und Berater bei der oose Innovative Informatik eG in Hamburg. Seine Schwerpunkte liegen in den Bereichen modellbasiertes Systems- und Software Engineering mit UML und SysML, Softwarearchitektur und -design, sowie Software Craftsmanship und Clean Code Development.

(ID:44130029)