Software-Entwurf: Ein Blick zurück und nach vorn (5C-Design, Teil 1)

By 23. April 2019 Mai 10th, 2019 Allgemein, Inhaltliches

Als in den 80ern und 90ern des vorigen Jahrhunderts die Objektorientierte Programmierung angetreten ist alle Probleme der Entwicklung komplexer Software zu lösen, fasste Robert C. Martin einige Prinzipien des Software-Entwurfs unter dem Kürzel (und Schlagwort) SOLID zusammen. Diese Sammlung an Prinzipien ist auch heute noch populär, obwohl sich seither einiges getan hat. Anfangs der 2000er wurde dann die “SOA-Sau” durchs Dorf getrieben, und heute muss man sich fast schon schämen, wenn man keine “Microservices” macht. Und das, obwohl der Terminus “Microservices” alles andere als exakt definiert ist und eigentlich einige unterschiedliche Architekturstile umfasst. Die SOLID Prinzipien gab es als eine weithin anerkannte Grundlage des Software-Entwurfs dabei die ganze Zeit. Leider waren diese aber keineswegs hilfreich dabei die SOA zu widerlegen, noch kann man sie als Grundlage für gute Microservice Architekturen zu Rate ziehen.

  1. Software-Entwurf: Ein Blick zurück und nach vorn
  2. CUT: Richtig schneiden
  3. CONCEAL: Verbergen
  4. CONTRACT: Schnittstelle festlegen
  5. CONNECT: Verbinden
  6. CONSTRUCT: Aufbauen
  7. It´s a Wrap: Zusammenfassung

Über SOLID

Es war 1999 als Robert C. Martin auf der Basis seiner eigenen Arbeit, und der Arbeit einiger anderer wie Bertrand Meyer und Barbara Liskov, seine Top 5 Prinzipien des objektorientierten Entwurfs festgelegt hat. Später erst fiel es einem gewissen Michael Feathers auf, dass man diese prima unter dem Akronym SOLID zusammenfassen kann. Dass diese Abkürzung der Popularität geholfen hat merkt man alleine schon daran, dass die anderen Prinzipien, die er gleichzeitig für Kohäsion und die Kopplung von Packages festgelegt hat, heute kaum noch jemand kennt. Bei SOLID handelt es sich im Detail um die folgenden 5 Prinzipien:

  • Single-Responsibility Principle: Eine Klasse sollte einen, und nur (!) einen Änderungsgrund haben
  • Open-Closed Principle: Sie sollten in der Lage sein, das Verhalten einer Klasse zu erweitern, ohne diese im Zuge dessen ändern zu müssen
  • Liskov´s-Substitutionsprinzip: Basisklassen müssen durch ihre abgeleiteten Klassen ersetzbar sein
  • Interface-Segregation Principle: Entwerfe kleine Schnittstellen, jeweils passend für einen Anwendungsfall der Benützung der Klasse
  • Dependency-Inversion Principle: Sei immer von Abstraktionen abhängig, und niemals von konkreten Implementierungen

Wie man sieht ging man damals davon aus, dass Software-Entwurf dem Entwurf von Klassen entspricht. Darauf lag auch der Fokus von SOLID. Objektorientierung war damals das, was heute Microservices sind. Sie galten als Patentrezept zur Lösung so ziemlich aller Probleme der Software-Architektur. Unnötig zu erwähnen, dass man das heute anders sieht. Die OOP gilt als überschätzt, und auch wenn man ab und zu auf Vererbung setzt, so gilt doch der Grundsatz: Composition-over-Inheritance.

Dependency Inversion

Wenn auch tatsächlich keiner der 5 Punkte in SOLID falsch ist, so möchte ich doch zumindest die damals gültige Interpretation des Dependency-Inversion Prinzips hinterfragen. Robert C. Martin war der Meinung, dass eine Klasse A, welche die Schnittstelle einer anderen Klasse B benützt nicht direkt von Klasse B abhängig sein sollte. Niemals. Stattdessen sollte A eine Abstraktion der benötigten Schnittstelle festlegen. B implementiert dann diese Schnittstelle. Damit wäre die Abhängigkeit umgekehrt (siehe u.a. Abbildung), wenn man die neu hinzugefügte Schnittstelle als Teil von A sieht. Das hätte den positiven Effekt, dass später die Implementierung der Schnittstelle ausgewechselt werden kann. B kann also durch eine andere Klasse C ersetzt werden, wenn diese dieselbe Schnittstelle implementiert. Heute sieht man es keineswegs so, dass dies immer zu geschehen habe, sondern eher, dass dies in Ausnahmefällen Sinn macht. Eine abstrakte Schnittstellendefinition sollte man nur festlegen, wenn:

  • … es mehr als eine Implementierung davon gibt.
  • … es absehbar ist, dass es in naher Zukunft mehr als eine Implementierung davon geben wird.
  • … die Schnittstelle als möglicher Erweiterungspunkt dienen soll. Dies ist oft bei der Entwicklung von Frameworks der Fall.
Abhängigkeitsumkehr, dem D in SOLID entsprechend

Das genügt hoffentlich als Beleg dafür, dass SOLID keinesfalls als allumfassende Grundlage für zeitgemäßen Software-Entwurf gesehen werden kann. Wenn wir Software in ihre Einzelteile zerlegen, und uns dabei an Best-Practices orientieren möchten, dann benötigen wir etwas anderes, moderneres. Bevor wir so etwas entwickeln, schärfen wir aber noch die Zielsetzung. Schließlich gibt es ja moderne Prinzipien-Sammlungen, wie die der 12-Factor-App. Diese hat als konkrete Zielsetzung, dass sich die damit entwickelten Applikationen zum “Deployment auf modernen Cloud-Plattformen eignen, die Notwendigkeit von Servern und Serveradministration vermeiden”. Ein adäquater Ersatz für Robert C. Martins Prinzipiensammlung sollte sich aber eher um Wartbarkeit von Code drehen.

Die IT in der Legacy Krise

Dass Wartbarkeit ein großes Thema ist merkt man alleine schon an der Zahl der komplexen Softwaresysteme, die inzwischen als “Legacy” bezeichnet werden. Meist meint man damit einen gewissen Kontrollverlust, der sich in Instabilität und steigenden Kosten bei den laufenden Updates (der Wartung) am System zeigt. Um hier für Abhilfe zu sorgen werden wir zunächst eine Blick auf mögliche Ursachen werfen. Sobald das Team die Software nicht mehr versteht besteht die Gefahr des Kontrollverlustes. Das Verhalten bei Änderungen an der Software wird dadurch in hohem Ausmaß nicht-deterministisch und es kommt zu unerwünschten Seiteneffekten. Dies kann ganz banale Ursachen haben, wie das Fehlen einer Dokumentation. Oder es wurde den Entwicklern, denen das System zur Wartung übergeben wurde, schlichtweg nicht die Möglichkeit gegeben sich in den Code einzuarbeiten. Meist ist es aber so, dass das System zu komplex geworden ist.

Über Komplexität

Aber was heißt eigentlich Komplexität? Eine mögliche Definition ist, dass es dabei um die Knoten eines Graphen geht, und die Kanten mit denen diese miteinander verbunden sind. Je mehr Knoten in Summe, und je mehr Kanten in Relation zu diesen Knoten ein Graph hat, desto komplexer ist er. Ein Beispiel für eine sehr hohe Komplexität ist im folgenden Bild dargestellt. Es handelt sich bei den außen dargestellten Knoten um die Klassen eines der Module der OpenJPA , und bei den Kanten dazwischen um die Abhängigkeiten dieser zueinander. Man kann sich leicht vorstellen, dass die Wartung dieses Codes zumindest eine Herausforderung darstellt. Wenn die Entwickler der OpenJPA das Ding auch offensichtlich unter Kontrolle haben, so können wir uns hoffentlich darauf einigen, dass diese Situation keinesfalls als erstrebenswert bezeichnet werden kann. Ich habe jedenfalls noch nie erlebt, dass etwas vergleichbares vorab als Zielbild entworfen wurde. Demnach ist davon auszugehen, dass dies irgendwie “passiert” ist und niemals so geplant war.

Ein Teil (!) der Komplexität der OpenJPA Codebasis im Tool Sonargraph

So geht es weiter…

Nachdem wir nun ein gemeinsames Verständnis der Problematik haben, gehen wir einen Schritt weiter und beginnen mit der Lösungssuche. Wir brauchen ein Modell, welches den Entwicklern der Software hilft, dauerhaft die Kontrolle darüber zu behalten. Dazu ist es nötig die Komplexität, die bei der Entwicklung von Software automatisch entsteht, in geordnete Bahnen zu lenken. Dafür gibt es keinen besseren Weg als die Gesamtkomplexität in ihre einzelnen, danach im Kleinen einfacher beherrschbaren, Teile zu zerlegen. Ziel ist, dass diese einzelnen Teile danach:

  • … möglichst ohne Seiteneffekte und Abstimmungsarbeit weiterentwickelt werden können.
  • … unabhängig vom Rest zu dokumentieren und zu verstehen sind.
  • … an ihren ein- und ausgehenden Schnittstellen isoliert getestet werden können.
  • … unabhängig voneinander ausgetauscht werden können.

Im nächsten Teil dieser Artikelserie beginnen wir gleich mit dem ersten logischen Schritt, nämlich der möglichst effizienten Zerlegung eines großen Ganzen in seine Einzelteile und nennen dies: „CUT: Richtig schneiden“! Dabei handelt es sich um das erste von 5 C´s, welche dann in Summe die moderne Sammlung von Prinzipien zum Software-Entwurf darstellen werden.

Leave a Reply