Blog 6 Min. Lesezeit

Perfekter Code beginnt mit einer guten Geschichte

Diesen Artikel teilen
Perfecting Code: Start with a Good Story

Als Softwareentwickler schreiben wir jeden Tag viel Code. Jeder Codeabschnitt hat seine eigene Geschichte. Er kann beispielsweise:

  • von einer Webressource inspiriert sein (Forum, Tutorial, Blogbeitrag usw.);
  • von einem Open-Source-Projekt auf GitHub, SourceForge oder einer anderen Plattform inspiriert sein;
  • aus dem eigenen Projekt kopiert und eingefügt worden sein;
  • vollständig neu entwickelt worden sein.

Für jeden Codeabschnitt analysiert der Entwickler das zu lösende Problem. Seine Erfahrung und die Einschätzungen des Teams können die Entscheidungen und die Art der Implementierung stark beeinflussen.

Nach dem Schreiben und Committen von Code sollte man bedenken, dass dadurch technische Schulden entstehen können, mit denen sich Maintainer und Entwickler früher oder später befassen müssen. Um diese Schulden zu minimieren und allen Projektbeteiligten die Arbeit zu erleichtern, empfiehlt es sich, einige gute Gewohnheiten zu etablieren und den Code von Anfang an sauber zu halten.

1. Namensgebung

Manchmal verbringen wir viel Zeit damit, den Zweck einer Variablen oder Funktion zu verstehen, nur weil sie a, b oder x heißt. Ein klarer, aussagekräftiger Name hätte ihre Bedeutung von Anfang an deutlich gemacht.

Klare, aussagekräftige Namen nach einer einheitlichen Namenskonvention helfen dabei:

  • den Aufwand beim Lesen und Verstehen des Quellcodes zu verringern;
  • Code-Reviews auf wichtigere Themen zu konzentrieren, statt über Syntax und Namenskonventionen zu diskutieren;
  • Codequalitätswerkzeuge auf relevante Probleme statt auf Syntax- und Stilpräferenzen zu fokussieren.

2. Sichtbarkeit

Die Sichtbarkeit möglichst einzuschränken ist eine gute Praxis, weil sie die Kapselung fördert. Ein minimaler Gültigkeitsbereich macht für Nutzer des Codes klar, auf welche Member von außerhalb einer Klasse tatsächlich zugegriffen werden soll.

Werden sämtliche Klassenmethoden öffentlich gemacht, kann das Nutzer verwirren und den Vertrag der Klasse verschleiern. Dann ist zusätzliche Dokumentation nötig, um zu erkennen, welche Methoden tatsächlich verwendet werden sollen.

3. Parameter

Eine Funktion mit mehr als fünf Parametern deutet meist auf eines von zwei Problemen hin:

  1. Die Funktion übernimmt zu viele Aufgaben. Sie sollte in mehrere kleinere Funktionen mit jeweils weniger Parametern aufgeteilt werden.
  2. In den Parametern verbirgt sich möglicherweise ein eigenes Objekt. Eventuell sollte ein separates Objekt oder eine Datenstruktur erstellt werden, die diese Parameter zusammenfasst.

Das bringt mehrere Vorteile:

  • Der Code wird leichter lesbar.
  • Unit-Tests werden einfacher.
sqlite6

4. Größe

Übermäßig lange Methoden sind schwer zu verstehen und zu warten. Die folgende Empfehlung zur Funktionslänge stammt von der Webseite zum Linux-Coding-Style:

Functions should be short and sweet, and do just one thing.  They should
fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24,
as we all know), and do one thing and do that well.

The maximum length of a function is inversely proportional to the
complexity and indentation level of that function.  So, if you have a
conceptually simple function that is just one long (but simple)
case-statement, where you have to do lots of small things for a lot of
different cases, it's OK to have a longer function.
unreal44

5. Anzahl lokaler Variablen

Methoden mit einem NbVariables-Wert über 8 sind schwer zu verstehen und zu warten. Bei mehr als 15 lokalen Variablen sind Methoden extrem komplex und sollten in kleinere Methoden aufgeteilt werden – sofern sie nicht automatisch von einem Werkzeug generiert wurden.

sqlite7

6. Komplexe Funktionen vermeiden

Zur Erkennung komplexer Funktionen lassen sich viele Metriken verwenden. NBLinesOfCode, die Anzahl der Parameter und die Anzahl lokaler Variablen gehören zu den grundlegendsten.

Weitere nützliche Metriken helfen ebenfalls dabei, komplexe Funktionen zu erkennen:

  • Die zyklomatische Komplexität ist eine verbreitete prozedurale Softwaremetrik, deren Wert der Anzahl möglicher Entscheidungen innerhalb einer Prozedur entspricht.
  • Nesting Depth ist eine Metrik auf Methodenebene und beschreibt die maximale Tiefe verschachtelter Gültigkeitsbereiche innerhalb eines Methodenkörpers.
  • Max Nested Loops entspricht der maximalen Verschachtelungstiefe von Schleifen innerhalb einer Funktion.

Welche Maximalwerte für diese Metriken akzeptabel sind, hängt weitgehend vom jeweiligen Team ab; universelle Grenzwerte gibt es nicht.

Suchen wir nach Funktionen, die möglicherweise refaktoriert werden sollten:

sqlite8

7. Formatierung

Programmierstil und Einrückung beschreiben, wie Quellcode organisiert und dokumentiert wird. Die Einrückung ist Teil des Programmierstils und dient vor allem Lesbarkeit und Übersichtlichkeit. Mit einem guten Styleguide und konsequenter Einrückung lässt sich Code flüssig lesen und leichter verstehen. Eine saubere Einrückung macht Code:

  • leichter lesbar
  • leichter verständlich
  • leichter änderbar
  • leichter wartbar
  • leichter erweiterbar

Ziel von Einrückung und Programmierstil ist es, Programme leichter lesbar und verständlich zu machen. Das spart viel Zeit, wenn Code später erneut betrachtet oder wiederverwendet wird. Ein Styleguide gibt Entwicklern klare Leitlinien, sodass der von einem Team erzeugte Code einheitlich ist und von jedem Entwickler problemlos weiterverwendet werden kann.

8. Kommentare

Manchmal enthält Code überhaupt keine Kommentare, in anderen Fällen wird nahezu jede Zeile kommentiert. Vielleicht kennen Sie den Satz: Guter Code dokumentiert sich selbst.

Ja, es ist eine gute Praxis, Code sauber zu halten und möglichst für sich selbst sprechen zu lassen. In der Praxis ist das jedoch nicht immer möglich. Manchmal muss erläutert werden, was der Code tut.

9. Kopplung

Eine geringe Kopplung ist wünschenswert, weil Änderungen in einem Bereich einer Anwendung dadurch weniger Folgeänderungen in anderen Bereichen erfordern. Langfristig reduziert dies Zeit, Aufwand und Kosten bei Änderungen und beim Hinzufügen neuer Funktionen.

Funktionen, die von vielen anderen Funktionen abhängen, sind oft schwer zu verstehen und zu warten. Daher sollte die ausgehende Kopplung von Funktionen möglichst gering gehalten werden.

sqlite10

10. Kohäsion

Das Single-Responsibility-Prinzip besagt, dass eine Klasse nicht mehr als einen Grund für Änderungen haben sollte. Eine solche Klasse gilt als kohäsiv. Ein hoher LCOM-Wert weist im Allgemeinen auf eine geringe Kohäsion hin. Es gibt mehrere LCOM-Metriken. LCOM liegt im Bereich [0–1], LCOM HS (HS steht für Henderson-Sellers) im Bereich [0–2]. Ein LCOM-HS-Wert über 1 sollte als Warnsignal betrachtet werden. Die Metriken werden wie folgt berechnet:

LCOM = 1 – (sum(MF)/M*F) LCOM HS = (M – sum(MF)/F)(M-1)

Dabei gilt:

  • M ist die Anzahl der Methoden der Klasse (statische und Instanzmethoden; dazu zählen auch Konstruktoren, Property-Getter/-Setter sowie Add-/Remove-Methoden von Events).
  • F ist die Anzahl der Instanzfelder der Klasse.
  • MF ist die Anzahl der Methoden der Klasse, die auf ein bestimmtes Instanzfeld zugreifen.
  • Sum(MF) ist die Summe von MF über alle Instanzfelder der Klasse.

Die Idee hinter diesen Formeln lässt sich so zusammenfassen: Eine Klasse ist vollständig kohäsiv, wenn alle ihre Methoden sämtliche Instanzfelder verwenden. Dann gilt sum(MF)=M*F und damit LCOM = 0 sowie LCOMHS = 0.

Ein LCOMHS-Wert über 1 sollte als Warnsignal betrachtet werden.

unreal36

Fazit

Diese grundlegenden Gewohnheiten helfen dabei, Code von Anfang an sauber zu halten. Warten Sie nicht auf ein großes Refactoring, um aufzuräumen – halten Sie den Code möglichst von Beginn an sauber.

Diesen Artikel teilen