Die Kunst vom lesbaren Source Code

Es ist wieder Urlaubszeit. Schon viel zu lange -- seit über 5 Jahren -- steht das Buch “The Art of Readable Code” ungelesen bei mir im Regal. Am Strand der französischen Atlantikküste habe ich endlich die Zeit gefunden, dieses sehr interessante Buch zu lesen. Im Folgenden eine kurze Zusammenfassung mit wichtigen Tipps, die als Anregungen für eigene Code-Conventions verwendet werden können.
Buch 'The Art of Readable Code'


Die Autoren von “The Art of Readable Code [Amazon-Werbepartner] ” haben über einen Zeitraum von 5 Jahren den teameigenen wie auch fremden Quellcode in großem Umfang analysiert. Sie wollten wissen was schlechten Source Code ausmacht und an welchen Kriterien man dieses festmachen kann. Ihr schlichtes und dennoch allgemeingültiges Fazit: Source Code muss vor allem lesbar sein. Nicht Testbarkeit, Design, Modularisierung usw. müssen die primären Ziele sein, sondern die Tatsache ob ein Fremder (oder man selbst später) den Source Code zügig versteht. Das Beste: Gut lesbarer Source Code führt meist automatisch zu guter Struktur, Testbarkeit usw.

Folglich gibt es für die Verfasser eine zentrale Metrik, an welcher die Qualität von Source Code zu messen ist (bezeichnet als “The Fundamental Theorem of Readability”):


Source Code sollte so geschrieben werden, dass ein anderer Entwickler ihn in minimaler Zeit versteht.

Also lasst die Stoppuhr mitlaufen, wenn jemand euren Code zu verstehen versucht. :)


Viel Neues?

Das Buch besteht aus einer Vielzahl von Tipps und praktischen Beispielen, die zeigen wie wir Softwareentwickler gut lesbaren Quellcode produzieren. Dabei wird das Rad nicht neu erfunden. Viele Hinweise sind dem erfahrenen Entwickler (hoffentlich) bekannt und können auch anderer Fachliteratur entnommen werden. Dennoch hat das Buch einen ganz besonderen Reiz. Es ist eine kompakte und programmiersprachenunabhängige Zusammenfassung, die uns sehr anschaulich, u.a. mit lustigen Cartoons, die wichtigsten Kriterien für guten Source Code einimpft.


Im ersten von vier Abschnitten geht es in dem Buch um “oberflächliche Verbesserung”, wozu insbesondere das Thema Naming, Formatierung und Kommentare gehören.


Nomen est Omen

Das wohl wichtigste Kriterium für gut lesbaren Source Code ist die “Benamung” von Variablen, Methoden/Funktionen, Klassen und Modulen. Ein Name ist schnell vergeben. Einen ausdrucksstarken und verständlichen zu finden, ist jedoch nicht trivial. Namen sollten einen beschreibenden Informationsgehalt beinhalten und wenig Interpretationsspielraum beinhalten. Dabei darf er aber auch nicht zu lang werden, da sonst die Lesbarkeit wieder leidet.


Ein Beispiel: Was ist schlecht an der Benennung der folgenden Konstante ?

SHOPPING_CARD_ITEM_LIMIT = 15

Bedeutet es, dass keine 15 Gegenstände in den Warenkorb gelegt werden dürfen (also maximal 14)? Oder sind 15 Items noch erlaubt, aber keine 16 mehr? Klar wird es, wenn man MAX (oder MIN) verwendet. Der versierte Programmierer weiß, dass MAX immer als “inklusive” definiert ist. In diesem Beispiel darf der Käufer also bis zu 15 Dinge shoppen:

MAX_SHOPPING_CARD_ITEMS = 15

Die Kunst ist, sich bei der Vergabe von Namen in eine fremde Person zu versetzen. Stelle Dir immer die Frage: Würde die fremde Person genau verstehen, was gemeint ist?

Cartoon Variable Namen
Bezeichner/Namen sollten genug Informationsgehalt beinhalten

Hier ein paar weitere Tipps zum Thema “good Naming”:

  • Verzichte auf generische Namen und Buchstabenabkürzungen wie z.B tmp, retval, a, h. Ausnahme von der Regel: Existierende Konventionen, wie z.B. i in einer for-Schleife oder ein tmp, welches nur innerhalb weniger Zeilen genutzt wird.
  • Verwende Maßeinheiten in Variablennamen, falls es um Messungen geht. Beispielsweise: var start_ms anstatt var start oder var size_mb anstatt var size.
  • Kurze einfache Namen sind für den Fall eines kleinen Scopes/Sichtbarkeit ok. (z.B. Map map in einem Block aus drei Zeilen). Verzichte auf extra lange Namen wie z.B. Map hashContainingIdAndSizeOfImageForBlogPost. Finde das richtige Mittelmaß mit genug Informationsgehalt; z.B. Map blogImageSizeMb
  • Entferne unnötige Namensbestandteile ohne Informationsgehalt. Z.B. anstatt convertToString() einfach nur toString()
  • Minimiere Interpretationsspielraum in den Bezeichnern. Beispiele:
    • min und max anstatt limit (siehe Beispiel oben)
    • include(), exclude(), select() anstatt filter()
    • first und last anstatt start und stop
  • Berücksichtige Konventionen und Erwartungen (z.B. getXXX()-Methoden haben die Erwartung einer schnellen Ausführungszeit; also lieber calcWhateverComplex() als getWhateverComplex() )

Ästhetik

Zu den oberflächlichen Verbesserungen, welche deutlich die Lesbarkeit des Source Code verbessern, gehört die Formatierung. Stelle sicher, dass Dein Quellcode so formatiert ist, dass auf den ersten Blick Zusammenhänge richtig erkannt werden können.

Vergleiche beispielsweise folgende Codeschnipsel mit gleichem Inhalt aber unterschiedlicher Formatierung, dann weißt Du was gemeint ist. :)


List config = [[ "timeout", 123, null, FIRST_PARAM_ALLOWED], [ "firstInFirstOutUsage", null, "<>", null ], [ "useragent", 9934.34, "sdfwekljlwerwerwer", NO ]]

eingerückt:

List config = [ [ "timeout", 123, null, FIRST_PARAM_ALLOWED ], [ "firstInFirstOutUsage", null, "<>", null ], [ "useragent", 9934.34, "sdfwekljlwerwerwer", NO ] ]

Noch wichtiger als die absolut perfektionierte Formatierung ist jedoch eine einheitliche Regelung im Projekt:


Einheitliche Formatierung ist wichtiger als überall die “richtige” Formatierung zu verwenden!

So ist es z.B. unerheblich ob die Klammersetzung so

class Logger {
   ....
};

oder so

class Logger
{
   …..
};

erfolgt. Beides ist ok, es sollte jedoch nur eine Variante von allen Teammitgliedern verwendet werden. Haltet eure Formatierungskonventionen kurz und schriftlich fest und verwendet die IDE-Formatierungs-Tools um diese durchzusetzen.


Kommentierung gewünscht?

Eine strikte und moderne Auffassung besagt, dass der Source Code an sich nicht dokumentiert werden sollte. Der Code muss so geschrieben sein, dass er selbsterklärend und somit leicht lesbar ist. Das einzige, welches dokumentiert werden sollte, ist das “Warum”. Also die Frage wieso diese Methode/Klasse/Modul usw. notwendig ist. Die Dokumentation würde in so einem Falle also ausschließlich Fragen zu Designentscheidungen und fachlichen Anforderungen erläutern. Die Kommentare würden keinesfalls erläutern, “was” der Source Code macht.

Für die Autoren des Buches ist dieser Ansatz zu radikal. Sie plädieren für einen gesunden Mittelweg. Selbstverständlich ist die Dokumentierung des “Warums” richtig und notwendig. Beim Schreiben des Quellcodes sollten wir unsere Gedankengänge und Entscheidungen immer kurz und präzise im Code kommentieren. Darüber hinaus gibt es jedoch durchaus Fälle, wo ein kurzer Kommentar über das “Was” die Lesbarkeit des Codes deutlich erhöht.


Cartoon documentation and comments
Wo Kommentare sinnvoll sind….

Nicht sinnvoll sind Kommentare…

  • nur weil laut Chef “immer alles kommentiert werden muss”
  • die unklare Namen von Variablen/Methoden/Klassen erläutern (stattdessen sollten die Bezeichner umbenannt werden)

Sinnvoll sind Kommentare…

  • im (legacy) Code bei komplizierten Codestellen (z.B. komplexe “One-Liner”)
  • bei wichtigen Gedankengängen/Anforderungen/Designentscheidungen
  • bei Mängeln und offenen Aufgaben im Code mittels TODO: FIXME: HACK: oder XXX:
  • bei Definition von Konstanten
  • vor allem, wenn sie kurz und kompakt sind (“high information-to-space ratio”)
  • oft als kurzes Beispiel/Pseudocode (“ein Beispiel kann mehr als tausend Worte sagen”)
  • bei nicht Verwendung von Pronomen (verzichtet auf it und this usw.)

Keep Complexity Simple

Nach den oberflächlichen Verbesserungen für lesbaren Code, kümmern sich die Autoren um das Thema wie komplexere Logiken und Schleifenkonstrukte verständlich bleiben.

Ihre Ansage an uns ist dabei folgende:


Alle Bedingungen, Schleifen und andere Konstrukte, die den Kontrollfluss beeinflussen, sollten so “normal und natürlich” wie möglich formuliert werden -- also in der Form, dass ein Leser nicht anhalten und Source Code wiederholt lesen muss.

Im Folgenden ein paar -- aus meiner Sicht wichtige und selbstverständliche --Tipps der Autoren


Die linke Seite in Bedingungen sollte die variablere sein, die rechte die konstantere:

Also

if (length  >= 10)   // good

anstatt

if (10 <= length)    // bad

Weiter gilt für die Frage was in den if-Zweig vs. else-Zweig geschrieben werden soll, folgendes:

  • Verhindere Negierungen; also lieber if (debug) … else …..; anstatt if (!debug) ….
  • Starte mit dem einfacheren/kürzeren Teil in dem if-Zweig und der komplexere Teil geht in den else-Zweig
  • Im Zweifel starte im if-Zweig mit dem interessanteren und wichtigeren Teil.

Cartoon if else command
Auf die Reihenfolge kommt es an….

Zusammenfassend weitere wichtige Tipps, um die Lesbarkeit von komplexen Logiken zu verbessern:

  • Verwende den Ternary Operator (cond ? a : b) nur bei einfachen Ausdrücken; sonst nutze die if-Anweisung
  • Nutze keine do/while Schleifen
  • Returniere früh von Methoden/Funktionen und verzichte auf unnötige else-Zweige (z.B. if (str == null) return false)
  • Minimiere Verschachtelungen; jeder zusätzlich eingerückte Code-Block erschwert die Lesbarkeit
  • Die Nr. 1 für lesbaren Code: Lagere zusammenhängenden Source in eigene Methoden und Funktionen aus. Bei langen Ausdrücken (z.B. in der if-Expression): Teile sie und lagere diese in eigene boolsche Variablen aus.
  • Unterteile lange Ausdrücke in beschreibende Variablen; z.B. line.split(‘:’)[0].strip() == “root” aufteilen in username = line.split(‘:’)[0].strip() und dann username == “root
  • Boolsche Ausdrücke können durch De Morgan’s Laws vereinfacht werden.
  • Unnötige Variablen, die keine Lesbarkeit erhöhen, gilt es zu entfernen. Minimiere den Scope einzelner Variablen soweit möglich. Änderungen von Variablen-Inhalten minimieren (oder unveränderbar machen).

Teile und Herrsche -- Code Sanierung

Beim Engineering geht es darum große komplexe Probleme in kleine Einheiten zu teilen und diese für eine Gesamtlösung geschickt zusammenzuführen. Das gleiche Prinzip gilt auch für das Software Engineering. Die Autoren zeigen im dritten Teil des Buches, was dabei besonders zu beachten ist, so dass die eigene Software maximal lesbar bleibt. Freunde des Refactorings werden sich hier wohl fühlen.


Im ersten Abschnitt geht es um die Extrahierung von Code in eigenständige Funktionen (Refactor: Extract Method). Weitere oben habe ich dieses Verfahren nicht ohne Grund bereits als “Nr.1 für lesbaren Code” bezeichnet.

Die Autoren betrachten das Extrahieren von Funktionalität insbesondere für den speziellen Fall von unabhängigen Teilproblemen (“unrelated subproblems”). Anhand eines JavaScript-Beispiels zur Bestimmung eines Ortes, der am nahesten zu einer gegebenen Lokation liegt, machen sie es deutlich:


Vorher:


// Return which element of 'array' is closest to the given latitude/longitude. // Models the Earth as a perfect sphere. var findClosestLocation = function(lat, lng, array) { var closest; var closest_dist = Number.MAX_VALUE; for (var i = 0; i < array.length; i += 1) { // Convert both points to radians. var lat_rad = radians(lat); var lng_rad = radians(lng); var lat2_rad = radians(array[i].latitude); var lng2_rad = radians(array[i].longitude); // Use the "Spherical Law of Cosines" formula. var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) + Math.cos(lat_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng_rad)); if (dist < closest_dist) { closest = array[i]; closest_dist = dist; } } return closest; };

Nachher (Berechnung der Distanz ausgelagert):


var spherical_distance = function(lat1, lng1, lat2, lng2) { var lat1_rad = radians(lat1); var lng1_rad = radians(lng1); var lat2_rad = radians(lat2); var lng2_rad = radians(lng2); // Use the "Spherical Law of Cosines" formula. return Math.acos(Math.sin(lat1_rad) * Math.sin(lat2_rad) + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng1_rad)); }; var findClosestLocation = function(lat, lng, array) { var closest; var closest_dist = Number.MAX_VALUE; for (var i = 0; i < array.length; i += 1) { var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude); if (dist < closest_dist) { closest = array[i]; closest_dist = dist; } } return closest; };

Also: Wann immer Source-Code wiederverwendet wird (“Redundanzen”) oder ein unabhängiges Teilproblem aus vorhandenem Code extrahiert werden kann, gönnt euch eine neue Funktion, Methode oder Klasse. Aber auch hier gilt: Findet ein gesundes Mittelmaß. Man könnte theoretisch jede kleinste Teil-Funktionalität in eine eigene Funktion auslagern. Dies wäre kontraproduktiv und würde die Lesbarkeit verschlechtern.



Weiter geht es in dem Buch mit der Frage, warum es für lesbaren Software-Code extrem wichtig ist, Aufgaben nicht zu vermischen. One Task at a Time. Sonst ergeht es unserem Quellcode wie diesem Mann:


Cartoon Multitasking
Mehrere Aufgaben auf einmal -- es wird unübersichtlich

Das Ziel ist es folglich die Aufgaben in unserem Code nicht zu vermischen. Mit anderen Worten: Vermeide Seiteneffekte und behandle in einem Code-Block immer nur eine Funktion/Fachlichkeit.

In dem Buch wird dieser Vorgang auch als “Defragmentierung” beschrieben. Dabei sollen unterschiedliche Aufgaben in unterschiedliche Funktionen ausgelagert werden. Jedoch auch innerhalb eines Codeblocks sollten zusammenhängende Aufgaben gruppiert werden:


Defragmentiere Source Code


Ein weiterer Abschnitt im dritten Teil des Buches beschreibt ein einfaches Verfahren, dass uns hilft, den Code besser zu strukturieren und lesbarer zu machen. Die Idee leitet sich von dem berüchtigten Albert Einstein Zitat ab:


”Was man der eigenen Großmutter nicht erklären kann, hat man nicht wirklich verstanden.” (Albert Einstein)

Das gilt gleichermaßen für unseren Source Code. Wenn wir nicht in einfachen Worten wiedergeben können, was unser noch zu schreibender Quellcode tun soll, wird dieser höchstwahrscheinlich nicht sehr lesbar werden. Von daher empfehlen die Autoren folgendes Vorgehen auszuprobieren:

  1. Beschreibe in kurzen Sätze, was der von dir geplante Source Code tun soll. So als wenn Du es einem Kollegen erklären möchtest.
  2. Betrachte Schlüsselwörter und wichtige Sätze in deiner Beschreibung.
  3. Schreibe den Code so, dass er im Ablauf der Beschreibung entspricht.

Im Buch werden hierzu ein paar Beispiele aufgezeigt. Das Verfahren überzeugt. :)


Ein letzter kleiner -- aus meiner Sicht aber wichtiger -- Hinweis in diesem Abschnitt: Um prägnante und damit lesbare Software zu programmieren, muss man wissen was die Programmiersprache und die API/Bibliotheken alles anbieten.


Der letzte Abschnitt im dritten Teil des Buches, lautet: “Weniger Code schreiben”.

Kernfrage: Was ist der am besten lesbare Source Code?
Einfache Antwort: Der, der gar nicht geschrieben wurde!

Der Les- und Wartbarkeit eures Software-Projektes tut ihr einen großen Gefallen, wenn ihr folgende Hinweise zum Reduzieren von Code beachtet:

  • Keep it simple. Suche und implementiere zunächst die einfachste Lösung für das Problem. Probiere nicht die allumfassende eierlegende Wollmilchsau zu bauen, die alle möglichen zukünftigen Anforderungen mit abdeckt.
  • Remove duplicates. Entferne Redundanzen indem Du Source Code zusammenfasst. Oft landen solche Code-Stellen in Utility-Klassen/Modulen.
  • Remove unused code. Auch wenn es weh tut oder die Angst besteht, Dinge kaputt zu machen: Entferne nicht (mehr) benötigten Quellcode.

Schluss

Im letzten Teil des Buches veranschaulichen die Autoren das Gelernte sehr anschaulich an zwei Praxisbeispielen. Zum einen wird gezeigt wie Unit-Tests lesbar geschrieben werden und wie sich testbarer Code und Lesbarkeit gegenseitig befruchten. Zum anderen wird Schritt für Schritt anhand der Implementierung eines “Minuten/Stunden Counters” gezeigt, wie die Konzepte aus dem Buch iterativ in einem Projekt angewendet werden können.


Fazit

Die Quintessenz aus dem Buch könnte man wie folgt zusammenfassen:

Versetze dich in einen neuen Kollegen, der Deinen Source Code verstehen muss. Würde ihm das leicht fallen? Falls Du einen Kollegen zur Hand hast noch besser: Frage ihn, ob er Deinen Code versteht (“Kurzes oberflächiges Code Review”). Wenn nicht, verbessere ihn oder füge Kommentare hinzu.


Das Buch ist sehr empfehlenswert, um in kompakter Form wesentliche Konzepte zum Programmieren von lesbarer Software zu erlernen bzw. aufzufrischen. Es kann u.a. bei Amazon [Amazon-Werbepartner] oder innerhalb eines Safari-Abos bei O’Reilly betrachtet und erworben werden.


The Art of Readable Code
The Art of Readable Code [Amazon-Werbepartner]