Schluss mit Frust: Clean Code hilft bei der Softwarequalität

Sauberen, verständlichen und wartbaren Code zu schreiben ist eine Kunst. Aber nur so lässt sich unnötiger Frust beim Softwareentwickeln vermeiden.

In Pocket speichern vorlesen Druckansicht 1013 Kommentare lesen

(Bild: nattaphol phromdecha/Shutterstock.com)

Lesezeit: 16 Min.
Von
  • Arkadius Roczniewski
Inhaltsverzeichnis

Was ist schlechter Code und wie unterscheidet er sich von Clean Code? Was genau verstehen Entwicklerinnen und Entwickler unter Clean Code und wie hilft er ihnen dabei, ihren Quellcode "sauber" zu bekommen? Ziel dieses Artikels ist es, Antworten auf diese Fragen zu geben und zu demonstrieren, wie sich durch Clean Code Frust im Arbeitsalltag vermeiden lässt.

Ausgangspunkt soll eine den vermutlich meisten Entwicklerinnen und Entwicklern vertraute Situation in einem beispielhaften Softwareprojekt sein: Ein erfolgreich gereiftes Projekt mit kontinuierlicher Entwicklung rund um ein Kernteam sucht einen neuen Mitarbeiter. Die Anwendung ist bereits seit einigen Jahren im produktiven Einsatz und das Team hat in dieser Zeit bereits mehrere Personalwechsel hinter sich gebracht. Mit einem auf weitere zwei bis drei Jahre ausgerichteten Budget existiert eine solide Grundlage, um auch in Zukunft neue Features zu entwickeln und entscheidende Fortschritte zu erzielen.

Auch wenn sich die Stellenbeschreibung auf dem Papier nahezu perfekt liest, hinterlässt die Realität im Projekt bei den neuen Teammitgliedern häufig nur Frust. Das Problem dabei: Die Bewerberinnen und Bewerber erhalten vor dem Einstieg in das Projekt keinen Einblick in die Codebasis. Enthält diese aber schlechten Code, ist Stress vorprogrammiert.

Um die Relevanz von sauberem Code gegenüber schlechtem Code zu verdeutlichen, eignet sich der Vergleich mit einem Restaurant. Will ein Koch seine Gerichte nicht nur lecker, sondern auch möglichst reibungslos zubereiten, muss die Küche, in der er arbeitet, einige Rahmenbedingungen erfüllen: Ungewaschenes Geschirr und verstreute Zutaten schon vor Arbeitsbeginn schrecken jeden Koch ab. Restaurantgästen, die einen Blick in eine solche Küche werfen sollten, dürfte zudem der Appetit vergehen. Unter solchen Bedingungen möchte vermutlich kein Koch auf dieser Welt freiwillig arbeiten.

Ähnlich verhält es sich, wenn es um Software geht. Auch hier trifft man immer wieder auf die unangenehme Wahrheit, dass in eingefahrenen Projekten solche schlechten Umstände herrschen, die sich häufig um unsauberen Code ranken. Leider ist schlechter Code aber auf den ersten Blick nicht leicht erkennbar.

Das weitere Vorgehen im beispielhaften Projekt sieht nun die möglichst reibungslose Einarbeitung des neuen Mitglieds im Entwicklerteam vor. Dazu sollen ein einfaches Feature umgesetzt und die lokale Projektumgebung eingerichtet werden. Neulingen sind zwar die Architektur sowie das benutzte Framework noch nicht bekannt, nach einem ersten Blick in den Quellcode zeigt die neue Kollegin oder der neue Kollege sich jedoch hoch motiviert, mit der Umsetzung des ersten Features zu starten. Die Antwort der Teamkollegen, auf eine um Hilfe bittende Frage, sorgt dann aber für Ernüchterung: "Schau dir dafür die Klassen aus dem Package an. Mehr kann ich dir im Moment auch nicht sagen. Ein Arbeitskollege ist seit gestern nicht mehr in das Projekt involviert. Das Team steht unter enormem Zeitdruck, da bis zum Release in zwei Tagen noch vier Features fertigzustellen sind. Sämtliche Unit-Tests sind rot und die Probleme auf dem Live-System haben derzeit höchste Priorität. Schau bitte, dass du das Feature irgendwie fertigstellst, damit der Release-Termin eingehalten wird."

Rot markierte Unit-Tests, fehlende Teamkollegen, Live-Probleme und ein straffer Release-Plan. Auch ohne den Quellcode der Applikation gesehen zu haben, lässt sich in dieser Situation leicht einschätzen, dass nicht nur das anstehende, sondern vermutlich auch zukünftige Releases in diesem Projekt termingerecht nur schwer fertigzustellen sind.

Leider kommt es in der Praxis in vergleichbaren Projekten allzu häufig vor, dass sich die Softwarequalität kontinuierlich verschlechtert, selbst wenn erfahrene und motivierte Entwickler mitarbeiten. Am Anfang gehen alle Stakeholder mit den besten Absichten ans Werk, müssen sich dann aber vorhersehbaren und unvorhersehbaren Einflussfaktoren beugen – denn Software ist bekanntlich nie ganz fertig. Häufig kommt es vor, dass sich Anforderungen kurz vor vereinbarten Release-Terminen ändern oder ignorierte Unit-Tests zu Produktionsproblemen führen. Mitunter folgen Sicherheitsupdates, die größere Änderungen erfordern. Die beteiligten Entwickler ändern dann hier und da ein paar Codezeilen, damit das Projekt weiterläuft.

Ein weiterer ausschlaggebender Faktor für schlechten Code ist der Mensch. Selbst mit den besten Absichten im Team passieren irgendwann Fehler. Typische Gründe dafür können Zeitdruck, Leistungsdruck, Konflikte im Team, Überlastung oder Fehlentscheidungen sein. Schritt für Schritt geht dann mit der Qualität des Quellcodes auch die des Softwareprojekts bergab.

Beim Implementieren von Anwendungen gilt die grobe Faustregel: Das Verhältnis zwischen Code schreiben und Code lesen liegt unter idealen Bedingungen bei 1:10. Das bedeutet, dass Entwickler mindestens hundert Zeilen Code lesen und verstehen müssen, um zehn Zeilen Code zu schreiben. Enthält ein großes Softwareprojekt viel unsauberen und schlecht dokumentierten Code, der sich nicht intuitiv verstehen lässt, verschlechtert sich das Verhältnis mitunter sogar deutlich.

Anhand des im nachfolgenden Listing gezeigten Beispiels mit bewusst verunstaltetem Code lässt sich die Situation verdeutlichen. Nur mit viel Zeit und Mühe ist erkennbar, welchem Zweck diese Anwendung dient. Obwohl es sich um ein einfaches Programm handelt – es zählt Wort-Duplikate und gibt diese aus, ist der Code schwer lesbar, da er verschiedene Prinzipien verletzt.

public class Main { public static void main(String[] args) {

  String s = "Ohne Clean Code ist Code nicht wartbar.";

 int yc;



  s=s.toLowerCase();

 String strings[]=s.split(" ");



 start();

  for(int i = 0; i<strings.length; i++) {

   yc = 1;

   for(int j = i+1; j<strings.length; j++) {

   if(strings[i].equals(strings[j])) {

​    yc++;

​    strings[j] = "0";

   }

  } if(yc > 1 && strings[i] != "0") System.out.println(strings[i]);

  }

 }



 private static void start() {

  System.out.println("Duplikate: ");

 }

}

Kein Entwickler möchte freiwillig an einer solch unsauberen Codebasis arbeiten. Jede weitere Zeile Code fordert mehr Zeit und einen höheren Aufwand. Da sich im Team häufig niemand um die Verbesserung des Codes kümmert, verschlechtert sich der Zustand zusehends. Die Entwicklung wird mit jeder kleinen Änderung teurer, die Folgen sind steigender Frust und eine sinkende Motivation im Team.

Beim Vergleich mit der Restaurantküche drängt sich das Bild des sich stapelnden, dreckigen Geschirrs auf, das nie abgewaschen und wegräumt wird. Die Teller stapeln sich so lange, bis kein Koch mehr in dieser Küche arbeiten möchte.

Solche "gestapelten Teller" bezeichnet man in der Softwareentwicklung als technische Schulden. Wer dem Problem nicht entgegenwirkt, riskiert einen wirtschaftlichen Totalschaden des Projekts. Irgendwann wächst der Haufen an Aufräumarbeiten so hoch, dass es sich eher lohnt, die Software neu zuschreiben.

Funktioniert in einem Projekt die Technik nicht, bekommen dafür meist Techniker die Schuld in die Schuhe geschoben. Geht es hingegen um die Software, müssen in der Regel Entwicklerinnen und Entwickler als Sündenböcke herhalten. Wachsen die durch unsauberen Code verursachten technischen Schulden in Höhe, beginnt meist die Suche nach einem Schuldigen. Dabei spielt es keine Rolle, ob die Qualitätsmängel durch Fehler oder zu hohen Aufwand verursacht sind – Entwickler haben diese Situation zugelassen und daher mitzuverantworten.

Mit Clean Code können die Betroffenen den technischen Schulden jedoch aktiv entgegenwirken und die Qualität im Softwareprojekt langfristig gewährleisten. Es gilt lediglich, einen Anfang zu machen und sich an den Geschirrstapel heranzuwagen – um im Bild der Küche zu bleiben. Das Ziel ist eine saubere Küche, respektive sauberer Code.

(Bild: Khaled ElAdawy/Shutterstock.com)

Da Softwareprojekte nur selten "auf der grünen Wiese" starten, müssen Entwickler meist Koch, Putzkraft und Küchenmonteur zugleich sein, um den Code sauber zu halten und durchgängig zu optimieren, während kontinuierlich neue Features hinzukommen und Fehler zu bereinigen sind.

Clean Code zeichnet sich dadurch aus, dass er sich in kurzer Zeit und mit wenig Aufwand verstehen lässt. Dabei folgt sauberer Code den folgenden Grundprinzipien:

  • Lesbarkeit
  • Übersichtlichkeit
  • Verständlichkeit
  • Erweiterbarkeit
  • Testbarkeit

Erfüllt Code diese Kriterien, ergeben sich für alle Projektbeteiligten Vorteile:

  1. Die technischen Schulden innerhalb des Projekts verringern sich, die Projektarbeit läuft effizienter und die langfristige Wartbarkeit ist gewährleistet.
  2. Die Software sowie geplante Änderungen daran bleiben stabil.
  3. Das Implementieren neuer Anforderungen oder Fehlerbehebungen lässt sich mit verkürztem Zeitaufwand umsetzen.

Ein nach den Clean-Code-Prinzipien umgesetztes Projekt verschafft Entwicklerinnen und Entwicklern erhebliche Arbeitserleichterung, trägt zu höherer Motivation bei und vermeidet auch an stressigen Tagen aufkommenden Frust, da beim Programmieren auf einer sauberen Codebasis weniger Reibungspunkte entstehen. Darüber hinaus profitiert das Management von geringeren Entwicklungskosten und einem motiviert arbeitenden Team. Nicht zuletzt lässt sich die von Auftraggebern und Nutzern der Software erwartete hohe Qualität der Anwendung leichter gewährleisten.

Wie aber lässt sich Clean Code in einem realen Softwareprojekt erfolgreich umsetzen? Projektleiter, Product Owner sowie Entwicklerinnen und Entwickler müssen dabei einige wichtige Aspekte beachten.

Die größte Herausforderung ist ein unzureichendes Budget. Die für das notwendige Refactoring des Codes vom Entwicklungsteam geforderte Zeit ist meist zu knapp bemessen – häufig als Folge einer zu geringen Aufwandsschätzung zum Projektstart. Wie im analogen Beispiel der Restaurantküche lassen sich solche Planungsdefizite nachträglich nur schwer in den Griff bekommen. Plant ein Koch etwa fünfzehn Minuten Zubereitungszeit für ein Gericht ein, vergisst aber die zusätzlich notwendigen zehn Minuten für die Aufräum- und Säuberungsarbeiten der Küche, droht ihm schon nach drei bis vier Gerichten nicht nur ein Zeitproblem, sondern auch die Qualität gerät in Gefahr. Die zwangsläufige Folge sind Beschwerden der Gäste sowie lange Wartezeiten in der Küche.

Übertragen auf die Softwareentwicklung bedeutet das: Schätzen Entwickler den Aufwand für eine Feature-Implementierung nicht richtig ein und schaffen es nicht, den Code sauber zu hinterlassen, entstehen unweigerlich technische Schulden. In die Aufwandsschätzung sollten daher realistische Einschätzungen für die Dauer des Aufräumens und Säuberns des Codes einfließen, um eine saubere Codebasis zu gewährleisten. Summieren sich die erforderlichen Zeiten erst so weit, dass die Aufräumarbeiten eher einer Kernsanierung gleichen würden, lässt sich das Projekt meist nur noch mit einem neuen Plan retten, nach dem sich das Qualitätsziel Schritt für Schritt erreichen lässt.

Um sich in einem laufenden Projekt eine ungefähre Vorstellung davon zu verschaffen, wie viel Zeit für das Aufräumen einzuplanen ist, bedarf es eines umfassenden Überblicks. Obwohl die dafür geeignetsten Werkzeuge hinlänglich bekannt sind, werden sie allzu oft ignoriert. Eine statische Codeanalyse mit Compiler Warnings, Linter und Code Inspections verschafft Überblick, denn diese Werkzeuge kennen die wichtigsten Prinzipien sowie Coding-Regeln und weisen sogar auf bekannte Fehler hin. Die vorab durchgeführte Analyse betroffener Klassen oder Quelltexte verschafft dem Entwicklungsteam eine Einschätzung dazu, wie viel Aufwand für Implementierungen zusätzlich notwendig ist, um den Code zu säubern.

Dabei gilt es jedoch zu beachten, dass die Werkzeuge kaum Ausnahmen kennen und somit auch False Positives anzeigen, solang nicht die voreingestellten Regeln deaktiviert sind. Die Tools erfordern daher initial etwas Pflegeaufwand, um sich gezielt einsetzen zu lassen.

Die effektivste Methode bleibt es, direkt sauberen Code zu schreiben. Das gelingt allerdings nicht immer, weil die Clean-Coding-Regeln und Design-Prinzipien in Vergessenheit geraten, nachdem ein Feature programmiert und die Arbeit daran beendet ist.

In einer Restaurantküche wäre das undenkbar, denn dort weiß jeder, dass nach dem Kochen der Abwasch folgt. Ein Koch, der ein Drei-Gänge-Menü zubereitet hat, lässt anschließend nicht alle Töpfe, Pfannen und das Geschirr einfach stehen – sondern das gesamte Team macht schon während des Kochens sauber und räumt auf, damit die Arbeit am nächsten Tag reibungslos weitergehen kann.

Den Code nach einer Implementierung aufzuräumen, sollte daher auch in Softwareprojekten selbstverständlich sein. Je früher Code gesäubert ist, desto geringer fällt der künftige Aufwand aus. Besonders wichtig ist das in Bereichen wie Namensgebung, Funktionen, Kommentare, Klassen, Formatierung und Struktur sowie in der Fehlerbehandlung. Das folgende Listing präsentiert den nach Clean-Code-Prinzipien überarbeiteten und gesäuberten Code aus dem ursprünglichen Listing mit "unsauberem" Code.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DuplicateFinder {
  private static List duplicates;
  public static void main(String[] args) {
    String text = "Ohne Clean Code ist Code nicht wartbar.";

    welcomeMessage();
    collectDuplicateWordsInList(text);
    showDuplicates();
  }

  private static void welcomeMessage() {
    System.out.println("Duplikate: ");
  }

  private static void collectDuplicateWordsInList(String text) {
    duplicates = new ArrayList<String>();
    List<String> words = getWordsList(text);

    words.forEach(currentWord -> {
      addDuplicateToList(words, currentWord);
    });
  }

  private static void addDuplicateToList(List words, String currentWord) {
    Boolean alreadyContained = duplicates.contains(currentWord);
    Boolean hasDuplicate = checkCurrentWord(words, currentWord);

    if (alreadyContained == false && hasDuplicate) {
      duplicates.add(currentWord);
    }
  }

  private static Boolean checkCurrentWord(List words, String currentWord) {
    long wordCount = words.stream().filter(word -> currentWord.equals(word)).count();
    return wordCount > 1;
  }

  private static List getWordsList(String text) {
    text = text.toLowerCase();
    String words[] = text.split(" ");
    return Arrays.asList(words);
  }

  private static void showDuplicates() {
    duplicates.forEach(word -> System.out.println(word));
  }
}

Dieser Code ist nun intuitiv verständlich. Auch wenn das Beispiel jetzt deutlich mehr Codezeilen enthält, ist es leichter, die Funktionalität zu verstehen und weitere Änderungen am Code durchzuführen. Der Quelltext dokumentiert sich in dem Fall selbst.

Code besitzt dann eine lesbare Basis, wenn sich die Software anhand der Namen von Klassen, Methoden sowie Variablen selbst dokumentiert und Entwickler dadurch nahezu ohne Kommentare im Quelltext auskommen. Zu noch leichterer Verständlichkeit trägt bei, Funktionen und Klassen möglichst kleinzuhalten und sie nicht ihre Aufgaben sowie Verantwortlichkeiten überschreiten zu lassen.

Eine saubere Formatierung und Struktur des Codes erhält die Übersichtlichkeit des Projekts. Durch den Einsatz der gezeigten Clean-Code-Grundregeln erhält das Team sauberen Code, den auch andere später gerne und frustfrei bearbeiten können.

Nur in seltenen Fällen ist es möglich, von Grund auf Clean Code zu schreiben, denn meist müssen sich Programmierer mit Code aus bestehenden Projekten auseinandersetzen. Eine längst fällige Überarbeitung solcher Codeabschnitte tritt oft aber erst dann zutage, wenn die Software angepasst werden muss und die Entwickler gezwungen sind, den alten Code durchzulesen (vergleiche Listing mit "unsauberem" Code).

Schon das Lesen und Verstehen des Codes bedeutet Aufwand. Es empfiehlt sich daher, den Code bereits bei der Einarbeitung zu refaktorieren. Das Refactoring umfasst Maßnahmen zum Vereinfachen des Codes, ohne das Verhalten der Anwendung zu beeinflussen. Das Umbenennen, Verschieben, Extrahieren, Zusammenfassen oder Löschen von Code sind dabei die am häufigsten genutzten Methoden.

Ein umfangreicheres Refactoring sollte jedoch vor der Durchführung ausgiebig geplant sein, um nicht das eigentliche Ziel der Qualitätsverbesserung zu gefährden. Denn abhängig von der Codebasis im Projekt kann schon eine einzige irrtümlich vertauschte Codezeile oder ein falsch geänderter Variablenname zu plötzlich auftretenden Problemen im Livesystem führen.

Um das Risiko ungewollter Verhaltensänderungen der Anwendung beim Refactoring komplexer Softwareprojekte zu minimieren, sind automatisierte Unit-Tests unerlässlich. Eine hohe Testabdeckung schützt vor ungewollten Nebeneffekten – auch bereits ohne den Einsatz eines komplexen Test-Frameworks oder der Methoden des Test-Driven Development (TDD).

Hilfreich sind bereits simple Unit-Tests, die nach dem Graybox-Prinzip Anforderungen und Bugfixes testen. Sie stellen sicher, dass implementierte Anforderungen und Fehlerbehebungen auch bei Änderungen weiterhin funktionieren. Auf diese Weise gewährleistet das Entwicklungsteam eine hohe Testabdeckung, da die Tests wesentlich mehr Codezeilen ausführen und prüfen müssen. Das Vorgehen verspricht in den meisten Fällen außerdem mehr Erfolg als das Testen einzelner Klassen und Methoden. Um noch umfassendere Testabdeckung und damit höhere Qualität sicherzustellen, empfiehlt es sich, zusätzliche Komponententests im weiteren Projektverlauf zu implementieren. Bei Unit-Tests gilt der allgemeine Grundsatz: Schon ein Prozent Testabdeckung ist besser als keines. Idealziel ist aber eine Testabdeckung von mindestens 80 Prozent im Projekt.

Bücher werden vor einer Veröffentlichung mehrfach gelesen und überarbeitet. Das gleiche Prinzip sollte auch für das Programmieren von Software gelten. Das Ziel von Clean Code ist es, eine saubere und lesbare Codebasis für das Projekt-Team zu hinterlassen. Wer mit Clean Code startet, ist sich besonders am Anfang nicht sicher, ob auch wirklich sauberer Quelltext entsteht. Gegen diese anfängliche Unsicherheit hilft die Code-Review eines anderen Entwicklers. Egal, ob Pair Programming, Pull Requests oder Screensharing, sauberer Code entsteht, wenn sich mehrere Entwickler und Entwicklerinnen zum Quelltext austauschen und sich gegenseitig helfen, den Code verständlicher zu machen.

Clean Code sichert den langfristigen Erfolg einer Anwendung. Zwar fällt die Investition eingangs höher aus, wenn die Softwarequalität im Projektalltag bisher nicht im Fokus stand, jedoch zahlt sich das Umsetzen von Clean Code meistens schnell aus. Technischen Schulden lässt sich damit effektiv entgegenwirken. Sie wachsen dann nicht mehr so stark an, dass sie einen wirtschaftlichen Totalschaden des Projekts verursachen könnten.

Ein weiterer Punkt, der für das Implementieren von Clean Code spricht, ist, dass im Team sowie beim direkten Arbeiten im Quellcode weniger Reibungspunkte entstehen. Das fördert die Motivation und reduziert unnötigen Frust unter Entwicklern wie auch Projektleitern. Mit einer realistischen Aufwandsschätzung, dem gezielten Einsatz von Tools, automatisierten Unit-Tests sowie Code-Reviews und der nötigen Weiterbildung im Bereich Clean Code können Entwicklerinnen und Entwickler die Qualität und Wartbarkeit jeder Software Schritt für Schritt erhöhen – bis ihre Küche endlich sauber und aufgeräumt ist.

Arkadius Roczniewski
(aka Arek) ist seit 15 Jahren Softwareentwickler und begeistert von Clean Code. Auf LerneProgrammieren.de bringt er Anfängern und Fortgeschrittenen das Programmieren bei.

(mdo)