Wartbarkeit von Software verbessern

In letzter Zeit mache ich mir vermehrt Gedanken zur Strukturierung von Software und wie wir unsere Produkte/Projekte längerfristig besser machen können. Ich glaube, viele von uns haben nach wie vor Probleme mit der Wartbarkeit von Software.

Im Artikel möchte ich einige Ansätze aufzeigen, mit denen es möglich ist, die Wartbarkeit von Software zu verbessern.

Software als LEGO-Steine

Ich lese immer wieder von der Vorstellung, dass man Software ganz einfach wie LEGO-Steine zusammenbauen kann. Dieser Artikel versucht zu erläutern, wie man Software und LEGO vergleichen kann.

Software ist bedeutend komplexer und trotzdem reizt mich die Vorstellung, dass man verschiedene Komponenten wie LEGO-Steine zusammensetzen kann. Wenn man dies erreichen würde, wäre vieles einfacher (ich bin der Meinung, dass wir es bis jetzt meistens noch nicht schaffen).

Die wichtigste Aussage in diesem Artikel ist folgende

The components themselves are less important than the interface.  The interface stays consistent even as the blocks change.

Das Interface ist das wichtigste an solch einem Baustein und sollte auch konsistent bleiben. Für mich heisst das, dass es extrem wichtig ist, überhaupt Module zu erstellen und für ein Modul ein gutes API zu definieren.

Ok, klingt soweit einfach in der Theorie. Aber wie sieht so ein Modul aus? Wie werden verschiedene Module (Bausteine) zusammengesetzt? Wie sieht es im Innern eines Moduls aus?

Im weiteren Verlauf des Artikels versuche ich, zumindest einen Teil, dieser Fragen zu beantworten.

Alles war am Anfang gut

In allen Firmen in denen ich bis jetzt gearbeitet habe lief es ähnlich. Zuerst ist da ein Projekt/Produkt, welches in möglichst kurzer Zeit entwickelt werden muss. Man geht dabei einige Kompromisse bezüglich Qualität/Erweiterbarkeit ein, sei es mangels Zeit oder Erfahrung. Am Anfang geht alles gut, Feature um Feature wird rausgehauen und alle sind zufrieden. Nach einiger Zeit nimmt die Entwicklungsgeschwindigkeit ab, weil die technische Schuld inzwischen sehr gross ist. Was soll man nun also tun? Refactoring betreiben oder die Applikation komplett oder zumindest in Teilen neu schreiben?

Refactoring vs Rewrite

Unser Softwareentwicklerherz sagt natürlich rewrite, weil man dieses mal alles besser macht. Ich bin aber zum Schluss gelangt, dass refactoring in den meisten Fällen besser ist. Bei einem refactoring sind die Risiken viel kleiner, da man inkrementell Dinge verbessern kann und dabei jederzeit eine laufende und grösstenteils fehlerfreie Applikation hat (weil viele Fehler schon behoben wurden).

In diesem Artikel von Joel Spolsky wird meine Ansicht sehr gut begründet. Eine schöne Übersicht über Refactor vs Rewrite kann man auf infoq finden.

Wie man nun ein solches Refactoring anpacken könnte, habe ich ich für Symfony2 Controller in einem früheren Artikel beschrieben.

Zwar ist es schön, dass man nun weiss, ob Refactoring oder Rewrite, aber in welche Richtung soll man die Architektur einer Software nun entwickeln? Dies sollte in den folgenden Abschnitten aufgelöst werden.

Integration & Operation

Ich möchte hier einen interessanten Ansatz auf der Funktionsebene zusammenfassen, welchen ich vor einiger Zeit hier gelesen habe. Es geht darum, wie wir unseren Code strukturieren.

Eine Operation ist eine Funktion, die Logik enthält. Das bedeutet, sie enthält Kontrollstrukturen (if, for, while usw.) und/oder Ausdrücke (+, *, && usw.). Eine Integration besteht nur aus Funktionsaufrufen und enthält keine Logik.

Integrationen greifen auf Operationen zu, aber nicht umgekehrt.

Integration&Operation

Solche Operations/Integrationsblöcke kann man in anderen, höher angesiedelten Modulen als Blöcke wiederverwenden.

Der Vorteil mit diesem Ansatz ist, dass der Code insgesamt viel verständlicher und einfacher wartbar wird. Die Logik steckt in den “komplexen” Operationen und die Integrationen sind einfache prozedurale Abläufe und können sehr einfach refaktorisiert werden. Ausserdem ist auch ziemlich klar, wie getestet wird. Die Operationen können perfekt mit Unittests, Integrationen mit Integrationstests getestet werden.

Aus der LEGO Perspektive ist also die Integration die Platte mit den Bausteinen und die Operation ist der Baustein selber.

Probleme in einer Schichtenarchitektur

Viele Projekte sind mit einer Schichtenarchitektur aufgebaut (was per se nicht schlecht ist). In der Grafik unten sieht man ein typische Webapplikation-Schichtenarchitektur mit Backend/Frontend, welche das MVC Framework Symfony2 verwendet.

Schichtenarchitektur

Ich sehe bei einer Schichtenarchitektur folgende Probleme/Gefahren. Man entwickelt meistens horizontal (also in einer Schicht). In dieser Schicht fügt man Feature um Feature hinzu und erstellt Featureübergreifend Hilfsfunktionen.

Aus der LEGO Perspektive hat man also ein paar wenige ziemlich lange Bausteine.

Mit der Zeit entsteht ein Monolith für eine Schicht, was meistens auch bedeutet, dass das Projekt eine monolithische Struktur erhält. Soll die Applikation später erweitert werden, bzw reduziert werden, fällt es häufig schwer dies zu bewerkstelligen, weil die Applikation bzw. die Schichten sehr ineinander verzahnt sind. Darum glaube ich, dass man das Prinzip von Integration & Operation auch in einer Schichtenarchitektur verwenden kann, welche im nächsten Abschnitt beschrieben wird.

Integration & Operation in einer Schichtenarchitektur

Den Ansatz der Trennung von Integration & Operation hat mich ziemlich beschäftigt und ich denke, auf Projektebene kann man diesen Ansatz perfekt verwenden. Als Ausgangslage nehmen wir wiederum dasselbe Webprojekt mit derselben Schichtenarchitektur von vorhin.

Operationen sollten kleine, hoch spezialisierte Module sein, welche die Logik enthalten. Das Projekt selber ist nur noch der Klebstoff, der die Module miteinander verbindet und integriert (Integration).

Operation & Integration auf Projektebene

Was ergeben sich für Vorteile, wenn die Logik in den Modulen ist und das Projekt sie nur integriert?

  • Ein Feature kann relativ einfach eingebaut bzw. ausgebaut werden (das Fleisch am Knochen ist ja im Modul und nicht irgendwo verteilt
  • Die Gefahr der Durchmischung pro Schicht für verschiedene Features ist deutlich kleiner
  • Es ist klar, wo der Code für ein Feature ist (nämlich im Modul) plus der möglichst kleine Integrationsteil im Projekt
  • Das Modul kann mittels Unittests abgedeckt werden, das Projekt mit Integrationstests
  • Das Feature bzw. das Modul kann in mehreren Projekten oder Schichten (z.B. Frontend/Backend) verwendet werden
  • Man kann sich pro Feature, falls nötig, individuell entscheiden, ob man es refaktorisiert oder ein rewrite gemacht wird, da die Abhängigkeit zum Projekt möglichst minimal ist
  • Das Framework könnte theoretisch relativ einfach ausgewechselt werden und die Module einfach in einem anderen Framework eingebunden werden

Aus der LEGO Perspektive haben wir nun Platten (Schichten/Integration) und kleine LEGO Steine (Operationen/Module), welche man auf die Platten stecken kann.

Zusammenfassung

Die Wartbarkeit von Software kann meiner Meinung nach enorm verbessert werden, wenn man ein Projekt gemäss dem LEGO Modell in kleine, spezialisierte Module unterteilt. Auf Modulebene sollte man stets Integration/Operation trennen. Auf Projektebene ist das Projekt die Integration und das Modul die Operation.

Ich hoffe ihr konntet meinen Gedanken folgen und ich bin extrem gespannt auf Feedback.

Last but not least. Als Softwareentwickler sollten wir stets offen für Neues bleiben und nicht in starre Denkmuster verfallen. Darum finde ich den Talk von Bret Victor – The Future of Programming extrem erfrischend und auch motivierend noch mehr über den Tellerrand zu schauen.

Update

Ich habe einen wirklich sehenswerten Talk von Robert Martin gefunden, welche einige Aspekte meines Artikels untermauert bzw. verdeutlicht. Es lohnt sich! Ruby Midwest 2011 – Keynote: Architecture the Lost Years by Robert Martin

 

 

1 Response

Leave a Reply

Your email address will not be published. Required fields are marked *