BMU Verlag Logo

Clean Code

Schlechten Code gibt es fast überall – und auch wenn er funktioniert, sollte man ihn beseitigen. Denn damit der Code weiterhin funktioniert, ist viel Zeit und Wartungsarbeit nötig, was die Produktivität reduziert. Zudem ist unsauberer Code demotivierend für andere Entwickler, die sich mit dem Code auseinandersetzen müssen, anfällig für Fehler und nur sehr schwer auf neue Anforderungen anzupassen. Die Ursache für unleserliche Methoden und obskure Implementierungen sind neben externen Faktoren wie Zeitdruck vor allem fehlende Coding-Standards oder mangelnde Kenntnisse über Entwurfsmuster. Dabei sollte das heutzutage eigentlich kein Problem mehr sein. Mächtige Entwicklungsumgebungen, neue Sprachfeatures und schlichtweg Erfahrungswerte, die bestimmte Standards und Herangehensweisen etabliert haben, ermöglichen es, einfacher besseren Code zu schreiben. Ein guter Einstieg in die Welt der "sauberen" Programmierung ist Clean Code, eine Sammlung von bewährten Prinzipien und Praktiken, die Entwickler schrittweise zu einem guten Programmierstil leiten. 

Was genau ist Clean Code?

Clean Code ist Code, der zum einen gut verständlich und zum anderen gut änderbar ist. Das beginnt bei den Funktionen und den Verantwortlichkeiten der einzelnen Klassen, geht über ihre Beziehungen und ihren Zusammenhang und endet bei der Gesamtarchitektur und dem Kontrollfluss eines Programms. Gleichzeitig ist Clean Code die Sammlung an Richtlinien, die eben diesen "Clean Code" erzeugen. Die Bezeichnung geht auf das gleichnamige Buch von Robert C. Martin zurück. Basierend darauf ist Clean Code mittlerweile auch eine Philosophie geworden. Der Entwickler ist der Träger der Software und damit für ihren Aufbau, ihre Struktur und ihr Aussehen verantwortlich. Man würde nicht in einem Haus mit kaputten Fenstern, wackligem Boden oder abgerissener Tapete leben – warum sollten sich Programmierer also mit schlecht durchdachter, fehleranfälliger Software abfinden? 

Die Regeln von Clean Code

Clean Code folgt vielen Regeln, die sich über die Zeit als guter Standard etabliert haben. Den Grundstein kann jeder Entwickler sofort legen – auch in bereits bestehenden Projekten – indem er sich lediglich der Struktur einzelner Quellcodedateien und der Bezeichnung von Variablen, Klassen und Methoden widmet. Auf der nächsten Stufe stehen Praktiken für das gesamte Softwaredesign und die innere Struktur von Klassen oder Modulen. Durch Refactoring, also die manuelle oder automatisierte Strukturverbesserung von Code unter Beibehaltung des gewünschten Verhaltens, lassen sich diese Prinzipien auch in vorhandenen Projekten umsetzen. In neuen Projekten sollten sie von Anfang an der Maßstab sein. 

Eine saubere Codebasis

Die wichtigsten Regeln für eine saubere und gut leserliche Codebasis betreffen Variablennamen und Funktionen. Hier geht es fast ausschließlich um das Aussehen des Codes und noch nicht um das Design und den Entwurf der Software. 

Klare Bezeichner wählen 

Jede Methode, Variable und Klasse sollte einen sinnvollen Bezeichner besitzen, der ihre Rolle und Funktion klar und unmissverständlich wiedergibt. Für Klassennamen sollten Substantive ohne Abkürzungen verwendet werden, während Methoden und Funktionen mit einem Verb beginnen sollten. 

Die Verwendung des Datentyps als Präfix oder Suffix ist im Übrigen ein Überbleibsel aus alten Zeiten, in denen Entwicklungsumgebungen weniger Metainformationen bereitgestellt haben. Metainformationen sind strukturierte Informationen über andere Daten, also neben dem Datentyp unter anderem Speicherort oder Größe. Dadurch, dass diese Daten leicht eingesehen werden können, sollte man auf sie verzichten, außer, die Information bietet dem Leser des Codes expliziten Mehrwert, beispielsweise weil dieselbe Variable mehrfach konvertiert wird. 

Kurz und präzise 

Funktionen sollten möglichst kurz sein – maximal 4 bis 6 Zeilen. Das hört sich nach wenig an, sorgt aber dafür, dass jede Funktion schnell erfasst werden kann. Ist eine Funktion länger, können Codeblöcke beispielsweise aus If-Anweisungen oder Schleifen in eigene Methoden ausgelagert werden. Dieses Vorgehen nennt sich Extract Methodund ermöglicht es, einen deskriptiven Namen für die Funktion zu vergeben, der die Lesbarkeit für andere Entwickler erhöht. 

Keine Nebeneffekte 

Die Regel "Keine Nebeneffekte" wird implizit bereits erfüllt, wenn Methoden kurz und präzise sind und einen klaren Namen haben. Trotzdem sollte zusätzlich darauf geachtet werden, dass eine Methode nichts tut, was der Entwickler nicht auch anhand des Namens erwarten würde. Dies entspricht dem Principle of Least Surprise und verhindert, dass sich Fehler, die schlichtweg auf Unwissenheit oder falschen Annahmen beruhen, in das Programm einschleichen. 

Wenige Parameter 

Grundsätzlich sollte eine Methode so wenige Parameter wie möglich – maximal zwei – benötigen. Werden mehr Parameter übergeben, können diese in ein eigenes Objekt ausgelagert werden, das stattdessen verwendet wird. Parameter, die der Konfiguration dienen, können vermieden werden, indem mehrere Funktionen implementiert werden. Jede der Funktionen stellt dann eine der konfigurierbaren Optionen bereit. Optimalerweise geben die Funktionen durch ihren Namen bereits Aufschluss über den Unterschied, zum Beispiel GetIntFromString(string input) und GetFloatFromString(string input) anstatt GetNumberFromString(boolean asInt, string input)

Konventionen einhalten 

Wichtig für die Lesbarkeit des Codes ist es, sich an Konventionen zu halten, beispielsweise bei der Verwendung von camelCasePascalCase oder snake_case. Am besten ist es, wenn es projektweite Vorgaben über diese Konventionen gibt, sodass auch der Code verschiedener Entwickler auf den ersten Blick gleich aussieht. Einzelne Programmiersprachen wie C# oder Java geben bereits Richtlinien vor, an die sich Bibliotheken und Software von Drittanbietern halten. So sollten öffentliche Variablen mit Großbuchstaben beginnen, während lokale oder private Variablen klein geschrieben werden. 

Konsistente Bezeichnungen wählen 

Daneben sollte man sich auf einheitliche Wörter und Bezeichnungen für dasselbe Konzept einigen. Beispielsweise sollten Funktionen zum Löschen nicht an manchen Stellen mit "clean" und an anderen mit "delete" oder "destroy" verwendet werden, da dies zu Verwirrung beim Leser führen kann. 

Kommentare vermeiden 

Grundsätzlich sollten im Code abgesehen von Copyright und rechtlichen Hinweisen keine Kommentare nötig sein – der Code spricht für sich selbst. Tut er es nicht, sollte versucht werden, Blöcke in Methoden mit deskriptivem Namen auszulagern oder Variablenbezeichner zu verbessern, um Kommentare zu vermeiden. Das Problem an Kommentaren ist, dass sie schnell veralten und dadurch später falsche oder irreführende Informationen repräsentieren. Wird Code nicht mehr benötigt, sollte er zudem nicht auskommentiert, sondern komplett gelöscht werden. 

An manchen Stellen kann es sinnvoll sein, den Hintergrund einer Methode zu erklären – das ist eine der wenigen Ausnahmen, an denen Kommentare erlaubt sind und sollte im Optimalfall nur an den öffentlichen Schnittstellen notwendig sein. Auch hier besteht aber weiterhin das Risiko, dass der Kommentar veraltet. 

Die Formatierung beachten 

Gut lesbarer Code ist sauber formatiert. Das betrifft beispielsweise Einrückungen, auch wenn diese für den Kompilierer selbst irrelevant sind. Viele Entwicklungsumgebungen bieten automatische und frei konfigurierbare Codeformatierung an. Wie auch für Konventionen über die Schreibweise sollte eine projektweite Richtlinie existieren, die vorgibt, wie der Code formatiert wird. 

Keine "Magic Numbers" 

Magic Numbers sind Zahlen, die direkt im Code stehen und für die daher nicht auf den ersten Blick ersichtlich ist, was sie bedeuten, woher sie kommen und wo sie gegebenenfalls noch verwendet werden. Solche Zahlen sollten in Konstanten mit eindeutigen Namen ausgelagert werden. 

Abschließendes Beispiel 

Wie stark die oben genannten Regeln den Code verbessern können, zeigt folgendes Beispiel. 

for i from 1 to 52: 
    j = randomInt(52)
    temp = i
    i = j
    j = temp

Auf den ersten Blick ist nicht ersichtlich, was die Funktion tut. 

const int DECK_SIZE = 52 
for first_index from 1 to DECK_SIZE: 
    second_index = randomInt(DECK_SIZE)
    swap(first_index, second_index)

Nach Anwendung der Regeln wird allerdings klar, dass es sich um eine Methode handelt, die ein Kartendeck mischt. 

Gute Softwarestruktur und -architektur

Die eben genannten Regeln betreffen lediglich das Aussehen des Codes – erhöhen also die Lesbarkeit. Um die zweite wichtige Eigenschaft von Clean Code, die Änderbarkeit, umzusetzen, muss die gesamte Software gut strukturiert werden. Oft kommen hierfür Entwurfsmuster, sogenannte Patterns, zum Einsatz. Dabei handelt es sich um Lösungsansätze für wiederkehrende Probleme, die teilweise als Standards für Softwareentwicklung verstanden werden können. 

Hohe Kohäsion und lose Kopplung 

Ein Grundkonzept der Softwareentwicklung ist, dass Klassen einen starken Zusammenhalt, also eine hohe Kohäsion, und gleichzeitig möglichst wenige Abhängigkeiten zu anderen Klassen haben sollten. Das wird auch als "lose Kopplung" bezeichnet. Höhe Kohäsion gibt es beispielsweise, wenn alle Felder und Methoden einer Klasse miteinander zusammenhängen und so ein großes Ganzes bilden. 

Gesetz von Demeter 

Das Gesetz von Demeter hängt eng mit dem Prinzip loser Kopplung zusammen. Jedes Objekt soll nur mit seinen nächsten "Freunden" kommunizieren. Das beinhaltet die Methoden des Objektes selbst, die Methoden von Objekten, die als Parameter übergeben werden oder als Instanz hinterlegt sind, und die Methoden von selbst erzeugten Objekten. Verkette Aufrufe verletzen dieses Prinzip und binden Objekte zu sehr aneinander, was spätere Änderungen erschweren kann. 

Single Responsibility 

Klassen sollten nicht nur unabhängig voneinander sein, sie sollten auch jeweils nur eine Verantwortlichkeit haben. Um zu prüfen, ob eine Klasse diese Vorgabe erfüllt, gibt es mehrere Möglichkeiten: Zum einen muss es möglich sein, der Klasse einen guten Namen ohne "Manager" oder "Processor" zu geben und ihre Funktionalität mit maximal 25 Wörtern zusammenzufassen. Zum anderen sollte es nur einen Grund geben, aus dem sich die Klasse ändert. Gibt es mehrere, spricht das dafür, dass die Klasse zu viele Fachgebiete oder Aufgabenbereiche berührt und in mehrere Klassen gesplittet werden sollte. 

DRY (Don't Repeat Yourself) 

Das DRY-Prinzip ist eines der wichtigsten Prinzipien der Softwareentwicklung und bedeutet, dass man niemals Code doppelt schreiben sollte. Benötigt man dieselbe Funktionalität an mehreren Stellen, kann man stattdessen eine Methode oder Klasse implementieren, die an den entsprechenden Stellen aufgerufen wird. Ändert sich dann etwas an der Funktionalität, muss man die Änderung nur an einer einzigen Stelle umsetzen. 

KISS (Keep it simple, stupid) 

Viele Entwickler neigen dazu, bereits am Anfang des Projektes zu viel zu wollen. Sie statten eine Methode mit vielen Möglichkeiten zur Konfiguration über Statusindikatoren(Flags) und Booleans aus oder implementieren unnötige Funktionen, die später gar nicht oder in völlig anderer Form gebraucht werden. Das Ziel sollte es daher zuerst sein, die Grundfunktionalität korrekt zu implementieren – das ist oft schon schwer genug. Anschließend kann man den Code durch Refactoring verbessern oder auf Basis des Gelernten weitere Features umsetzen. 

Fazit

Softwareentwicklung hat sich in den letzten Jahrzehnten stark gewandelt und die Anforderungen an Code sind entsprechend gestiegen: Er muss gut lesbar, wartbar und änderbar sein, denn sonst wird der Code fehleranfällig und man kommt nur noch langsam voran. Mittlerweile gibt es viele Prinzipien und Praktiken, denen Entwickler folgen können, um Code zu schreiben, der diesem Anspruch gerecht wird. Die wahrscheinlich wichtigsten Regeln, die im Prinzip sofort angewendet werden können, sind die Nutzung von klaren Bezeichnern und Variablennamen und das Kürzen von Funktionen auf nur wenige Zeilen. Um wirklichen Clean Code zu schreiben, müssen aber auch Entwurfsmuster und Paradigmen bekannt sein, die die Beziehungen zwischen den Klassen und die Programmstruktur verbessern. Dabei stehen vor allem die klare Trennung von Verantwortlichkeiten, ein starker Zusammenhalt innerhalb einer Klasse und eine lose Kopplung zwischen mehreren Klassen im Vordergrund.

 

Wie gefällt Ihnen unser erster Blogartikel? 

Hinterlassen Sie uns einen Kommentar oder schreiben Sie uns eine E-Mail an kaiser@bmu-verlag.de!

8 Kommentare:

Wolfram am 15.02.19 um 9:38:

Wie Fabian!

Benni am 14.02.19 um 19:08:

Guter Artikel! Ich bin schon auf den nächsten gespannt. Ein bisschen konstruktive Kritik:

- Mehr Beispiele helfen Einsteigern.

- Quellen wie das Buch verlinken und das Cover zeigen.

- Bessere (schönere) Formatierung des Textes und Darstellung des Code in einer Box mit fixed-with Schrift erleichert das Lesen.

    Antwort durch Matthias Kaiser am 15.02.19 um 11:35:

    Hallo Benni, vielen Dank für dein wertvolles Feedback, das wird defintiv in den nächsten Artikel               miteinfließen! Beste Grüße, Matthias Kaiser

tomtss am 15.02.19 um 7:19:
Hi, mit dem Beispiel bin ich aber jetzt…verwirrt… 1.Code: „ for i from 1 to 52: j = randomInt(52) temp = i i = j j = temp …“ Welcher Typ ist denn nun i, j und temp? Anscheinend Int. Dann kann/sollen das kein Variablen von Type Objekte(*) sein, oder? Meiner Ansicht nach, werden im 1.Code hier nur Int-Werte getauscht, und dann verworfen. Das ist doch Sinn frei. 2.Code: „… for first_index from 1 to DECK_SIZE: second_index = randomInt(DECK_SIZE) swap(first_index, second_index) Nach Anwendung der Regeln wird allerdings klar, dass es sich um eine Methode handelt, die ein Kartendeck mischt. \" Ich kann hier nicht erkenne, dass es sich um das Mischen von „Karten“ etc. handelt. Es fehlt das \"Feld/Objekt“ etc. Auch wenn es „Methode“ benannt wird. Selbst wenn swap (hier eine unbekannte Funktion/Methode) auf nicht erkennbare (*)(Objekten-/)Elemente wäre, könnte man das aus dem 2. Code nicht gleich erkennen/\"glauben\". Auf jedem Fall wäre dann die swap Methode hier „unschön“ intransparent, was den Clear Code widerspricht, oder? Ähm grübel

     Antwort durch Matthias Kaiser am 15.02.19 um 11:32:

    Hallo tomtss, das Beispiel ist vielleicht in der Tat nicht optimal gewählt. Die integer Werte könnten        zum Beispiel den Index in einem Array oder ähnlichem repräsentieren. Was das Beispiel aber                    eigentlich Aussagen soll ist, dass durch eine externe Methode das Vertauschen wesentlich                        eleganter und einfacher gestaltet werden kann. Beste Grüße, Matthias Kaiser


Andre am 14.02.19 um 12:56:

Sehr gute zusammenstellung. Gerne mehr davon- - schade, das es nicht in Assembler ist ;-)


Bernhard Moosmann am 14.02.19 um 9:52:

Guter fundierter Vortrag. Gerne mehr davon.

Stephan Strittmatter am 14.02.19 um 9:51:

Hallo, vielen Dank für die schöne Zusammenfassung. Das trifft sich super, denn ich werde mit meinen Auszubildenden (Fachinformatiker Anwendungsentwicklung) eine CCD-Aktion starten, organisieren und unseren Entwickungsteams anbieten! Viele Grüße Stephan Strittmatter

Jojo am 13.02.19 um 19:14:

Ich finde diesen Artikel sehr gut und interessant

Fabian am 13.02.19 um 17:55:

Schöne Zusammenfassung von gültigen Prinzipien.