Modularität und Liskovsches Prinzip in komplexen Systemen

Modularität wird in der modernen Softwareentwicklung zunehmend wichtiger, denn mit ihr lassen sich Systeme auf zukünftige Änderungen vorbereiten. Die Ersetzbarkeit einzelner Teile kommt allerdings nicht umsonst: Verschiedene Prinzipien müssen eingehalten werden, damit am Ende alles funktioniert.

In Pocket speichern vorlesen Druckansicht 17 Kommentare lesen
Lesezeit: 11 Min.
Von
  • Ulf Fildebrandt
Inhaltsverzeichnis

In der Industrie geht die Softwareentwicklung heutzutage immer weiter in Richtung Automatisierung. Die Automobilindustrie wird hierfür meist als gutes Beispiel angeführt: Ein Auto setzt sich aus vielen, genau spezifizierten Bauteilen zusammen, die in verschiedenen Modellen einzusetzen sind. Dieses Prinzip will man verstärkt auch in der Softwareentwicklung umsetzen, denn durch die Verwendung vorgefertigter Softwarebausteine lässt sich der Aufwand beim Erstellen eines neuen Systems reduzieren, ein großer Vorteil in den schnellen Internet-Zeiten.

Die wachsende Komplexität hat zur Folge, dass zeitgemäße Softwaresysteme zumeist nicht mehr von einer einzelnen Person zusammengebaut werden, sondern ein mitunter großes Team von Entwicklern zusammenarbeiten muss, um das System zu entwerfen. Damit einzelne Entwickler oder Teams sinnvoll entkoppelt arbeiten können, muss das Gesamtprojekt in beherrschbare Teile aufgeteilt werden. Ein Verfahren, das als "teile und herrsche" (engl. "divide and conquer") bekannt wurde.

Mehr Infos

"Software modular bauen"

Der Artikel stellt nur eines der Prinzipien zur Modularisierung von Software dar. Weitere Konzepte sind in Ulf Fildebrandts Buch "Software modular bauen" zu finden. Auf 332 Seiten führt der Autor anhand von OSGi- und Javabeispielen in die Archtektur langlebiger Softwaresysteme ein.

Ein Problem in viele Unteraufgaben aufzuteilen, ist keine neue Herangehensweise. Viele sehen die Ursprünge der modularen Softwareentwicklung in David Parnas Veröffentlichung "On the criteria To Be Used in Decomposing Systems into Modules" (PDF) aus dem Jahr 1972, in der er sich wiederum auf Richard Gauthier und Stephen Pont bezieht, die den Begriff 1970 in ihrem Buch "Designing Systems Programs" verwendeten. Laut Wikipedia ist ein Softwaremodul

"eine abgeschlossene funktionale Einheit einer Software, bestehend aus einer Folge von Verarbeitungsschritten und Datenstrukturen. Inhalt eines Moduls ist häufig eine wiederkehrende Berechnung oder Bearbeitung von Daten, die mehrfach durchgeführt werden muss. Module bieten eine Kapselung (encapsulation) durch die Trennung von Schnittstelle und Implementierung: Die Schnittstelle eines Moduls definiert die Datenelemente, die als Eingabe und Ergebnis der Verarbeitung durch das Modul benötigt werden. Die Implementierung enthält den tatsächlichen Programmcode."

Entscheidend ist die Entkopplung von Implementierung und Schnittstelle. Die Schnittstelle ist eine Art vorgeschriebener Vertrag zwischen den Modulen und muss in ihrem Verhalten durch die technische Definition sowie durch die Dokumentation eindeutig beschrieben sein, um eine Wiederverwendung durch andere Module zu erlauben.

Ein System soll sich im weiteren Verlauf aus klar definierten Modulen zusammensetzen. Durch die Anwendung des Konzepts allein ist noch nicht erreicht, dass es auch in Zukunft änderbar bleibt. Erst durch die Anwendung weiterer Prinzipien lässt sich ein Softwaresystem evolutionär weiterentwickeln.

Vor mehr als zehn Jahren hat Robert C. Martin in "Agile Software Development: Principles, Patterns and Practices" [2] fünf Prinzipien der objektorientierten Programmierung aufgestellt:

  • Single-Responsibility
  • Open/Closed
  • Liskovsches Substitutionsprinzip
  • Interface Segregation
  • Dependency Inversion

Die meisten davon, speziell Dependency Inversion, durch Frameworks wie Spring oder Guice auch als Dependency Injection bekannt, gehören zum Handwerkszeug eines Softwareentwicklers.

Weniger bekannt ist das 1993 von Barbara Liskov und Jeannette Wing eingeführte Liskovsche Substitutionsprinzip, obwohl es gerade bei der evolutionären Weiterentwicklung eines Systems gute Dienste leisten kann. Zunächst einmal muss klar sein, dass es sich beim Liskovschen Prinzip um eines für objektorientierte Programmierung handelt. Es geht demnach um Klassen und Typen. Die folgende Definition klingt sehr kompliziert, aber nach dem theoretischen Teil folgt die praktische Bedeutung.Laut Wikipedia ist das Liskovsche Substitutionsprinzip wie folgt definiert:

"Eigenschaften, die anhand der Spezifikation des vermeintlichen Typs eines Objektes bewiesen werden können, sollten auch dann gelten, wenn das Objekt einem Untertyp dieses Typs angehört: Sei q(x) eine beweisbare Eigenschaft von Objekten x des Typs T. Dann soll q(y) für Objekte y des Typs S wahr sein, wobei S ein Untertyp von T ist. Damit ist garantiert, dass Operationen, die auf ein Objekt des Typs T vom Typ S angewendet werden, auch korrekt ausgeführt werden."

Das bedeutet, dass die Eigenschaften von Objekten allgemein festgelegt sind und sowohl für einen Typ T als auch einen Typ S gelten. Für die objektorientierte Entwicklung ist das sinnvoll, wobei mit Typen allgemein Klassen und Subklassen gemeint sind. Für Module ergibt sich daraus eine weitere Dimension. Wenn man voraussetzt, dass ein Typ auch ein Modul sein kann, dann besagt das Prinzip nicht weniger, als dass ein Modul durch ein anderes ersetzbar ist, wenn es dieselben Eigenschaften besitzt. In Bezug auf die Weiterentwicklung eines Systems folgt daraus, dass Module sich durch andere austauschen lassen, sofern das neue Modul alle erforderlichen Eigenschaften aufweist. Sobald ein System aus Modulen zusammengesetzt ist und diese beliebig austauschbar sind, lässt sich das System schrittweise weiterentwickeln. Die Eigenschaften eines Moduls sind über die Schnittstelle klar definiert. An der Stelle kommt die klare Aufteilung von Implementierung und Schnittstelle zum Tragen, denn dadurch gibt es eine eindeutige Definition der Eigenschaften eines Moduls im Sinne des Liskovschen Prinzips für Typen (in diesem Fall Module).