Clean Code ist eine von Robert C. Martin entwickelte Praxis, die sich auf die Erstellung von Software mit hoher Lesbarkeit und Wartbarkeit konzentriert. Der Fokus liegt auf Verständlichkeit und Qualität, damit Entwickler die Software effizient warten und weiterentwickeln können. Besonders in Zeiten von KI-generiertem Code bleibt durch Clean Code die menschliche Kontrolle und Überprüfbarkeit gewährleistet.

Überblick

Dieser Artikel gibt einen schnellen Überblick über die wesentlichen Prinzipien und Techniken von Clean Code auf Klassen- und Modulebene. Das Hauptziel ist es, die Lesbarkeit und Wartbarkeit durch klare Benennung, saubere Funktionsaufteilung, gezielte Fehlerbehandlung und durchdachte Kommentare zu verbessern. Die vorgestellten Techniken richten sich speziell an Fachleute, die daran interessiert sind, ihre Arbeit effizienter zu gestalten und ihren Code zukunftssicher zu machen.

Namen

Eine der wichtigsten Prinzipien von Clean Code betrifft die Benennung von Variablen, Methoden und Klassen. Namen sollen stets den Zweck erkennen lassen und niemals irreführend sein. Zum Beispiel: Verwende customerOrder statt data für eine Variable, die eine Kundenbestellung repräsentiert. Vermeide Namen wie tmp oder foo, die keine klare Bedeutung vermitteln. Ein klarer und aussagekräftiger Name hilft dir und anderen, den Code ohne zusätzliche Kommentare zu verstehen. Unterschiedliche Konzepte sollten dabei auch unterschiedliche Namen erhalten, sodass der Lesende nicht raten muss, worauf sich ein Begriff bezieht.

Verwende klare und unmissverständliche Namen.

Namen sollen aussprechbar und suchbar sein. Das bedeutet, dass kryptische Abkürzungen oder Codierungen, wie die ungarische Notation, vermieden werden sollten. Moderne Entwicklungsumgebungen bieten ohnehin leistungsfähige Suchfunktionen und Autovervollständigung, die durch gute Namen optimal genutzt werden können.

Verwende aussprechbare Namen ohne unnötige Codierungen.

Interfaces und Implementierungen sollten eine konsistente Namensgebung erhalten. Anstelle von IThing und Thing ist die Verwendung von Thing und ThingImpl oft sinnvoller, um eine klare Trennung und bessere Verständlichkeit zu erreichen. Zudem sollten im Code eher Interfaces als konkrete Implementierungen verwendet werden, um Flexibilität und Testbarkeit zu gewährleisten. Dies erleichtert das Verständnis und verringert die mentale Belastung des Lesenden.

Benenne Implementierungen klar und vermeide unnötige Präfixe.

Klassennamen sollten sich aus Nomen zusammensetzen, während Methoden Namen aus Verben oder Verb-Nomen-Kombinationen tragen sollten. Auf diese Weise wird klar, dass Klassen Dinge darstellen und Methoden Aktionen ausführen. Informationslose Namenszusätze wie “Manager” sind zu vermeiden, da sie die Aussagekraft verringern.

Nutze Nomen für Klassen und Verben für Methoden.

Schließlich sollten Namen immer einen sinnvollen Kontext vermitteln. Eine gut benannte Variable innerhalb einer gut benannten Funktion in einer gut benannten Klasse macht den Code selbsterklärend. Kontextlose Namen, die nicht auf den Anwendungszweck hinweisen, erschweren das Verständnis erheblich.

Sorge dafür, dass Namen stets einen sinnvollen Kontext haben.

Funktionen

Funktionen sollen so klein wie möglich sein und nur eine Sache tun. Wenn eine Funktion mehrere Aktionen durchführt, wird der Code schwieriger zu verstehen und zu warten. Diese Trennung der Verantwortlichkeiten sorgt dafür, dass der Code sowohl flexibler als auch leichter testbar wird.

Erstelle Funktionen, die nur eine Aufgabe erfüllen.

Ein weiteres Prinzip ist die Begrenzung der Anzahl von Argumenten. Idealerweise sollte eine Funktion nicht mehr als zwei Parameter haben. Funktionen mit vielen Parametern sind schwieriger zu verstehen und zu verwenden. Sollten mehrere Parameter erforderlich sein, können sie oft zu Objekten zusammengefasst werden, die ein gemeinsames Konzept repräsentieren.

Begrenze die Anzahl der Funktionsargumente auf ein Minimum.

Switch-Anweisungen sind in der Praxis manchmal unvermeidbar, führen jedoch dazu, dass mehrere Dinge gleichzeitig geschehen. Ein guter Ansatz ist es, Switches zu kapseln und nur einmal im Code zu verwenden, um die Komplexität gering zu halten.

Verwende Switch-Anweisungen so selten und klar wie möglich.

Funktionen sollten darüber hinaus keine Seiteneffekte haben. Das bedeutet, dass sie keine Veränderungen des Zustands vornehmen sollten, wenn dies nicht aus ihrem Namen hervorgeht. Wenn eine Funktion den State ändert, sollte dies klar im Funktionsnamen kommuniziert werden.

Vermeide Seiteneffekte in Funktionen, die nicht explizit darauf hinweisen.

Fehler sollten in Funktionen immer durch Exceptions und nicht durch Rückgabewerte wie Fehlercodes signalisiert werden. Dies erleichtert die Fehlerbehandlung und sorgt dafür, dass der Normalfall des Codes klar vom Fehlerfall getrennt ist.

Verwende Exceptions anstelle von Fehlercodes.

Kommentare

Kommentare können hilfreich sein, aber sie machen schlechten Code nicht besser. Stattdessen sollte der Code selbst so verständlich wie möglich geschrieben werden. Ein guter Code benötigt nur selten zusätzliche Erklärungen. Kommentare werden bei Änderungen am Code oft vergessen und „verrotten“ dadurch, was zu Verwirrung führt, da sie nicht mehr zum aktuellen Code passen. Wenn sich der Code nicht ohne Weiteres verbessern lässt, sind erläuternde Kommentare erlaubt, um wichtige Konzepte hervorzuheben.

Schreibe Code, der sich selbst erklärt, anstatt viele Kommentare zu verwenden.

TODO-Kommentare sind akzeptabel, solange sie konsequent abgearbeitet werden. Sie können dazu dienen, notwendige Verbesserungen oder Erweiterungen im Code festzuhalten. Es ist jedoch wichtig, diese Kommentare nicht zu ignorieren und regelmäßig zu überprüfen.

Verwende TODO-Kommentare bewusst und arbeite sie konsequent ab.

Wichtige, aber auf den ersten Blick triviale Code-Passagen dürfen kommentiert werden, um sicherzustellen, dass deren Bedeutung nicht übersehen wird. Dies kann dazu beitragen, dass andere Entwickler die Bedeutung dieser Passagen verstehen, ohne dass Missverständnisse entstehen.

Kommentiere wichtige, aber triviale Passagen, um Missverständnisse zu vermeiden.

Öffentliche APIs sollten immer ausführlich kommentiert werden, damit Nutzer der API verstehen, wie sie diese korrekt verwenden. Für private APIs hingegen ist es oft besser, Kommentare zu vermeiden und stattdessen den Code selbst so klar wie möglich zu gestalten.

Kommentiere öffentliche APIs ausführlich, private APIs jedoch nur bei Bedarf.

Redundante oder nicht hilfreiche Kommentare sollten vermieden werden. Offensichtliche Informationen müssen nicht kommentiert werden, da sie nur die Wartung des Codes erschweren, ohne zusätzlichen Wert zu bieten.

Schreibe nur Kommentare, die wirklich zusätzlichen Nutzen bieten.

Struktur

Die Methoden einer Klasse sollten in einer sinnvollen Reihenfolge strukturiert sein, wobei der Aufrufer einer Methode immer oberhalb der aufgerufenen Methode steht. Dies sorgt für eine logische Abfolge im Code und erleichtert die Lesbarkeit.

Strukturierte Klassenmethoden von oben nach unten, mit absteigender Abstraktionsebene.

Zusammenhängende Gedanken im Code sollten nicht auseinandergerissen werden. Dies bedeutet, dass eng verwandte Logikblöcke nahe beieinanderstehen sollten, während unterschiedliche Gedanken durch Leerzeilen getrennt werden. Dies verbessert die Lesbarkeit und hilft dabei, den Code schneller zu verstehen.

Gruppiere zusammengehörige Logikblöcke eng und trenne unterschiedliche Gedanken durch Leerzeilen.

Variablen sollten so nah wie möglich an ihrem Punkt der Nutzung deklariert werden, um die Übersichtlichkeit zu erhöhen. Instanzvariablen sollten jedoch zu Beginn einer Klasse definiert werden, damit ihr Gültigkeitsbereich klar ist.

Deklariere Variablen immer so nah wie möglich am Nutzungspunkt.

Eine gute Formatierung sorgt dafür, dass der Code auf den ersten Blick gut strukturiert wirkt. Dazu gehört, dass eine Zeile nicht länger als 120 Zeichen sein sollte und dass Einrückungen konsistent sind. Eine einheitliche Formatierung im Team stellt sicher, dass der Code von allen gleich verstanden wird.

Halte dich an eine konsistente und verständliche Formatierung des Codes.

Fehler

Exceptions sind in der Regel besser als Fehlercodes, da sie den normalen Programmfluss nicht stören. Fehlercodes müssen nach jeder Funktion überprüft werden, was die Lesbarkeit des Codes erschwert und zu vielen Wiederholungen führt. Exceptions hingegen erlauben es, den Fehlerfall klar zu isolieren.

Verwende Exceptions statt Fehlercodes, um die Fehlerbehandlung zu vereinfachen.

Try-Catch-Finally-Blöcke sollen als Transaktionen betrachtet werden. Sie definieren einen klaren Gültigkeitsbereich für eine bestimmte Operation. Ein guter Ansatz ist es, mit dem Try-Catch-Block zu beginnen und dann den Normalfall zu implementieren.

Beginne die Fehlerbehandlung mit einem Try-Catch-Finally-Block.

Exceptions sollten ausreichend Kontext liefern, damit der Grund des Fehlers nachvollziehbar ist. Einfache Fehlermeldungen ohne detaillierte Informationen erschweren das Debugging und verursachen zusätzlichen Aufwand bei der Fehlersuche.

Gib Exceptions genug Kontext, um den Fehlergrund nachvollziehen zu können.

Ein weiterer wichtiger Punkt ist die Vermeidung von NULL als Rückgabewert oder Parameter. Wenn eine Funktion NULL zurückgibt, muss der Aufrufer dies überprüfen, was zu unnötigem, zusätzlichem Code führt. Stattdessen sollten Defaults oder geeignete Exceptions verwendet werden.

Verwende keine NULL-Werte als Rückgaben oder Parameter.

Tests

Die drei Gesetze des Test-Driven Development besagen: Tests müssen vor dem produktiven Code geschrieben werden. Es darf kein produktiver Code geschrieben werden, bevor ein fehlschlagender Test existiert. Ein Test darf nicht umfänglicher sein, als dass er fehlschlägt (Compiler-Fehler zählen ebenfalls dazu). Es darf nicht mehr produktiver Code geschrieben werden, als notwendig ist, um den Test erfolgreich zu bestehen. Dies stellt sicher, dass jede Änderung im Code sofort von Tests abgedeckt wird. Tests sind somit eine Garantie für stabile und wartbare Software.

Schreibe zuerst Tests, bevor produktiver Code entsteht.

Tests müssen genauso sauber geschrieben sein wie der produktive Code. Sie sind sogar wichtiger als der produktive Code: Wenn Tests fehlen, wird der Code schnell zu Legacy-Code, also Code, der schwer zu ändern und zu warten ist. Fehlt hingegen der Code, kann er leicht entlang der vorhandenen Tests erstellt werden, besonders effizient mithilfe von KI. Tests sind ein wichtiges Mittel, um Änderungen abzusichern, und dienen oft als erste Anlaufstelle, wenn es darum geht, die Funktionsweise einer Klasse oder Methode zu verstehen. Auch hier gilt: Sauberkeit und Lesbarkeit haben Vorrang vor Effizienz.

Achte bei Tests besonders auf Sauberkeit und Lesbarkeit.

Ein Test sollte nur eine Assertion enthalten, damit er sich auf einen bestimmten Aspekt konzentriert. Das erleichtert die Fehlersuche, da sofort klar wird, welcher Teil des Codes fehlerhaft ist. Mehrere Assertions in einem Test erschweren die Fehlersuche, da es dann schwieriger ist, den fehlerhaften Teil des Codes schnell zu erkennen.

Begrenze Tests auf eine einzelne Assertion, um Klarheit zu schaffen.

Tests sollen den produktiven Code flexibel halten und die Angst vor Änderungen nehmen. Gute Tests sind in der Lage, Änderungen am Code zu erkennen und dadurch zu bestätigen, dass bestehende Funktionalität weiterhin korrekt ist. Dies ermöglicht ein leichteres Refactoring.

Nutze Tests, um den Code flexibel und änderungssicher zu halten.

F.I.R.S.T ist ein Akronym, das die wichtigsten Prinzipien guter Unit Tests beschreibt: Sie sollen schnell, unabhängig, wiederholbar, selbsterklärend und zeitnah sein.

  • Fast: Tests sollen schnell ausgeführt werden, um die Entwicklerproduktivität zu erhöhen. Beispiel: Unit-Tests sollten so geschrieben sein, dass sie in Millisekunden ausgeführt werden.

  • Independent: Tests sollen unabhängig voneinander sein, sodass ein Test nicht vom Erfolg oder Misserfolg eines anderen abhängt. Beispiel: Jeder Test sollte seine eigene Testumgebung aufbauen und nicht auf den Zustand anderer Tests angewiesen sein.

  • Repeatable: Tests sollen in jeder Umgebung zuverlässig ausgeführt werden können. Beispiel: Ein Test sollte sowohl auf einem Entwicklerrechner als auch in der CI/CD-Pipeline das gleiche Ergebnis liefern.

  • Self-Validating: Tests sollen ein klares Ergebnis liefern, idealerweise in Form eines booleschen Wertes (Erfolg oder Misserfolg). Beispiel: Verwende Assertions, um sicherzustellen, dass die erwarteten Ergebnisse den tatsächlichen Ergebnissen entsprechen.

  • Timely: Tests sollen zeitnah zum produktiven Code geschrieben werden, idealerweise während oder direkt nach der Implementierung. Beispiel: Schreibe Tests direkt nach der Implementierung einer neuen Funktion, um sicherzustellen, dass sie wie erwartet funktioniert.

Diese Prinzipien sorgen dafür, dass Tests effektiv eingesetzt werden können, ohne den Entwicklungsprozess zu verlangsamen.

Folge den F.I.R.S.T-Prinzipien für gute Unit Tests.

Ausblick

Der nächste Artikel wird sich mit Clean Code auf Systemebene befassen. Dabei wird das Zusammenspiel von Klassen und Modulen genauer betrachtet, um sicherzustellen, dass der Gesamtentwurf einer Software ebenso sauber und wartbar ist wie der Code auf der Ebene einzelner Funktionen und Klassen.

Abschluss

Für jeden Softwareentwickler ist es eine sinnvolle Investition, das Buch “Clean Code” von Robert C. Martin zu lesen. Die in diesem Artikel beschriebenen Prinzipien werden dort im Detail erklärt und durch viele praxisnahe Beispiele erläutert. Clean Code ist nicht nur ein Satz von Regeln, sondern eine Philosophie, die zu besserer und effizienterer Software führt.