Teil 1: Clean Code – Regeln für guten, sauberen Code


Sauberen und leicht verständlichen Code zu schreiben ist das höchste Ziel in einem guten IT-Projet. Vieles hängt davon ab:

  • Wartbarkeit
  • Einarbeitungszeit für andere Programmierer, versteht man schnell, was einzelne Funktionen erledigen
  • Robustheit bei Änderungen
  • Testbarkeit, fällt alles zusammen, bei kleinen Änderungen, können schnell stabile Updates bereitgestellt werden
  • Popularität bei anderen Programmierern z.B: bei Open Source Projekten, als negative Beispiel sei XT-Commerce genannt

Das sehr zu empfehlende Standardwerk zu dem Thema ist “Clean Code – Refactoring, Patterns, Testen und Techniken für sauberen Code” von Robert C. Martin. In diesem Artikel werden Kapitel 1 bis 3 behandelt.

Aussagekräftige Namen

Der Namen einer Variable, Funktion oder Klasse sollte sofort erklären, warum Sie existiert, was sie tut und wie sie benutzt wird. Wenn eine Variable einen Kommentar benötigt, drückt Sie ihren Zweck nicht aus.

Bsp:

int d //Anzahl vergangener Tage
besser ist:
int daysSinceCreation;

Aussprechbare Namen verwenden

Keine Konstrukte mit unklaren Abkürzungen wie: int daSiCre anstatt von daysSinceCreation.

Suchbare Namen verwenden

Moderne IDEs machen das Suchen einfach, aber es nützt nichts, wenn man nach dem Buchstaben e einer Laufvariable suchen muss und von Ergebnissen überschwemmt wird.

Variablen Namen mit einem Buchstaben sind nur als lokale Variablen in kurzen Methoden zu verwenden.

Keine Member Präfixe für Klassenvariablen

Präfice wie m_ für Klassenvariablen sind unnötig, weil die Klassen und Funktionen möglichst klein seien sollten und moderne IDEs die Variablen farblich hervorheben.

Also nicht $m_variable oder $_variable, sondern $variable schreiben.

Interfaces und Implementierungen

Eine Interface Klasse sollte im Bezeichner keine Erwähnung des Namens beinhalten, wie z.B. IShape, sondern einfach Shape heißen. Die Implementierung des Interfaces sollte am kenntlich sein, am Ende des namens, z.B. ShapeImp.

 Schleifenzähler Variablen

Schleifenzähler Variablen wie i,j oder k sind zulässig, jedoch niemals l (“klein L”), weil schwer von der 1 unterscheidbar. Der Geltungsbereich solcher Variablen muss sehr klein sein.

Klassenamen

Sollten aus einem Substantiv oder einem substantivischem Ausdruck bestehen.

Methodennamen

Methodennamen sollten aus einem Verb oder mit einem Verb beginnen. Der JavaBean-Standard gibt z.B. folgende Präfixe vor: get, set, is.

Wenn Konstruktroren überladen werden sollten statische Factory Methoden verwenden, die die Argumente beschreiben:

//schlecht:
GeoPoint geoPoint = new GeoPoint(2,3.5);
//gut:
GeoPoint geoPoint = new GeoPoint.fromLatLong(2,3.5);

Ein Wort pro Konzept

Es ist bspw. verwirrend fetch, retrieve und get als Namen für gleichwertige Methoden verschiedener Klassen zu verwenden.

Gleiches gilt für controller, manager, driver: Es sollte immer ein konsistentes Lexikon verwendet werden um Klarheit zu schaffen.

Namen der Lösungsdomäne verwenden

Die Leser Ihres Codes werden auch Informatiker sein, deswegen sollten immer Fachbegriffe aus der Informatik oder der Mathematik verwendet werden und keine Begriffe des Kunden (z.B: BWL- Fachbegriffe).

Nur wenn kein passender Ausdruck vorhanden ist, kann ein Begriff der Problemdomäne verwendet werden, weil dann der Kunde evt. weiterhelfen kann.

Keine überflüssigen Informationen

Manche Programmierer stellen den Klassen bestimmte Präfixe vor den Klassenamen, wie z.B. das Zend_Framework (Zend_). In einer Programmiersprache wie PHP, wo Name-Spaces erst mit PHP 5.3 eingeführt worden sind, ist das ok, ansonsten ist die Information redundant.

Kürzere Namen sind im Allgemeinen besser, solange Sie klar sind. Fügen Sie nicht mehr Kontext hinzu als erforderlich.

Funktionen

  • Funktionen sollte sehr klein sein, kaum länger als 20 Zeilen. Alle Funktionen die länger sind, lassen sich zu kleineren Funktionen refakturieren.
  • Einrückungstiefe nicht mehr als 2 Ebenen, steigert die Lesbarkeit ungemein
  • sie sollten eine Aufgabe erledigen
  • eine Abstraktionsebene pro Funktion, das generieren von HTML und das konfigurieren der von Tools liegt bspw. weit auseinander
  • Code sollte von oben nach unten gelesen werden können, es sollte nach jeder Funktion eine aus der nächsten Abstraktionsebene stehen.

SWITCH-Anweisungen

Switch Anweisungen erfüllen meist mehrere Aufgaben, was schlecht ist. Wenn sie nicht vermieden werden können, dann sollten Sie zumindest tief vergraben werden in Klassen und niemals wiederholt werden. Die Lösung ist Polymorphismus und das Abstract Factory Pattern.

Bsp.

public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch(e.type)
{
case FREELANCER:
return calculatePayFreelancer(e);
case SALARIED:
return calculatePaySalary(e);
case COMISSIONED:
return calculatePayComissioned(e);
default:
throw new InvalidEmployeeType(e.type);
}
}

Bei diesem Bsp gibt es mehrere Probleme:

  1. Die Funktion ist zu lang und wird mit jedem neuen Mitarbeiter-Typ länger.
  2. Sie erfüllt mehrere Aufgaben: Fehlerbehandlung und Gehaltsberechnung
  3. Sie muss geändert werden, wenn ein neuer Typ hinzukommt
  4. Es gibt wahrscheinlich mehrere andere Funktionen mit denselben switch-Bedingungen, wie z.B: getPayPerHour(Emplayee e)

Die Lösung besteht darin die switch Anweisung hinter eine Abstract Factory zu verstecken und die Funktionenpolymorph von dem Employee-Interface implementieren zu lassen.

public abstract class Employee{
public abstract Money caluclatePay();
public abstract Money getPayPerHour();
}
-----
public interface EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
----
public class EmployeeFacotryImpl implements EmployeeFactory{
public Employee makeEmployee(Employee e) throws InvalidEmployeeType {
switch(r.type)
{
case FREELANCER:
return new FreelancerEmployee(r);
case SALARIED:
return SalariedEmployee(r);
case COMISSIONED:
return ComissionedEmployee(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}

Anzahl von Funktionsargumenten

Es sollten besten falls kein Argument und höchstens 2 bis 3 Argumente verwendet werden. Umso weniger, umso leichter verständlich sind die Funktionen.

Vorteil ist, dass die Funktionen einfach zu testen sind, weil sich durch das Erhöhen der Argumenten-Anzahl die Anzahl an Kombinationen potenziell ansteigt.

Output-Argumente sind schwieriger zu verstehen als Rückgabe-Werte:

//schlecht: Verwendung von Output-Argumenten
generateHTML("<div>lalala</div>", $html);
//besser: Rückgabewerte verwenden
$html = generateHTML("<div>lalala</div>");

Funktionen mit einem Input-Parameter (monadische Funktionen)

  1. Frage über das Argument stellen: boolean is_int($var)
  2. Das Argument manipulieren, z.B. Umwandeln: InputStream fileOpen(String dateiname)

Flag Argumente

Sind absolut verboten und deutet darauf hin, das mehr als eine Aufgabe bearbeitet wird, z.B. render(true), hier muss erst nachgeschaut werden, wofür der Parameter steht. Die Funktion sollte in 2 Funktionen zerlegt werde, z.B. renderHighQuality() und renderLowQuality().

Funktionen mit zwei Input-Parametern(dyadische Funktionen)

Sind schwer zu verstehen und die Parameterreihenfolge kann zu Problemen führen, z.B. assertEquals(expected, actual). Sinn macht es bei Funktionen, bei den die Parameter logisch zusammen gehören z.B. new Point(1,1).

Viele dyadische Funktionen lassen sich z.B. über die Einführung von Klassenvariablen zu monadischen Funktionen umwandeln. Dasselbe gilt für triadische Funktionen.

Funktionen mit mehr als 2 Input-Parametern(polyadische Funktionen)

Es ist wahrscheinlich, dass die Argumente in separate Klassen eingehüllt werden sollten:

drawCycle(double x, double y, double radius);
//besser:eigene Klasse, verständlichere Bennenung
drawCycle(Point center, double radius);

Nebeneffekte vermeiden

Wenn Funktionen verprechen eine Aufgabe zu erfüllen, aber auch eine verborgene Funktion ausführen, führt das oft zu Bugs. Das können z.B. das Ändern von Klassenvariablen/globalen Variablen sein. Dies führt zu schwer findbaren Fehlern, die vom zeitlichen Aufruf der Funktion abhängig sind und schwer reproduzierbar sind.

function checkPassword($userName, $password)
{
if(isAuthorized($userName, $password))
{
// führt zu Nebeneffekten
session.start();
return true;
}
else
{
return false;
}
}

Anweisungen und Abfragen trennen

Funktionen sollten entweder etwas tun oder etwas antworten, beides ist nicht zulässig.

boolean setString($value)

ist verwirrend, weil

if(setString($value))

heißen kann, dass überprüft wird, ob der Wert schon gesetzt war oder ob der Wert erfolgreich gesetzt worden ist.

Exception vs. Fehlercodes

Durch Fehlercodes als Rückgabe von Argumenten werden viele if-Schleifen provoziert und der Code wird hässlich.Bei der Verwendung von Exceptions kann die Fehlerbhandlung von der Logik getrennt werden. So sollten die try-catch Blöcke immer getrennt werden von der Logik und in einzelnen Funktionen ausgegliedert werden:

public void setString(String value)
{
try{
setStringValue();
}
catch (Exception e)
{
logError(e);
}
}

private void setStringValue(String value) throws Exception
{
this.string = value;
}

private void logError(Exception e)
{
logger.log(e.getMessage());
}

DRY – Don’t Repeat Yourself

Wiederholungen blähen den Code auf und erfordern bei Änderungen Mehraufwand.

Strukturierte Programmierung

Nach Edsger Dijkstra sollte jede Funktion einen Ausgang und einen Eingang haben: Es verbietet sich mehrere return-Anweisungen sowie break, continue und goto zu verwenden.

Dies gilt nur bei langen Funktionen, bei kurzen Funktionen sind mehrere returns, break und continue absolut zulässig und nützlich, weil sie den Code ausdrucksstärker machen.

GOTO sollte wirklich nie verwendet werden.

Wie programmiert man richtig um solche Funktionen schreiben zu können?

Beim Schreiben von Code ist es vollkommen natürlich, dass der erste Entwurf unschön, zu lang und unstrukturiert ist. Dieser Entwurf sollte so lange umgebaut und verbessert werden, begleitet durch Unit-Tests, bis man mit dem Ergebniss zu frieden ist und alle Kriterien erfüllt sind.

weiter zu: Teil 2: Clean Code – richtige und flasche Kommentare

Weiterlesen?

  1. Oder wir benutzen einen einheitlichen Stil um Varblen zu benennen. Sie müssen nicht zwingend aussprechbar sein, solange sie die Funktion komplett erklären.

    “ich” sowie “mpchgr” sind Variablennamen die den Datentyp sowie Funktion vollständig beschreiben.

    Dieses System wird u. a. von Microsoft benutzt

  2. Gute und leichtverständliche Einführung in den Clean Code –> hat mir weitergeholfen.

    Mfg
    CodeHasser: Hasser der Code hasst;)