Reviews

Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin

beaconatnight's review against another edition

Go to review page

4.0

Für einen Leser mit wenig Programmiererfahrung ist Clean Code hervorragend dafür, seinen Code mit ein wenig mehr Selbstvertrauen zu betrachten. Es gibt eine Reihe von gut motivierten Richtlinien und Ratschlägen, die zwar nicht immer leicht umzusetzen sind und sicher viel Übung erfordern, deren Zweck aber gut einsichtig ist.

Für mich waren die für viele vielleicht fast schon trivialen Empfehlungen der frühen Kapitel sehr anregend, weil sie einige Ideen vermitteln, anhand welcher Kriterien die Cleanness von Code bewertet werden kann. Insbesondere denke ich, dass ich den Stellenwert von Tests häufig unterschätze. Den Ausführungen der späteren Kapitel hingegen konnte ich zugegebenermaßen nur schwer folgen. Vielleicht habe ich aber dennoch einige Dinge mitnehmen können, die man im Hinterkopf behalten kann, sollte man mal an größeren und komplexeren Projekten arbeiten.

Es folgen eine Reihe mehr oder weniger strukturierte Zitate, die mir bemerkenswert erschienen. Zunächst einige generelle Weisheiten:
- "Schlechter Code verleitet dazu, das Chaos zu vergrößern! Wenn andere schlechten Code ändern, neigen sie dazu, ihn noch schlechter zu machen."
- "Schlechter Code tut zu viel; seine Absicht ist nicht klar zu erkennen und er versucht, mehrere Zwecke auf einmal zu erfüllen. Sauberer Code ist fokussiert. Jede Funktion, jede Klasse, jedes Modul ist eindeutig auf einen einzigen Zweck ausgerichtet und lässt sich von den umgebenden Details weder ablenken noch verunreinigen."
- "Natürlich ist es unmöglich, Code zu schreiben, ohne ihn zu lesen. Deshalb ist das Bemühen, das Lesen von Code zu erleichtern, zugleich ein Bemühen, das Schreiben von Code leichter zu machen."
- "Das @author-Feld einer Javadoc sagt, wer wir sind. Wir sind Autoren. Ein Merkmal von Autoren ist es, dass sie Leser haben. Tatsächlich sind Autoren dafür verantwortlich, erfolgreich mit ihren Lesern zu kommunizieren. Wenn Sie Ihre nächste Codezeile schreiben, sollten Sie daran denken, dass Sie ein Autor sind, der für Leser schreibt, die Ihre Anstrengung beurteilen."
- "Wenn wir alle unseren Code ein wenig sauberer einchecken, als wir ihn ausgecheckt haben, kann der Code einfach nicht verrotten."
- "Die Duplizierung ist der Hauptfeind eines guten System-Designs. Sie bedeutet zusätzliche Arbeit, zusätzliche Risiken und zusätzliche unnötige Komplexität."
- "(...) (D)ie wichtigste Methode, ausdrucksstarken Code zu schreiben, besteht darin, es auszuprobieren. Allzu oft bringen wir unseren Code zum Laufen und wenden uns dann dem nächsten Problem zu, ohne genügend zu fragen, ob der Code für den nächsten Leser aussagestark genug ist. Vergessen Sie nicht: Wahrscheinlich sind Sie der Nächste, der den Code lesen wird."
- "Jedes Mal, wenn Sie einen Kommentar schreiben, sollten Sie sich mangelnder Ausdrucksfähigkeit im Code bewusst sein."
- "Kleine Dateien sind normalerweise leichter zu verstehen als große."
- "Konzepte, die eng verwandt sind, sollten vertikal eng beisammenstehen." Oder auch: "Bestimmte Code-Stücke wollen in der Nähe anderer Code-Stücke stehen. Sie haben eine bestimmte konzeptionelle Affinität (Verwandtschaft). Je stärker diese Affinität ist, desto geringer sollte der vertikale Abstand zwischen ihnen sein."

Laut Kent (Beck (Simple Design)) ist ein Design 'einfach', wenn es folgende Bedingungen (...) erfüllt:
- Es besteht alle Tests.
- Es enthält keine Duplizierungen.
- Es verkörpert die Absicht der Programmierer.
- Es minimiert die Anzahl der Klassen und Methoden."

Einige Bemerkungen zu Variablen und Variablennamen:
- "Die Länge eines Namens sollte der Größe seines Geltungsbereiches entsprechen."
- "Variablen sollten so eng bei ihrem Verwendungsort wie möglich deklariert werden. Weil unsere Funktionen sehr kurz sind, sollten lokale Variablen am Anfang einer Funktion stehen (...). Instanzvariablen sollten dagegen am Anfang der Klasse deklariert werden."

Grundsätze zum Schreiben von Funktionen (Methoden, Prozeduren):
- "Die erste Regel für Funktionen lautet: Funktionen sollten klein sein. Die zweite Regel für Funktionen lautet: Funktionen sollten noch kleiner sein." In Zahlen ausgedrückt: "Funktionen sollten kaum jemals länger als 20 Zeilen sein." (64)
- "Funktionen sollten eine Aufgabe erledigen. Sie sollten sie gut erledigen. Sie sollten nur diese Aufgabe erledigen."
- "Sie erkennen, dass Sie mit sauberem Code arbeiten, wenn jede Routine im Wesentlichen das tut, was Sie erwartet haben."
- "Funktionen, die eine Aufgabe erledigen, können nicht vernünftigerweise in Abschnitte zerlegt werden."
- "Dyaden sind kein Übel, und Sie werden sie sicher schreiben müssen. Doch Sie sollten wissen, dass sie mit gewissen Kosten verbunden sind und Sie sollten jede Möglichkeit nutzen, sie in Monaden umzuwandeln."
- "Im Allgemeinen sollten Sie keine Output-Argumente verwenden. Wenn Ihre Funktion den Status einer Komponente ändern muss, sollte sie den Status des Eigentümerobjeks ändern."
- "Wenn eine Funktion eine andere aufruft, sollten sie vertikal eng beisammenstehen, und die aufrufende Funktion sollte über der aufgerufenen Funktion stehen, falls möglich." Das heißt insbesondere: "Im Allgemeinen sollen Funktionsaufruf-Abhängigkeiten nach unten zeigen. Das heißt, eine Funktion, die aufgerufen wird, sollte sich unter einer Funktion befinden, von der sie aufgerufen wird. Dadurch entsteht ein hübscher Fluss durch das Sourcecode-Modul, der von der hohen Ebene am Anfang nach unten zu den niedrigeren Stufen fließt."

Zum Thema Klassen:
- "Das Single-Responsibility-Principle (SRP (...)) fordert, dass eine Klasse oder ein Modul einen und nur einem Grund zur Änderung haben sollte."
- "(...) (V)iele Entwickler (fürchten), dass eine große Zahl kleiner, auf einen Zweck beschränkter Klassen das Verstehen des Großen und Ganzen erschwere. Sie sind besorgt, dass sie von Klasse zu Klasse navigieren müssen, um herauszufinden, wie eine größere Komponente des Ganzen funktioniert. Doch ein System mit vielen kleinen Klassen hat nicht mehr bewegliche Teile als ein System mit einigen wenigen großen Klassen." Worauf es ankomme sei eine saubere Ordnung.
- "Unsere Systeme sollten aus vielen kleinen Klassen, nicht aus wenigen großen aufgebaut sein. Jede kleine Klasse kapselt eine einzige Verantwortlichkeit ein, hat einen einzigen Grund zur Änderung und arbeitet mit wenigen anderen zusammen, um das gewünschte Systemverhalten zu realisieren."
- "Klassen sollten eine kleine Anzahl von Instanzvariablen haben. Jede Methode einer Klasse sollte eine oder mehrere dieser Variablen manipulieren. Im Allgemeinen hängen eine Methode und eine Klasse um so kohäsiver zusammen, je mehr Variablen die Methode manipuliert. (...) (D)ie Kohäsion (soll) hoch sein. Eine hohe Kohäsion ist ein Kennzeichen dafür, dass die Methoden und Variablen der Klasse als logische Gesamtheit voneinander abhängen."
- "Meine allgemeine Regel für `switch`-Anweisungen lautet: Sie können toleriert werden, wenn sie nur einmal auftauchen, zur Erstellung polymorpher Objekte verwendet werden und hinter einer Vererbungsbeziehung verborgen werden, damit sie für den Rest des Systems unsichtbar sind."

Einige Punkte zur sauberen Fehlerbehandlung:
- "Wenn eine Funktion das Schlüsselwort `try` enthält, sollte es das allererste Wort in der Funktion sein und nach den `catch`/`finally`-Blöcken sollte nichts anderes stehen." Dann erfüllt die Funktion genau eine Aufgabe, nämlich die Fehlerverarbeitung.
- "Fehler-Handling ist wichtig, aber wenn es die Logik verschleiert, ist es falsch."
- "(...) (E)s (ist) sinnvoll, mit einer `try-catch-finally`-Anweisung zu beginnen, wenn Sie Code schreiben, der Ausnahmen auslösen kann. Dies hilft Ihnen zu definieren, was der Benutzer dieses Codes erwarten sollte, egal was in dem Code schiefgeht, der in dem `try` ausgeführt wird."
- "Versuchen Sie, Tests zu schreiben, die Ausnahmen erzwingen. Fügen Sie dann Verhaltensweisen zu ihrem Handler hinzu, um Ihre Testbedingungen zu erfüllen. Diese Vorgehensweise bewirkt, dass Sie zuerst den Transaktionsgeltungsbereich des `try`-Blocks erstellen, und hilft Ihnen, den Transaktionscharakter dieses Geltungsbereiches zu erhalten."
- "Wenn wir `null` zurückgeben, schaffen wir im Wesentlichen Arbeit für uns selbst und wälzen Probleme auf unseren Aufrufer ab. Man muss nur die `null`-Prüfung [`if (item != null` im Körper der aufrufenden Funktion] vergessen, und schon läuft eine Anwendung aus dem Ruder [`NullPointerException` wird irgendwo ausgelöst]." Stattdessen sollte man die Rückgabe von `null` durch etwas Äquivalentes (wie leere Listen) ersetzen.
- "Wenn Sie nicht gerade mit einem API arbeiten, das von Ihnen die Übergabe von `null` erwartet, sollten Sie, falls möglich, nie den Wert `null` in Ihrem Code übergeben."

Tests sind wichtig:
- "Code ohne Tests ist nicht sauber. Egal wie elegant er ist, egal wie lesbar und änderungsfreundlich er ist, ohne Tests ist er unsauber."
- "Testcode ist genauso wichtig wie Produktionscode. Tests sind keine Bürger zweiter Klasse. Testcode erfordert Nachdenken, Design und Pflege. Er muss genauso sauber wie der Produktionscode gehalten werden."
- "Mit Tests haben Sie keine Angst, den Code zu ändern! Ohne Tests ist jede Änderung ein möglicher Bug. Egal, wie flexibel Ihre Architektur sein mag oder wie sauber Sie Ihr Design partitioniert haben, ohne Tests werden Sie zögern, Code zu ändern, weil Sie Angst haben, damit unentdeckte Bugs einzuführen."
- Saubere Tests folgen fünf Regeln: Fast, Independent, Repeatable, Self-Validating, Timely (für Details, s. S. 171-172)
- "(...) (I)n jeder Testfunktion (sollte) nur ein einziges Konzept getestet werden. Wir wollen keine langen Testfunktionen haben, die einen beliebigen Aspekt nach dem anderen testen."

Martin formuliert drei Gesetze der Test Driven Development (TDD):
- "Erstes Gesetz – Sie dürfen Produktionscode erst schreiben, wenn Sie einen scheiternden Unit-Test geschrieben haben."
- "Zweites Gesetz – Der Unit-Test darf nicht mehr Code enthalten, als für das Scheitern und ein korrektes Kompilieren des Tests erforderlich ist."
- "Drittes Gesetz – Sie dürfen nur so viel Produktionscode schreiben, wie für das Bestehen des gegenwärtig scheiternden Tests ausreicht."

Auch der Umgang mit APIs ist nicht immer einfach:
- "Es gibt eine natürliche Spannung zwischen dem Lieferanten eines Interfaces und dem Benutzer eines Interfaces. Lieferanten von Drittanbieter-Packages und -Framworks streben nach einer breiten Anwendbarkeit, damit sie in vielen Umgebungen arbeiten und eine größere Zielgruppe ansprechen können. Dagegen wünschen sich die Benutzer ein Interface, das auf ihre speziellen Anforderungen zugeschnitten ist."
- "Drittanbieter-Code nutzen zu lernen, ist schwer. Drittanbieter-Code zu integrieren, ist ebenfalls schwer. Beides gleichzeitig zu tun, ist doppelt so schwer. (...) Anstatt herumzuprobieren und den neuen Code in unseren Produktionscode zu testen, könnten wir einige Tests schreiben, um den Drittanbieter-Code besser kennen zu lernen. Jim Newkirk bezeit solche Tests als Lern-Tests (engl. Learning Tests (...))."
- "(...) (E)ine saubere Grenze sollte von einem Satz von nach außen gerichteten Tests unterstützt werden, die das Interface auf dieselbe Weise prüfen, wie wir den Produktionscode testen."

Martin bezieht Stellung in einigen allgemeineren Debatten:
- Der Test Driven Development wird uneingeschränkt empfohlen.
- Der Autor ist gegen dogmatisches OOP.
- Der Autor ist gegen Checked Exceptions.
- Der Autor spricht sich für den Einsatz von Nebenläufigkeit aus und diskutiert einige Beispiele, Mythen und falsche Vorstellungen bezüglich dieses Ansatzes. Er formuliert drei klassische Probleme.
- In der Frage, ob Code in der Zukunft nicht mehr geschrieben, sondern anhand normalsprachlicher Anforderungsspezifizierungen generiert wird, spricht sich Uncle Bob unmissverständlich gegen die Möglichkeit von AI-begründeter Ansätze aus.

Uncle Bob's Haltung zur objektorientierten Programmierung (OOP):
- "Gestandene Programmierer wissen, dass die Vorstellung, alles wäre ein Objekt, ein Mythos ist. Manchmal wollen Sie wirklich mit einfachen Datenstrukturen und mit Prozeduren arbeiten, die diese Strukturen manipulieren."
- "Objekte verbergen ihre Daten hinter Abstraktionen und enthüllen Funktionen, die mit diesen Daten arbeiten. Datenstrukturen enthüllen ihre Daten und haben keine Funktionen. (...) Beachten Sie die komplementäre Natur der beiden Definitionen. Sie sind praktisch Gegenteile."
- "(...) (D)ie Dinge, die für OO schwer sind, (sind) für Prozeduren leicht; und die Dinge, die für Prozeduren schwer sind, sind leicht für OO."
- "Prozeduraler Code (Code, der Datenstrukturen verwendet) macht es leicht, neue Funktionen hinzuzufügen, ohne die vorhandenen Datenstrukturen zu ändern. Dagegen macht es OO-Code leicht, neue Klassen hinzuzufügen, ohne vorhandene Funktionen zu ändern."
- "Prozeduraler Code macht es schwer, neue Datenstrukturen hinzuzufügen, weil alle Funktionen geändert werden müssen. OO-Code macht es schwer, neue Funktionen hinzuzufügen, weil alle Klassen geändert werden müssen."

Abschließend noch einige Empfehlungen für eine sinnvolle Formatierung der Dateien:
- "Ich habe meine (Zeilen-)Grenze auf 120 (Zeichen) gesetzt." (wie Pythons `black`)
- "Ich habe die Zuweisungsoperatoren mit Whitespace umgeben, um sie zu betonen. (...) Andererseits habe ich kein Leerzeichen zwischen die Funktionsnamen und die öffnende Klammer eingefügt, weil die Funktionen und ihre Argumente eng zusammengehören. (...) Ich trenne Argumente innerhalb der Klammern eines Funktionsaufrufs (...)."
- "Mit Whitespace kann man (...) den Vorrang von Operatoren hervorheben (...). Leider sind die meisten Formatierungstools blind für den Vorrang von Operatoren und wenden durchgehed denselben Abstand an."

utoxin's review against another edition

Go to review page

informative medium-paced

5.0

dobbykroket's review against another edition

Go to review page

informative slow-paced

4.25

nickhodges's review against another edition

Go to review page

4.0

I hate to say this, but I wasn't as impressed with this book as I thought I should have been, given its place in the pantheon of programming books.

The first half was excellent, but the second half left me a bit cold. It was too Java-y, and had -- dare I say it -- too much code in it.

However, I still list this as a must read for all developers.

ctrl_alt_read's review against another edition

Go to review page

informative slow-paced

4.0

maarten12345's review against another edition

Go to review page

3.0

There is some good advice in here, overall it was an alright book. The examples are a bit outdated and could have been better.

chris_hendriks's review against another edition

Go to review page

4.0

Ondanks alle Java code, ik ben geen Javist, een zeer lezenswaardig boek met praktische begrijpelijke heuristieken hoe je je code clean, onderhoudbaar, kunt houden. Grappig om te lezen dat concurrent code inherent moeilijk wordt gevonden, dat belooft wat voor het aantal bugs in de toekomst.

alexdriaguine's review against another edition

Go to review page

slow-paced

1.5

josevillalta's review against another edition

Go to review page

4.0

Good book about best practices, only thing I didn't like was the it only had examples in Java. The thing I liked most was that each chapter came with real examples. There was a lot of code in this book and that's a good thing

dzver's review against another edition

Go to review page

5.0

Took a while to digest this book. Most ideas are good and natural. Some are not and I already forgot them.

The code samples are in Java and some are way too obscure and time consuming to grok. I followed Bob's rules and didn't read examples longer than 15 lines :-)