Kostenloses Erstgespräch info@x-works.at

Was ist Clean Code und was ist ein Clean Code Developer

15.6.2022 Technischer Artikel

Clean Code Clean Code und Clean Code Developer sind Begriffe aus der Softwareentwicklung, die dir helfen die Struktur und den Source Code von Software aufzubauen. Eines der ursprünglichen Werke zu diesem Thema stammt von Robert C. Martin, wo er eine Reihe von Prinzipien und Praktiken beschreibt und erklärt, wie du eine Software entwickeln sollst, um jederzeit Erweiterungen und Anpassungen vornehmen zu können.

Während hingegen aktuellere Tendenzen wie Clean Code Developer in der Softwareentwicklung den Entwickler selbst als Mensch in den Fokus rücken und ihm ein Wertesystem an die Hand geben, anhand dessen die Entwicklung ausgerichtet werden soll. Dabei gelten aber nach wie vor die ursprünglichen Clean Code Prinzipien und Praktiken.

Clean Code Developer: Die 4 fundamentalen Werte

Der Ansatz von Clean Code Developer legt diese 4 fundamentalen Werte zugrunde, an die sich die moderne Softwareentwicklung anlehnen soll. Dabei steht immer im Vordergrund, sich von Beginn an eines Projektes nach diesen Grundlagen auszurichten und das gesamte Projekt über einzuhalten:

Wandelbarkeit

„Design is a process“ – Die Entwicklung und das Design von Software ist ein agiler Prozess. Es gibt keinen definierten Endpunkt, wann eine Software fertig ist. Genauso wenig wie es aus technischer Sicht keine Softwarewartung gibt, bei der proaktiv zu bestimmten Zeitpunkten bestimmte Tätigkeiten ausgeführt werden, wie z.B. der Wartung eines Fahrzeuges. Bei Software hingegen werden Anforderungen und Änderungen flexibel im Laufe des Projektes definiert und umgesetzt.

Damit jederzeit Anpassungen möglich sind, muss die Software eine „saubere“ innere Struktur haben. Damit wird es möglich ein Softwareprojekt über einen längeren Zeitraum kontinuierlich weiterentwickeln zu können, ohne dabei die Kosten in den späteren Projektphasen ansteigen zu lassen. Diese Konstanz von Anpassungen in einer Software, sind ein entscheidendes Kriterium. Vielleicht kennst du Projekte, wo in fortgeschrittenen Projektphasen auch kleine Anpassungen immer schwieriger und aufwendiger zu realisieren waren. Das Kriterium der Wandelbarkeit adressiert diesen Umstand.

Jederzeit in einem Projekt sollen Features zu gleichen Kosten ergänzt werden können. D.h. idealerwiese egal zu welchem Zeitpunkt im Projekt es umgesetzt wird, ist immer mit den gleichen Kosten zu rechnen. Je deutlicher dieser Zusammenhang in einem Projekt ausgeprägt ist, desto höhere ist die Wandelbarkeit.

In der Praxis zeigt sich häufig, dass bei Missachtung des Single Responsibility Principle die Wandelbarkeit darunter leidet. Dieses Prinzip sagt aus, dass eine Klasse oder Methode genau eine Verantwortlichkeit haben sollen, also genau eine Sache machen sollen. Wenn Klassen oder Methoden mehrere Dinge machen, wird es immer schwieriger den notwendigen Überblick für Änderungen zu haben, oder anders ausgedrückt, die Kopplung wird hoch. Zu viele Abhängigkeiten und Side Effects entstehen, wenn die Kopplung hoch ist und dieser Zustand kann später nur sehr schwer wieder korrigiert werden.

Korrektheit

Die Korrektheit definiert, dass eine Software die gestellten funktionalen Anforderungen erfüllen soll, und die Funktionen fehlerfrei die erwartenden Ergebnisse liefern. Doch auch die nicht-funktionalen Anforderungen, wie Ressourcenschonung von Speicher und Prozessorzeit und Antwortzeiten müssen im gestellten Rahmen liegen. Wenn sie alle Anforderungen erfüllt, ist eine Software korrekt.

Für die Korrektheit von Software muss bereits bei der Entwicklung gesorgt werden. Es reicht nicht aus anzunehmen, dass ein nachgelagerter Testprozess die Fehler in der Software findet. Clean Code sagt hier eindeutig, bereits während der Entwicklung Tests durchzuführen, und zwar nach der TDD Methodik (Test Driven Development). Mit TDD wird im Entwicklungsprozess immer mit einem failing Unit-Test gestartet und anschließend der passende Production Code entwickelt. Dieser Prozess wird so lange iterativ durchlaufen, bis alle funktionalen Anforderungen an einen Source Code erfüllt sind.

Damit entfällt das „lästige“ Entwickeln von Unit-Tests im Anschluss an die Programmierung, die oft ohnehin nur „Happy Path“ Tests sind. Wird TDD konsequent ein- und umgesetzt, verbringst du als Programmierer deutlich weniger Zeit im Debugger und kannst dich auf die eigentliche Entwicklungsarbeit konzentrieren. Und als Nebeneffekt ist mit dem Production Code auch der Unit-Test fertiggestellt.

Die Korrektheit kann weiter erhöht werden, wenn Pair Reviews durchgeführt werden und vor der Umsetzung eine Anforderungsanalyse und Konzeptentwicklung stattfinden.

Produktionseffizienz

Jeder Arbeitsschritt, der mehr als einmal durchgeführt wird, sollte automatisiert werden. Die Produktionseffizienz eines Teams hängt direkt mit der Anzahl der automatisierten Tasks zusammen. Gleichzeitig reduziert eine Automatisierung die Fehleranfälligkeit.

Eine hohe Produktionseffizienz bedeutet, dass eine Software über längere Zeit weiterentwickelt werden kann.

Kontinuierliche Verbesserung

Dieser Punkt setzt eine Reflexion über den Prozess selbst als auch auf Ebene der Softwaretechnik voraus. Nur wenn die aktuellen Vorgänge durchleuchtet werden, kann entschieden werden, was anders gemacht und verbessert werden soll. Im Sinne von SCRUM ist diese Vorgehensweise für den Prozess schon fix in den Ablauf integriert, mit der Sprint Retrospektive und dem Sprint Review.

Im Sinne von Clean Code kann die Verbesserung auf Ebene des Source Codes mit Reviews und Refactoring erreicht werden. Refactoring ist ein entscheidendes Element bei Clean Code Development. Wenn eine Source Code nach der Umsetzung ein Clean Code Prinzip verletzt, steht jeweils als Folgeschritt das Refactoring am Plan. Dabei wird auch der Test Code genauso wichtig gesehen, wie der Production Code und es gelten für ihn die gleichen Prinzipien. Beim Refactoring wird entwickelter bereits funktionierender Code nochmals intensiv auf Clean Code Richtlinien hin überprüft. Sind Richtlinien nicht eingehalten oder ergeben sich Verbesserungen, werden diese in den Source Code eingearbeitet. Kontinuierliche Verbesserung ist bei Clean Code schon während der Entwicklung ständig aktiv.

Clean Code Prinzipien: wie programmierst du Clean Code?

Clean Code sagt aus, dass der Coder „sauber“ sein soll, gemeint ist damit, dass er human readable sein soll. Wenn du dir den Vorgang des Programmierens genauer überlegt, kommt man zum Ergebnis, dass Programmieren aus 2 Tätigkeiten mit sehr unterschiedlichen Anteilen besteht. Da wäre das Lesen von bestehenden oder gerade programmierten Source Code und das eigentliche Schreiben von neuem Source Code.

Beim Programmieren wird viel mehr Source gelesen

Das Verhältnis von Lesen zu Schreiben beim Programmieren ist grob etwa 10:1 verteilt. Der Hauptanteil beim Programmieren liegt eigentlich am Lesen von Source Code (ganz egal, ob er gerade geschrieben wurde oder bereits vorhanden war). Durch diese Verteilung wird auch klar, ein leicht lesbarer Source Code reduziert den Programmieraufwand erheblich.

Deswegen legt Clean Code so viel Wert auf Refactoring, da das Lesen von Source mit einem Anteil von etwa 10 in die Programmiertätigkeit einfließt. Wenn ein Source Code nicht den Clean Code Richtlinien entspricht, soll durch nochmaliges intensives Durchdenken der Prinzipien ein unmittelbares Refactoring erfolgen. Mit diesem kleinen Zeitaufwand beim Schreiben, kann in Folge durch das Verhältnis 10:1, beim Lesen ein enormer Aufwand eingespart werden.

Welche Prinzipien gibt es bei Clean Code ganz konkret? Wir haben dir hier die wichtigsten Aussagen zusammengefasst:

Boy Scout Rule

Diese Regel ist abgeleitet von Pfadfindern und besagt, dass du einen Source Code immer besser verlassen solltest, als du ihn vorgefunden hast. Wenn dir also Verbesserungen im Sinne der Clean Code Vorgaben an Source Code auffallen, dann setze sie um.

DRY – Don’t Repeat Yourself

Wiederhole dich nicht – es darf kein Teil im Source Code doppelt vorhanden sein. In den meisten Fällen betrifft diese Regel redundante Stellen im Source Code, die mehrfach eingesetzt werden und in eine Methode verschoben werden könnten.

Eine Logik darf nur an exakt einer Stelle definiert sein. Wenn die gleiche oder eine ähnliche Logik an mehreren Stellen vorhanden ist, erzeugt das mehr Lesearbeit und verwirrt zudem.

Meaningful Names für Klassen, Methoden, Variablen…

Neben den Keywords einer Programmiersprache machen Namen den größten Teil an Source Code aus. Mit einer guten Namensgebung kann das Lesen vereinfacht und das Verständnis von Source Code deutlich gesteigert werden. Als Programmierer verwenden wir Namen für verschiedenste Elemente beim Entwickeln von Software:

  • Variablen
  • Funktionen
  • Klassen
  • Parameter
  • Packages / Namespaces
  • Files
  • Verzeichnisse
  • ...

Namen sollen so gewählt sein, dass sie möglichst klar beim Lesen identifizieren, was damit bezeichnet wird. Ein Kommentar zum Verständnis eines Variablennamens solle nicht notwendig sein.

Funktionen

Funktionen sollen kurz sein, als Grundregel gilt etwa 20 bis max. 30 Zeilen. Mehr Code sollte eine Funktion nicht umfassen. Alles was diesen Wert übersteigt, lässt sich nur sehr schwer auf einmal im Kopf behalten, um z.B. eine Anpassung zu machen. Zudem lassen sich kurze Funktionen einfacher mit Unit-Tests abdecken.

Die Anzahl an Parameter sollte auf max. 3 reduziert werden, sogar besser nur 1 oder 2 Parameter. Falls Funktionen mehr Parameter benötigen sollten, ist es besser ein Objekt als Parameter zu übergeben.

Kommentare

Sollen auf ein Minimum reduziert werden. Nichts ist so schädlich wie ein „lügendes“ Kommentar. Clean Code ist mit Kommentaren eindeutig, sie sind schlichtweg an vielen Stellen nicht notwendig und erhöhen nur den Aufwand beim Programmieren. Neben dem Source Code müssen auch Kommentare angepasst werden, was zwangsweise bei Anpassungen zu Situationen führt, wo zwar der Source aber nicht das Kommentar angepasst wird. Was zur Folge hat, dass der Kommentar „lügt“.

Immer wenn du dich in der Situation wiederfindest, einen Kommentar schreiben zu wollen, solltest du nochmals intensiv über den Source Code nachdenken und diesen anpassen. In den meisten Fällen wird der Kommentar dann überflüssig.

Formatierung

Clean Code unterscheidet zwischen Richtlinien für die horizontale und vertikale Formatierung. Auch wenn hier die IDEs in der Zwischenzeit viel Aufwand ersparen, die grundsätzliche Formatierung von Source Code liegt beim Programmierer.

So beschreibt Clean Code vertikale als auch horizontale Openness between Concepts, was die Abstände an zusammengehörenden bzw. nicht zusammengehörenden Elementen beschreibt. Andere Begriffe aus der Formatierung sind Vertical Density und Distance sowie Horizontal Alignment und Indentation.

Error Handling

Clean Code hat für das Behandeln von Fehlern sehr klare Vorgaben. Die beiden wichtigsten daraus sind: verwende Exceptions statt Return Codes und gib kein null an eine Funktion oder von dort zurück.

Wenn in einer Funktion ein Fehler passiert, dann wirf diesen Fehler an den Aufrufer zurück. Das Zurückgeben von speziellen Return Codes ist hier keine saubere Lösung, weil es nicht zu einer besseren Codestruktur beiträgt. Solche speziellen Rückgabewerte müssen im Aufrufer ebenfalls extra behandelt werden und umgehen nur das Exception Handling, welches ohnehin in modernen Programmiersprachen vorhanden ist.

Ähnlich ist es mit der Übergabe oder Rückgabe von null Werten an und von Funktionen. Wenn null Werte übergeben werden, müsste am Beginn jeder Funktion ein null-Check erfolgen. Diese Checks machen den Source Code länger und schwieriger zu verstehen. Clean Code sagt hier eindeutig, dass keine nulls übergeben werden dürfen. Bei Programmiersprachen wie z.B. Kotlin ist diese Erkenntnis schon eingearbeitet. Variablen, die null-Werte enthalten dürfen, müssen hier entsprechend markiert werden.

Klassen

Ebenfalls wie Funktionen, sollen Klassen auch kurz sein. Als Grundregel gilt hier 200 bis max. 500 Zeilen, wobei je kürzer desto besser.

Eine Klasse soll zudem das Single Responsibility Principle erfüllen, also exakt eine Aufgabe haben und diese möglichst präzise und fehlerfrei erfüllen. Wenn dieses Prinzip eingehalten wird, ergibt sich automatisch eine Klasse mit weniger Zeilen und einer höheren Kohäsion (die Kohäsion soll möglichst hoch sein und sagt etwas über den logischen Zusammenhang des Codes aus). Und das Ganze noch bei einer möglichst losen Kopplung (also möglichst wenig Abhängigkeiten zwischen Klassen).

Unit Tests und TDD

Beide spielen bei Clean Code eine wichtige Rolle. Clean Code stuft die Wichtigkeit von Test Code gleich hoch ein wie den eigentlichen Production Code. Damit gelten für Test Code die gleichen Ansätze und er soll auch nach den gleichen Grundsätzen entwickelt sowie refactored werden.

Clean Code adressiert allerdings die oft nachlässige Einstellung zu Unit Tests, indem als Paradigma die Entwicklung mittels TDD also Test Driven Development festgelegt wird. Der Vorgang könnte als eine Art agiles Unit Testing gesehen werden.

Bei TDD wird in iterativen Schritten immer zuerst der Unittest nur so weit entwickelt, dass er fehlschlägt. Im Anschluss wird immer ein Teil des Production Code entwickelt, bis der Unit Test durchläuft. Die Entwicklung von Unit Test und Production Code erfolgt dabei abwechselnd und zu kleinen Teilen. Dieser Vorgang wird so lange wiederholt, bis der Production Code die gestellten Anforderungen abdeckt.

Bei der TDD Vorgehensweise verbringt der Programmierer weniger Zeit im Debugger, weil der Unit Test die Testwerte ohnehin automatisch auswertet. Weiterer Vorteil: am Ende der Entwicklung ist Production Code und Unit Test vorhanden.

Clean Code Praktiken: Welche Design Patterns sind wichtig?

Nachdem uns Clean Code eine Reihe von Prinzipien an die Hand gibt, welche sich mit dem Source Code selbst befassen, legt es auch fest, mit welchen Design Patterns die Struktur einer Software aufgebaut werden soll.

Open Closed Principle OCP

Softwarekomponenten (Klassen, Module, Funktionen, …) sollen offen für Erweiterungen sein, aber geschlossen für Änderungen. Gemeint ist damit eine vorausschauende Programmierweise, um zukünftige Anpassungen schon voraus anzunehmen. Anpassungen sollen immer in neuen Klassen erfolgen, existierender Code soll unangetastet bleiben.

Erreicht wird es, indem immer Richtung Interfaces programmiert wird, mit denen eine lose Kopplung möglich wird. Erweiterungen werden in neue Klassen eingefügt, die vom gleichen Interface abgeleitet sind.

Information Hiding Principle IHP

Dem Verbergen von Informationen liegt aus den Programmiersprachen die Datenkapselung zugrunde, welche das IHP ermöglichen. Es soll immer die niedrigste mögliche Sichtbarkeit für Variablen und Methoden eingesetzt werden. Ein generelles Verwenden des Access Modifiers public für alle Properties oder Methoden soll vermieden werden. Wenn möglich sollen getters und setters überhaupt weggelassen werden, wenn sie nicht gebraucht werden.

Damit wird erreicht, dass Programmteile großen Abhängigkeiten aufweisen und leichter angepasst werden können. Interne Mechanismen können einfacher verändert werden, wenn nur die notwendigen Informationen offengelegt werden.

Favour Composition over Inheritance FCOI

Eine Komposition ist gegenüber Vererbung zu bevorzugen, da hier weniger interne Details „mitgeschleppt“ werden müssen und die Kopplung reduziert wird. Vererbung könnte als „white box reuse“ und Komposition als „black box reuse“ bezeichnet werden.

Wenn Vererbungshierarchienumfassender werden, müssen mit jeder neu hinzukommenden Klasse bestimmte Methoden überschrieben werden, um die Funktion zu gewährleisten. Besser ist es, das Verhalten von Klassen in eigene Funktionsklassen auszulagern und diese in anderen Klassen einzusetzen..

Damit werden Vererbungshierarchien aufgebrochen, das Single Responsibility Principle eingehalten und der Umfang von Klassen reduziert.

Separation of Concerns SOC

Stellt sicher, dass ein Programm modular aufgebaut ist. Es wird erreicht, indem die Einzelteile voneinander gekapselt werden und über eine definierte Schnittstelle angesprochen werden können.

Der Source Code soll in Klassen geteilt werden, die Schnittstelle soll jeweils über ein Interface definiert sein, und zusammengehörende Klassen sollen in einem Package / Namespace zusammengefasst sein.

Single Responsibility Principle SRP

Dieses Prinzip wurde im Text schon an mehreren Stellen angesprochen, da es bei Clean Code eine zentrale Rolle spielt. Aussage ist, jedes Element in einem Source Code soll genau eine Funktion haben und diese perfekt erfüllen.

Die Einhaltung dieses Prinzips erhöht die Verständlichkeit des Source Codes und hält die Kopplung niedrig.

Dependency Inversion Principle DIP

Oder Abhängigkeiten-Umkehrungs-Prinzip. Es sagt aus, dass high-level Module keine Abhängigkeiten zu low-level Modulen besitzen sollen. Änderungen in einer unteren Ebene dürfen keine Auswirkungen auf höhere Ebenen haben.

Da die Funktionen der unteren Ebenen auf höheren Ebenen eingesetzt werden, kann dies nur erreicht werden, wenn die untere Ebene Interfaces bereitstellt. Die höheren Ebenen verwenden dann nur die Interfaces und nicht konkrete Klassen. Wie die Funktionen auf den unteren Ebenen im Detail umgesetzt sind, ist damit verborgen.

Integration Operation Segregation Principle IOSP

Jede Funktion soll entweder andere Funktionen aufrufen (Integration) oder soll Logik (Operation) enthalten. Mit Logik oder Operationen sind alle Funktionen gemeint, die tatsächliche Business Logic enthalten. Die Business Logic soll genau eine gestellte Anforderung lösen, darin sollen dann z.B. keine API oder IO Aufruf erfolgen. Diese Aufrufe wären dann Integration.

Integration und Operation soll klar getrennt sein. Erstellte Business Logic soll dann in eigenen Integration Funktionen oder Integration Klassen mit anderer Business Logic verknüpft (integriert) werden.

Tipps für den Praxiseinsatz

Wann es ist, nun für dich wichtig, sich mit Clean Code zu beschäftigen?

Je nach deinem aktuellen Job hängt es davon ab, wie tief du in welche Bereiche einsteigen solltest. Wenn du als Programmierer vor der Aufgabe stehst, ein umfangreicheres Projekt zu entwickeln, solltest du dich zuerst mit den Prinzipien vertraut machen. Damit kannst du beim Schreiben von Source Code gleich die wichtigsten Clean Code Vorschläge einhalten und dein Source Code ist von Beginn an „sauber“. Bevor du mit der Umsetzung konkreter Anforderungen beginnst und die Softwarearchitektur dafür festlegst, macht es Sinn sich intensiver mit den Praktiken und Design Patterns vertraut machen.

Auch wenn du z.B. Projektleiter auf der Auftraggeberseite bist, ist es durchaus von Vorteil, wenn du dich mit Clean Code vertraut machst. Aus Sicht einer nachhaltigen und langfristen Entwicklung einer Software, spielen hier vor allen die unter Praktiken beschriebenen Punkte eine wesentliche Rolle. Wird die Software in ihrer Struktur nicht von Beginn an nach Clean Code Vorschlägen gestaltet, kann es passieren, dass im Projektfortschritt Erweiterungen nur mit höherem Aufwand oder fast gar nicht mehr umsetzbar sind.

So und jetzt kann es losgehen

Lade einfach unser 160 Seiten umfassendes Whitepaper mit mehr Details und konkreten Beispielen herunter und lege los. Du kannst mit Clean Code in Deinem bestehenden Projekt beginnen oder du kannst bei deinem nächsten neuen Projekt losstarten.

Brauchst du mehr Infos, schreibe mir.