Designprozesse Der dynamikrobuste Softwareentwurf

Autor / Redakteur: Stephan Roth* / Christine Kremser

Nahezu alle Entwicklungsorganisationen sind heutzutage einem Umfeld hoher Dynamik ausgesetzt. Ständig ändern sich bereits bestehende Anforderungen, oder es kommen neue hinzu. Das muss eine Software aushalten können – sie muss dynamikrobust sein.

Anbieter zum Thema

Auch Surfer haben es mit einem dynamischen, sich ständig ändernden Umfeld zu tun. Insofern befinden sie sich in einer ähnlichen Situation wie Softwareentwickler.
Auch Surfer haben es mit einem dynamischen, sich ständig ändernden Umfeld zu tun. Insofern befinden sie sich in einer ähnlichen Situation wie Softwareentwickler.
(Bild: gemeinfrei/Pixabay / CC0 )

Die Welt befindet sich inmitten eines Veränderungsprozesses. Der rasante technologische Fortschritt, die zunehmende Komplexität technischer Systeme, der globale Wettbewerb, die weltweite Vernetzung, ständig steigende Anforderungen bezüglich Funktionalität und Qualität, aber auch wechselnde Rahmenbedingungen wie beispielsweise gesellschaftliche und soziodemographische Entwicklungen, sorgen insgesamt für eine Erhöhung der Dynamik der Märkte.

Bildergalerie
Bildergalerie mit 5 Bildern

Das heißt: Organisationen sind im heutigen Informationszeitalter sehr viel häufiger mit unerwarteten Ereignissen konfrontiert, als es noch im Industriezeitalter bis etwa Mitte der 1980er Jahre der Fall war. Die Zukunft wird immer unvorhersehbarer, und Planung als Instrument der Steuerung versagt immer öfter.

Um weiterhin erfolgreich zu bleiben und bestehen zu können bedeutet das für viele Entwicklungsorganisationen, dass sie auf verschiedenen Ebenen Wege, Mittel und Strategien finden müssen um mit diesem komplexen und dynamischen Umfeld adäquat umgehen zu können. Dr. Gerhard Wohland et al. bezeichnen die Organisationen, denen dieses gut gelingt, als dynamikrobuste Höchstleister [Wohland2012]. Diese Unternehmen sind so aufgestellt, dass sie Marktdruck erzeugen und es dadurch ihren Wettbewerbern schwer machen.

Um beispielsweise auf der Prozessebene mit hoher Dynamik umgehen zu können, arbeiten dynamikrobuste Höchstleister häufig mit agilen Frameworks, wie Scrum. Scrum als Inspect&Adapt-Framework zur Produktentwicklung erlaubt es, angemessen und zeitnah Änderungen und neue Anforderungen zu berücksichtigen; es etabliert in der Entwicklung quasi eine Art Willkommenskultur für das dynamische Umfeld.

Es reicht allerdings nicht aus nur die Organisations- und Prozessebene fit für hohe Dynamik zu machen. All diese Bestrebungen laufen ins Leere, wenn das zu entwickelnde Produkt sich dem widersetzt. Der starre und widerspenstige Software-Monolith kann in einem dynamischen Umfeld erhebliche Probleme bereiten und zu einem Show-Stopper werden.

Agile Teams werden nicht selten durch ihr eigenes Produkt ausgebremst. In den Prinzipien des agilen Manifests [Manifest2001] heißt es daher auch: Ständiges Augenmerk auf technische Exzellenz und gutes Design fördert Agilität. Man möchte damit zum Ausdruck bringen, dass die innere Qualität einer Software genauso wichtig ist wie die extern wahrnehmbare Qualität und Funktionalität.

Aus diesem Grund muss in der Entwicklung ein besonders hohes Augenmerk auf die Evolvierbarkeit des Produkts gelegt werden. Evolvierbarkeit ist eine nicht-funktionale Eigenschaft einer Software, die anzeigt, mit welcher Energie (Aufwand) und mit welchem Erfolg neue Anforderungen realisiert bzw. bestehende Funktionen geändert und an neue Rahmenbedingungen angepasst werden können.

Nur gut evolvierbare Software hat Aussicht auf eine hohe Lebensdauer; diese Eigenschaft stellt somit auch eine Art Investitionsschutz für den Kunden dar. Evolvierbare Software ist dynamikrobuste Software.

Abhängigkeiten – die Wurzel allen Übels?

Eine besonders wichtige Rolle im dynamikrobusten Softwaredesign spielen die Abhängigkeiten. Um mit der hohen Komplexität besser umgehen zu können, werden Softwaresysteme nach dem Prinzip Teile und Herrsche modularisiert, d.h. in kleinere Bausteine zerlegt.

Dabei entsteht in der Regel eine mehrstufige Hierarchie aus Softwarebausteinen auf unterschiedlichem Abstraktionsniveau. Damit diese wieder miteinander kollaborieren können um die von den Stakeholdern geforderten Anforderungen zu erfüllen, existieren zwischen diesen Bausteinen sogenannte Abhängigkeiten.

Unter einer Abhängigkeit versteht man, dass ein Softwarebaustein (etwa eine Klasse) einen anderen Softwarebaustein benötigt, um seine Aufgabe erfüllen zu können. Anders ausgedrückt: der abhängige Baustein ist in seiner Spezifikation und/oder Implementation ohne den unabhängigen Baustein unvollständig. In der Unified Modeling Language (UML) werden Abhängigkeiten zwischen Klassen ganz allgemein durch die dependency relationship (gestrichelter Pfeil) dargestellt (siehe Bild 1 in der Bildergalerie).

Im Sourcecode werden Abhängigkeiten dadurch erzeugt, dass man an irgendeiner Stelle im abhängigen Softwarebaustein den unabhängigen Softwarebaustein verwendet, erzeugt, oder er ist sogar struktureller Bestandteil (beispielsweise ein Attribut der Klasse) des abhängigen Bausteins, was einer sog. Assoziation entspricht. Das lässt sich auch in der UML-Notation unterscheiden (siehe Bild 2 in der Bildergalerie).

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Eine weitere, sogar besonders strenge Abhängigkeit, stellt in der Objekt-orientierten Softwareentwicklung die Generalisierung dar (siehe Bild 3 in der Bildergalerie); eine taxonomische Beziehung, die umgangssprachlich auch häufig als Vererbung bezeichnet wird.

In einem modularen Softwaredesign werden Abhängigkeiten schlichtweg benötigt. Die Schmerzen beginnen dann, wenn zu viele und ungünstige Abhängigkeiten existieren. Zu viele und falsche Abhängigkeiten können die Evolvierbarkeit einer Software drastisch verringern, womit auch ihre Robustheit gegenüber dem dynamischen Umfeld zurückgeht. Auch die Testbarkeit eines solchen Systems nimmt rapide ab.

Infolgedessen steigen die Aufwände in der Entwicklung an. Aufwandsschätzungen werden immer pessimistischer, Terminzusagen können nicht mehr eingehalten werden, und die Fehlerrate steigt. „Never touch a running system!“ ist ein Statement, das man in einem solchen Projekt häufig hört.

Das hat auch ökonomische Konsequenzen, denn die betriebliche Wertschöpfung in der Entwicklungsorganisation geht zurück. Im schlimmsten Fall endet das Ganze in einem sehr teuren Wartungsalbtraum, oder sogar in einer Havarie, d.h. es führt zu einem Projektabbruch.

Die gute Nachricht: Man ist einer ungünstigen Abhängigkeitssituation in einer Software nicht hilflos ausgeliefert, denn Abhängigkeiten können gestaltet werden. Damit eine Software evolvierbar bleibt, sollten Entwickler einige Prinzipien beachten und Praktiken beherrschen, die ich im Folgenden nur kurz vorstellen möchte, die aber im Konferenzvortrag ausführlicher präsentiert werden.

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:

  • 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)