Die Implementierung von Clean Code

Gastbeitrag von | 23.09.2019

Sehr viele Softwareprojekte leiden unter einem exponentiellen Anstieg der Kosten. Über die Jahre wird es immer aufwendiger, neue Features in der bestehenden Codebasis zu implementieren. Aufgrund der Größe ist Neuschreiben keine Alternative. Die Software macht so immer mehr Schwierigkeiten in den Bereichen Korrektheit und Wandelbarkeit. Da keine oder deutlich zu wenig automatisierte Tests vorhanden sind, bleiben bei Änderungen Fehler unentdeckt, die Korrektheit der Software verschlechtert sich. Da die Strukturen sehr kompliziert, Methoden und Klassen zu lang und Verantwortlichkeiten vermischt sind und noch vieles andere im Argen liegt, ist auch die Wandelbarkeit nicht gegeben. Code, den keiner versteht, kann man nur schwer verändern. Gleichzeitig geht damit einher, dass Entwickler sich nicht imstande sehen, solchen Code nachträglich zu testen.

Aus dieser Beobachtung sollten wir als Entwickler etwas lernen: machen wir es besser von Anfang an richtig! Allerdings liegt in dieser Erkenntnis allein noch nicht die Lösung. Es bleibt die Frage: was bedeutet das denn, es richtig zu machen? Wie vermeiden wir einen exponentiellen Anstieg des Aufwands?

Aufwände im Vergleich: exponentieller vs. linearer Anstieg des Aufwands

Prinzipien der professionellen Softwareentwicklung

Ende 2008 habe ich mich als Mitinitiator der Clean Code Developer Initiative mit der Frage befasst, welche Prinzipien und Praktiken zur Professionalisierung der Softwareentwicklung beitragen könnten. Wir haben dort über 40 Prinzipien und Praktiken zusammengetragen, wie etwa das Don’t Repeat Yourself (DRY) Prinzip oder die Test-first Praktik. Ferner haben wir vier Werte identifiziert:

  • Korrektheit,
  • Wandelbarkeit,
  • Produktionseffizienz und
  • kontinuierliche Verbesserung.

Das alles ist wichtig und richtig, doch hilft es Entwicklern nicht aus der Misere.

Es mangelt in der Regel nicht an der Einsicht, wichtige Prinzipien einzuhalten und Praktiken wie das automatisierte Testen anzuwenden. Die Herausforderung bleibt die Frage: wie?

Das tagtägliche Grauen

Einem Entwickler zu erklären, dass eine Funktionseinheit nur eine Verantwortlichkeit haben darf, ist das eine. Ihn zu befähigen, dieses Prinzip wirklich in jedem Moment einzuhalten, ist eine andere Sache. Und die Erkenntnis aus über 10 Jahren Clean Code Developer Initiative lautet: allein auf der Ebene von Quellcode wird das Problem nicht gelöst. Die SOLID Prinzipien führen eben leider nicht zu deutlich besseren Verhältnissen. Natürlich kann entweder beim Codieren oder im Anschluss durch Refactoring einiges verbessert werden. Doch den wirklichen Durchbruch erreichen Teams oder einzelne Entwickler erst, wenn sie vor dem Codieren die Lösung entwerfen. Es muss wieder nachgedacht werden!

Ob es an der Empfehlung der Agilitätsbewegung liegt, kein Big Design Upfront durchzuführen, oder daran, dass UML für den Entwurf nicht geeignet ist, lässt sich nicht abschließend klären. Fakt ist: Entwickler entwerfen ihre Lösung nicht vor der Codierung. Kaum wurde über die Anforderungen gesprochen, geht schon der Konsolendeckel auf, die IDE wird gestartet und loscodiert. Und glauben Sie mir, ich sehe in meinem Trainer- und Berateralltag tagtäglich das Grauen!

Die lieben Anforderungen

Da die Branche nun seit über 10 Jahren mit Schlagworten wie Clean Code, Clean Code Developer, Craftsmanship, SOLID, TDD etc. unterwegs ist und sich gleichzeitig kaum etwas verbessert, fehlt offensichtlich etwas Wesentliches. Die Prinzipien allein genügen nicht. Also wenden wir uns der Frage zu, welche Schritte in einem Entwicklungsprozess erforderlich sind, um von den Anforderungen zum Code zu gelangen.

Anforderungen

  • Anforderungen zerlegen
  • Entwurf in die Breite
  • eine Anforderung auswählen
  • Entwurf in die Tiefe
  • Arbeitsorganisation
  • Arbeitsteilige Implementation (inkl. Tests)
  • Integration (inkl. Tests)
  • Code Review

Code

Bevor irgendetwas Konkreteres mit den Anforderungen durchgeführt wird, müssen diese systematisch zerlegt werden. Systematisch bedeutet hier, dass es eine Zerlegungshierarchie geben muss, in die Anforderungen gegliedert werden. Die häufigste in der Literatur genannte Systematik besteht aus User Stories und Epics. Doch genügt es tatsächlich, die Anforderungen eines komplexen Softwaresystems in lediglich zwei Hierarchieebenen zu gliedern? Meiner Beobachtung nach lautet die ernüchternde Antwort: nein. Zudem stellt sich bei den User Stories immer wieder die Frage, wie man sie am besten formuliert und verfeinert, da die Vorgehensweise rein textbasiert ist. Wir schlagen daher für die Zerlegung der Anforderungen eine Hierarchie vor, wie sie in der folgenden Abbildung zu sehen ist.

Die Domänenzerlegung: Anforderung hierarchisch zerlegen

Die oberen Ebenen Bounded Context und Anwendung lassen wir hier außen vor, da sie nur in wirklich großen Systemen relevant sind. Innerhalb einer Anwendung lässt sich jede Anforderung einem Dialog zuordnen. Das liegt daran, dass Anwender mit der Anwendung interagieren müssen. Erst dadurch, dass ein Anwender einen Menüpunkt auswählt, eine Schaltfläche betätigt, etc. entsteht die Notwendigkeit, Domänenlogik zu programmieren. Und jede solche Interaktion findet in einem Dialog statt.

Stellen Sie sich einen Dialog als ein Fenster oder Formular vor. In einem solchen Dialog gibt es häufig mehrere Möglichkeiten der Interaktion. Daher gliedern sich damit die Anforderungen sehr anschaulich in immer kleinere Einheiten. Wir können alle Anforderungen einer Anwendung betrachten, nur die Anforderungen eines Dialogs, oder sogar nur die Anforderungen einer einzelnen Interaktion.

Der Zerlegung von Interaktionen in einem Dialog

Das Feature

Häufig sind auch die Anforderungen einer Interaktion noch zu umfangreich, um sie im Zeitrahmen von 1-2 Tagen zu realisieren. In solchen Fällen wird man Features identifizieren können, die zunächst zeitlich verschoben werden. Man betrachtet bspw. die zu realisierende Funktionalität einer Interaktion, verzichtet aber zunächst auf das Feature der Validierung. Durch Weglassen von Features wird der Umfang kleiner, so dass es früher zu einem Feedback durch den Product Owner kommen kann. Die folgende Abbildung zeigt am Beispiel einer Fragebogen-Anwendung, wie die Interaktionen in einem Dialog grafisch dargestellt werden können.

Der Entwurf in der Breite

Der Entwurf

Der nächste Schritt besteht darin, eine einzelne Anforderung auszuwählen, um diese dann in der Folge detailliert zu entwerfen und zu implementieren. Bei der Verfeinerung des Entwurfs in die Tiefe geht es jetzt darum, eine konkrete Lösung für das Problem zu finden. Das Ergebnis soll so detailliert sein, dass eine Implementation leicht möglich ist. Das bedeutet, die einzelnen zu implementierenden Methoden müssen gefunden werden. Nur so ist sichergestellt, dass Aspekte tatsächlich getrennt werden. Und nur so ist es möglich, zu entscheiden, welche Methoden aufgrund hoher Kohäsion zusammengefasst werden bzw. welche Methoden in unterschiedlichen Dateien oder Klassen untergebracht werden. Wir lassen uns also nicht mehr vom Code überraschen und führen dann Refactorings durch, sondern wir denken zuerst über die Lösung nach, bevor wir sie implementieren. Die nächste Abbildung zeigt eine Verfeinerung der Interaktion „Start“.

Die Verfeinerung der Interaktion
Dass eine solche Struktur sich leicht implementieren lässt, dürfte ersichtlich sein. In Trainings höre ich regelmäßig, dass die Implementation nach dem Entwurf schon fast langweilig wäre, weil doch sonnenklar sei, was implementiert werden müsste. Und das ist gut so, denn an dem Code, der auf diese Weise entstanden ist, lässt sich im Code Review nur wenig aussetzen. Die sonst anzutreffenden Single Responsibility Principle (SRP) Verletzungen sind nicht mehr da, weil darauf bereits im Entwurf geachtet wurde. Zudem entsteht durch das Mittel der Verfeinerung der Entwürfe Code, der das Integration Operation Segregation Principle (IOSP) einhält. Damit ist der Code leicht verständlich und er lässt sich gut testen.

Fazit

Nach über 10 Jahren Clean Code Developer Initiative kann ich ganz eindeutig feststellen, dass die Prinzipien und Praktiken der professionellen Softwareentwicklung allein nicht genügen, um Clean Code zu implementieren. Es braucht die systematische Zerlegung von Anforderungen sowie deren Entwurf. Ich nutze dafür ein leicht anzuwendenden Entwicklungsprozess: Flow Design. Er ist erprobt, lässt sich schnell erlernen und ist leicht zu verstehen. Und: Sie haben ihn bereits in meinen Abbildungen in Anwendung erlebt. Ein Cheatsheet zum Herunterladen finden Sie unter https://flow-design.info.

Flow Design Cheatsheet
Hinweise:

Interessieren Sie sich für weitere Tipps aus der Praxis? Testen Sie unseren wöchentlichen Newsletter mit interessanten Beiträgen, Downloads, Empfehlungen und aktuellem Wissen.

Weitere Informationen, Beiträge, Buchtipps finden Sie auf der Website von Stefan Lieser.

Stefan Lieser
Stefan Lieser

Stefan Lieser ist Informatiker aus Leidenschaft und arbeitet als Trainer/Berater/Autor. Er lernt gerne Neues und sucht ständig nach Verbesserung und Wegen, um die innere Qualität von Software sowie den Entwicklungsprozess zu verbessern. Er ist Mitinitiator der Clean Code Developer Initiative und bietet für Clean Code Development sowie Entwurf mit Flow Design Trainings und Beratungen an. Unter http://refactoring-legacy-code.net schreibt er zum Thema Refactoring von Legacy Code.