×

Clean Code – Part 2

1. Einleitung

Diese Blogartikel-Reihe soll einen Einblick in die Kriterien für „Clean Code“ geben – damit ist Code gemeint, der unter anderem die Kriterien Lesbarkeit, Testbarkeit und Änderbarkeit/Wartbarkeit erfüllt. Code, der diese Kriterien nicht erfüllt, kann für die lesenden Entwickler schnell anstrengend werden und sie aufhalten.

Mit der Blogartikel-Reihe soll eine Diskussionsbasis geschaffen werden. Aufbauend auf dieser kann dann in Projektgruppen oder in einem Unternehmen eine detaillierte, individuelle Definition von Codequalität erarbeitet werden – denn natürlich gibt es, wie so oft, kein allgemein gültiges Geheimrezept.

Im ersten Teil dieser Reihe [1] ging es um die Benennung von Variablen, Funktionen und Klassen sowie um die Implementierung von Funktionen. Wer die dortigen Tipps befolgt, der ist bereits auf einem guten Weg, sauberen Code zu schreiben. Aber es geht auch noch besser! In diesem Teil des Artikels soll es um das Kommentieren gehen – oder eigentlich eher darum, was alles besser nicht kommentiert werden sollte – und um die Formatierung von Code. Die gesamte Reihe orientiert sich an dem Buch „Clean Code“ von Robert C. Martins [2].

2. Kommentare

Wenn wir Code kommentieren, dann gibt es eine ganz wichtige Regel: „Kommentiere keinen schlechten Code, sondern schreibe ihn neu“ [übersetzt aus 2]. Meistens sind Kommentare tatsächlich ein Hinweis darauf, dass der Code nicht sauber genug geschrieben ist.

Code sollte – das haben wir im ersten Teil schon gelernt – selbsterklärend sein. Und wenn der Code sich selbst erklärt, dann ist ein erklärender Kommentar überflüssig. Aber jetzt stelle man sich vor, ein solcher, selbsterklärender Code würde geändert. Dann würde den überflüssigen Kommentar vermutlich niemand lesen, er würde nicht mit geändert werden und am Ende etwas ganz Falsches erklären oder an der ganz falschen Stelle stehen! Und dann würde uns unser – zunächst ja gut gemeinter und ganz harmlos daherkommender – Kommentar am Ende nur verwirren.

Aber es gibt ja nicht nur Kommentare, die den Code erklären. In Wahrheit ist auch nicht jeder Kommentar ein Zeichen für schlechten Code – nicht, dass nun jemand sofort alle Kommentare in seinem Quellcode löscht, die er jemals geschrieben hat. An der richtigen Stelle, im richtigen Umfang und mit dem richtigen Ziel können Kommentare den Code verständlicher und lesbarer machen! Im Folgenden werden aus [2] entnommene Kriterien für gute Kommentare und schlechte Kommentare aufgelistet – zum Teil direkt mit Beispiel.

Ein Kommentar sollte verwendet werden, wenn…

  • … er Copyrights und Autorenschaft des Codes dokumentiert, also rechtliche Hinweise.
  • /*
    * Copyright (c) 2019 eck*cellent IT.
    * All rights reserved.
    */
    
  • weiterführende Informationen geliefert werden. Manchmal kann es beispielsweise sinnvoll sein, den Rückgabewert einer Funktion zu beschreiben. Hier sollte man aber zweimal hinschauen, ob nicht ein besserer Funktionsname das Problem genauso gut löst.
  • … er die Absicht erklärt, sofern diese nicht aus dem Code ersichtlich ist. Dabei geht es nie darum, was der Code tut (schließlich kann das, weil wir ja selbsterklärenden Code schreiben, jeder sehen, der den Code liest), sondern darum, warum der Code das tut. Ein Beispiel kann sein, dass der Code umständlich ist, weil in einer Bibliothek, die im Code genutzt wird, ein Fehler ist, der umgangen werden soll.
  • … er Code erklärt, der nicht geändert werden kann, weil er beispielsweise aus einer Bibliothek stammt.
  • … er Warnungen über eventuelle Konsequenzen erwähnt
  • /*
    * ATTENTION:
    * Running this part of code will take extremely long!
    */
    
  • ToDos hinterlassen werden. Das kann hilfreich sein, wenn ein Reminder gesetzt wird, um eine veraltete Funktion auszutauschen, jemand gebeten wird, auf ein Problem zu sehen oder sich einen besseren Namen für eine Funktion oder Variable zu überlegen. Auch Erinnerungen daran, dass etwas geändert werden muss, falls sich die äußeren Bedingungen ändern, sind sinnvoll. ToDos sollten allerdings nicht als Ausrede genutzt werden, um schlechten Code stehen zu lassen.
  • … die Wichtigkeit bestimmter Code-Teile hervorgehoben werden soll, damit diese Teile nicht von anderen Entwicklern gelöscht werden.
  • /*
    * ATTENTION:
    * Altering the following method will break the whole system
    */
    
  • … es sich bei dem Code um eine öffentliche Programmierschnittstelle (API) handelt. Hier gilt eine Ausnahme, da solche Schnittstellen von externen Klienten und Entwicklern gebraucht werden, diese aber die Implementierung nicht sehen können. Daher sollte bei solchen Schnittstellen sehr genau beschrieben sein, wie sie aufzurufen und zu verwenden sind.

Ein Kommentar sollte NICHT verwendet werden, wenn…

  • … er „Gemurmel“ ausdrückt – also eigentlich gar nichts. Manchmal ist es durch einen Prozess notwendig oder durch einen Codestyle im Projekt erwünscht, dass Kommentare geschrieben werden. Wenn ein Kommentar geschrieben wird, sollten wir die notwendige Zeit aufbringen, um sicherzustellen, dass es der beste Kommentar ist, der an dieser Stelle stehen könnte.
  • … er eine Redundanz darstellt, also nicht mehr aussagt als es der Code bereits tut und diesen auch nicht rechtfertigt (siehe „Absicht“ in der oberen Liste).
  • // make Bear eat honey 10 times
    for (int i = 0; i < 10; i++) {
         bear.eatHoney();
    }
    
  • … er irreführend bzw. nicht präzise genug ist, also nicht das aussagt, was der Code eigentlich tut. Ein Beispiel: der Kommentar sagt aus, dass der nachfolgende Code eine Mail verschickt – der Code erstellt aber dann nur das Mailobjekt und verschickt es nicht. [3]
  • //send Mail
    private void mailToContact(Contact contact) {
       mail = Mail.createMail(contact);
    }
    
  • … es keine Notwendigkeit gibt. Nicht jede Funktion und jedes Attribut benötigt ein Java-Doc. Nicht jede noch so kurze Funktion muss mit einem Funktionsheader beschrieben werden.
  • … er zum Historisieren verwendet wird. Code-Historien werden durch die IDE oder Source Control Systeme verwaltet und müssen nicht im Code stattfinden. Auch Zuweisungen können durch das Source Control System nachgesehen werden.
  • /* added by Diana on 24th of September, 2019*/
    private class Contact{
        //…
    }
    
  • .. er einen Positionsmarker darstellt, um einen bestimmten Code-Teil schnell wiederzufinden. Durch eine sinnvolle Aufteilung und Benennung der Funktionen sind solche Kommentare normalerweise nicht notwendig und erschweren nur die Lesbarkeit des Codes.
  • ///////////// ACTION! ////////////////
    
  • … er das Ende einer Klammer markiert. Wenn wir so einen Kommentar setzen möchten, weil wir nicht mehr wissen, wozu diese gehört, ist das ein Zeichen dafür, dass die Funktion an dieser Stelle gekürzt werden sollte!
  •     } //if
    } //for
    
  • … es sich um auskommentierten Code handelt. Andere Entwickler, die den auskommentierten Code sehen, werden denken, dass er da ist, weil er immer noch einen Zweck erfüllt und zu wichtig ist, gelöscht zu werden. Aber es gibt ja die bereits erwähnten Source Control Systeme, die dafür da sind, dass Code, der einmal gelöscht wurde, wiedergefunden werden kann. Also können wir den nächsten Code, den wir auskommentieren möchten, besser gleich löschen.
  • … er HTML-Code enthält. HTML ist nicht dafür gedacht, von Menschen gelesen zu werden, deshalb sind HTML-Kommentare sehr schwer verständlich. Wenn die Kommentare (wie bei JavaDocs) durch ein Tool auf einer Webseite erscheinen, dann ist es die Aufgabe dieses Tools und nicht die des Entwicklers, dass die Dokumentation vernünftig formatiert ist.
  • … er nicht-lokale Information liefert. Ein Kommentar sollte keine systemweiten Informationen enthalten, sondern nur Code kommentieren, der in der Nähe steht. Im folgenden Code ist zum Beispiel der Defaultport angegeben, über den diese Funktion allerdings nicht die geringste Kontrolle hat (außerdem ist der Kommentar auch redundant und verwendet HTML-Formatierung) [2]:
  • /**
    * Port on which the hardware would be connected. Defaults to 8082.
    *
    * @param hardwarePort
    */
    public void setHardwarePort(int hardwarePort) {
        this.port = hardwarePort;
    }
    
  • … er zu viel Information enthält. Metainformationen, wie interessante historische Diskussionen oder irrelevante Detailbeschreibungen, lenken andere Entwickler davon ab, den eigentlichen Code zu verstehen.
  • … er nicht direkt zugeordnet werden kann, also nicht verständlich ist, was der Kommentar jetzt eigentlich genau kommentiert.
  • … es sich um Javadocs in nicht-öffentlichem Code handelt.>
  • … er eher einem Kunstprojekt als einem Teil des Sourcecodes ähnelt. [4]
  • /*
        _     _      _     _      _     _      _     _
       (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)
        / ._. \      / ._. \      / ._. \      / ._. \
       _\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__
     (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
        || B ||      || E ||      || A ||      || R ||
    _  .' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._
     (.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)
      `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'
    
    */
    

3. Formatierung

Wenn andere Entwickler unseren Code ansehen, dann wollen wir sie damit beeindrucken. Sie sollen am besten auf den ersten Blick sehen, dass hier ein Profi am Werk war – aber wie schaffen wir das?  Ein ganz wichtiger Punkt ist die Formatierung. Sie ist eine der Grundlagen zur Kommunikation mit dem Code. Wenn unser Code nicht schön und konsistent formatiert ist, dann wird ein Leser uns möglicherweise unterstellen, dass wir auch Rest des Projekts mit genauso wenig Sorgfalt behandelt haben. [2]

Es gibt zwei Prinzipien, auf die wir achten können, wenn wir eine schöne Formatierung angehen möchten: die vertikale und die horizontale Formatierung.

Vertikale Formatierung

Hier geht es um die Größe von Source Files und die Anordnung des Codes innerhalb dieser.

Auch wenn in Java die Größe der Dateien eng zusammenhängt mit der Größe von Klassen, soll die Klassengröße erst einmal eine untergeordnete Rolle spielen – dazu kommen wir dann im dritten Teil dieser Reihe. Grundsätzlich gilt, dass kürzere Dateien einfacher verständlich sind als längere Dateien. In einer Analyse, die in [2] durchgeführt wurde, stellte sich heraus, dass es möglich ist, signifikante Systeme zu bauen, die aus Dateien mit einer mittleren Länge von 200 Zeilen Code bestehen und bei denen die Dateien 500 Zeilen nicht überschreiten. Das ist ein Ziel, das angestrebt werden sollte.

Zur Anordnung des Codes können wir uns unsere Source-Dateien ein wenig so vorstellen wie einen Zeitungsartikel. Wenn wir einen Zeitungsartikel lesen, dann erwarten wir, dass der Titel erklärt, was in dem Artikel stehen wird, dass der erste Absatz die wichtigsten Informationen ohne viele Details zusammenfasst und dass im Folgenden dann die einzelnen Aspekte noch einmal genauer erläutert werden. So ähnlich können wir es mit den Source-Dateien auch halten: der Name des Moduls sollte uns verraten, ob wir an der richtigen Stelle sind, oben sollten die High-Level-Konzepte und Algorithmen stehen und im weiteren Verlauf immer kleinere Details behandelt werden. [2]

Um eine solche Struktur zu erreichen, gibt es einige Konzepte, auf die wir achten können:

  • Die vertikale Dichte ist ein Maß dafür, dass inhaltlich eng zusammenhängende Codezeilen auch dicht beieinander stehen. Variablendeklarationen sollten so nahe bei der Verwendung der Variablen stehen wie möglich, also am besten am Anfang jeder Funktion; Schleifen-Variablen sollten in der Schleifendeklaration stehen; Instanz-Variablen am Anfang der Klasse. Voneinander abhängige Funktionen sollten ebenfalls dicht beieinander stehen. [2]
  • Die vertikale Distanz ist allerdings auch wünschenswert – und zwar dann, wenn es um Codezeilen geht, die inhaltlich nicht direkt zusammenhängen. Ein „Absatz“ im Code sollte etwa einem Gedanken entsprechen. Die einzelnen Absätze sollten durch Leerzeilen getrennt werden, sodass beim Lesen ein Gedanke nach dem anderen angesehen und verstanden werden kann. [2]
  • Konzeptuelle Affinität: Es gibt Code-Teile (z.B. Funktionen), die einfach gerne zusammen stehen möchten. Je höher die Affinität, desto näher sollten die Teile zusammenstehen. Eine solche Affinität kann sich neben der direkten Abhängigkeit auch dadurch ausdrücken, dass ähnliche Operationen ausgeführt werden (oft erkennbar an ähnlichen Namensgebungen). [2]
  • private void eatHoney() {
        //...
    }
    
    private void eatBees() {
        //...
    }
    
    private void eatFish() {
        //...
    }
    

Das Gesamtziel der vertikalen Anordnung ist, dass der Flow innerhalb einer Datei – eben wie in einem Zeitungsartikel – von groben zu detaillierten Konzepten geht. Das wird fast automatisch erreicht, wenn die aufrufende Funktion jeweils über der aufgerufenen Funktion steht; man spricht dann davon, dass Aufruf-Abhängigkeiten von oben unten zeigen.

Horizontale Formatierung

Neben der vertikalen Formatierung ist auch die Horizontale wichtig.

Einigen Entwicklern mag der Name Hollerith noch ein Begriff sein: Herman Hollerith entwickelte 1890 ein Lochkartenverfahren mitsamt Stanz- und Auswertungsmaschinen, das später im Computerbereich weit verbreitet war. Diese Lochkarten hatten eine Zeilenlänge von 80 Zeichen. Die 80 Zeichen wurden dann auch lange über das Ende der Lochkarten-Ära hinaus noch als Grundlage für die maximalen Zeilenlängen von Emails, Texten und auch Terminals verwendet. [5]

Doch genug der Geschichte. Eine Analyse, die in [2] durchgeführt wurde, zeigt: Entwickler präferieren tatsächlich kurze Codezeilen. Etwa 70% der untersuchten Codezeilen war unter 80 Zeichen lang. Auch Zeilen mit 100 oder 120 Zeichen werden durchaus regelmäßig genutzt und sind – laut [2] – auch noch lesbar. Alles was darüber hinausgeht sollte allerdings dringend überprüft werden – vermutlich kann es gekürzt werden.

Einige weitere Faktoren zur schönen horizontalen Formatierung, die in [2] genannt werden, sind im Folgenden aufgelistet:

  • Auch in horizontaler Richtung gibt es die Konzepte Dichte und Distanz. Leerzeichen können – genauso wie Leerzeilen, aber auf einer detaillierteren Ebene – verwendet werden, um anzuzeigen, wie eng zusammenhängend Codeteile sind. Beispielsweise werden Zuweisungsoperatoren häufig durch Leerzeichen rechts und links abgetrennt. Das betont, dass sie aus dem Teil bestehen, dem etwas zugewiesen wird und dem Teil, der zugewiesen wird. Funktionsargumente hingegen werden üblicherweise nicht mit Leerzeichen von der Funktion abgetrennt, weil sie enger miteinander zusammenhängen.
  • int number = 3;
    setNumber(number);
    
  • Einige Entwickler, speziell solche, die aus der Assembly-Programmierung kommen, kennen die Angewohnheit, horizontale Anordnung zu verwenden, um bestimmte Strukturen herauszustellen – beispielsweise die Deklaration von Variablen (siehe Beispiel). Dies kann allerdings einen falschen Eindruck davon vermitteln, welche Codeteile direkt zusammengehören und verleitet dann beispielsweise in der Liste der Variablendeklarationen dazu, die Typen der Variablen zu ignorieren und nur deren Namen durchzulesen.
  • private Bear                    bear;
    private String                  bearName;
    private SomethingWithALongName  something;
    private int                     bearAge;
    private double                  bearSize;
    
  • Die Meisten werden schon darauf gewartet haben: Wichtig ist natürlich auch die Einrückung. Code besteht aus verschiedenen Hierarchieebenen, auf denen jeweils Variablen existieren können, die auf höheren Hierarchiestufen nicht sichtbar sind. Bei den Hierarchieebenen handelt es sich um Klassen in der Datei, Funktionen in den Klassen, Blöcke in den Funktionen und dann auch noch Blöcke in den Blöcken. Um diese Hierarchieebenen visuell zu verdeutlichen, verwenden wir Leerzeichen (bzw. Tabs) zur Einrückung. Entwickler verlassen sich auf diese Einrückungs-Praktiken. Mit der Zeit lernen wir, Code, der korrekt eingerückt ist, schnell zu lesen und die Ebenen, die für unsere aktuelle Situation irrelevant sind, einfach zu überspringen. Tatsächlich ist Code ohne Einrückung für das menschliche Auge kaum lesbar. Auch wenn es manchmal verlockend sein kann, die Einrückungsregeln zu brechen (zum Beispiel, weil man nur eine ganz kurze While-Schleife hat), sollte man sich das am besten gar nicht erst angewöhnen.
  • Manchmal müssen sogenannte „Dummy Scopes“ gebaut werden, also beispielsweise Schleifen, die, außer zu iterieren, nichts tun. Diese sollten, wie alles andere auch, korrekt formatiert werden und keine der sonst eingehaltenen Regeln brechen – das kann langer Debugging-Arbeit vorbeugen!
  • while(doSomething) { } // Negativbeispiel
    while(doSomething) {
    
    } // besser!
    

Natürlich sind die hier beschriebenen Regeln nicht in Stein gemeißelt. Jeder Entwickler hat seine eigenen Vorlieben, wenn es um Formatierung geht (man sehe sich nur die endlose Diskussion um Tabs oder Leerzeichen an). Wenn er aber in einem Team arbeitet, dann sollte das Team die Regeln bestimmen. Zu Beginn eines Projektes sollten sich alle Entwickler einmal für etwa 10 Minuten zusammensetzen, um einen Coding-Style auszumachen – so können konsistente, leicht lesbare Source Dateien erstellt werden [2]. Es kann darauf vertraut werden, dass Formatierungen, die in einer Datei benutzt wurden, in einer anderen dasselbe bedeuten.

Die wichtigste Regel ist also keine der oben genannten, sondern dass das Team sich auf einen Coding-Style einigt und diesen dann konsistent umsetzt. Schließlich soll es nicht so aussehen, als wäre die Software durch einen Haufen sich widersprechender Individuen entstanden, denn das wirkt unprofessionell – und unser Ziel ist ja, dass unsere Software auf den ersten Blick als das Werk von Profis erkannt wird!

4. Zusammenfassung

In diesem Artikel wurde eine ganze Reihe Regeln vorgestellt, wann Kommentare zu schreiben und wann sie besser zu unterlassen sind. Zusammengefasst lässt sich sagen: Kommentare sollten nur dann verwendet werden, wenn es nicht möglich ist, das, was gesagt werden soll, im Code auszudrücken. Dann allerdings können sie dafür sorgen, den Code verständlicher und lesbarer zu machen. Es ist außerdem wichtig, darauf zu achten, Kommentare zu ändern (oder häufig besser: zu löschen), wenn man Code ändert, damit die Kommentare nicht nutzlos werden. [6] Außerdem haben wir die Konzepte der vertikalen und horizontalen Formatierung kennengelernt und welche Regeln hier für übersichtlichen Code sorgen. Wichtig ist hierbei vor allem, dass alle Mitglieder einen Teams sich einigen, wie formatiert werden soll und sich dann daran halten.

In den meisten Unternehmen gibt es unternehmens- und/oder projektweit spezielle Coding-Styles zum Thema Kommentare und Formatierung, welche von allen Beteiligten eingehalten werden sollen – so ist es einfach, Code zu schreiben, der gleich auf den ersten Blick einen professionellen Eindruck macht.

Wer mehr über Clean Code erfahren möchte, kann noch einmal den ersten Teil dieses Artikels lesen [1] oder sich direkt das Buch [2] schnappen, in dem alles noch einmal ausführlicher beschrieben ist. Natürlich könnt ihr auch gespannt auf den nächsten Teil dieser Artikel-Reihe warten! Dort wird es dann um Objekte, Datenstrukturen und Fehlerbehandlung gehen.

5. Quellen

Von Diana Baumgaerte | 22.10.2019
Diana Baumgaerte

Softwareentwicklung