Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization

https://bildung.social/@oerinformatik/

https://oer-informatik.de/einfacher-code

Clean Code, KISS, YAGNI, DRY, Premature Optimization, Doppelter Code, Rule of Three, Profiler, Laufzeitkomplexität, Zielkonflikt

tl/dr; (ca. 34 min Lesezeit): Vier Prinzipien halten Code einfach: KISS (so einfach wie möglich), YAGNI (nur bauen, was heute gebraucht wird), DRY (jedes Wissen genau einmal) und Beware of Premature Optimization (erst messen, dann optimieren). An der Kata der CSB-Rad GmbH entfernst Du doppelte Berechnungen, einen ungenutzten Parameter und einen Zwischenspeicher, der nichts bringt. Dabei lernst Du auch die Grenzen kennen: Nicht jede Ähnlichkeit ist eine Dopplung, und manchmal ist Wiederholung das kleinere Übel [AI-Assisted]. (Zuletzt geändert am 10.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über Clean Code: Überblick / Lesbarer Code / Einfach bleiben / Refactoring / Methoden gliedern / Klassen schneiden / Vererbung richtig einsetzen / Erweitern statt ändern / SOLID / Vom Entwurf zum Code

Dieser Artikel baut auf der Kann-Liste des Artikels Lesbarer Code: Konventionen, sprechende Namen, Kommentare und das Prinzip der geringsten Überraschung auf.

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Als Grundlagen einfachen Codes kann ich nach Durchlesen des Artikels…

  • … KISS erklären und eine unnötig verschachtelte oder trickreiche Lösung vereinfachen.

  • … YAGNI erklären und spekulativ eingebaute Funktionen wie ungenutzte Parameter oder Erweiterungspunkte ohne Bedarf erkennen.

  • … DRY als „jedes Wissen hat genau eine Darstellung“ erklären und doppelten Code zusammenführen (Methode extrahieren, Konstante, Schleife).

  • … Beware of Premature Optimization erklären und beurteilen, wann eine Optimierung gerechtfertigt ist (Anforderung, Messung, Profiler, Laufzeitklasse).

Darüber hinaus kann ich als Vertiefung…

  • … zufällig gleich aussehenden Code von echter Wissensdopplung unterscheiden.

  • … begründen, wann Wiederholung das kleinere Übel ist (Rule of Three, falsche Abstraktion).

  • … Zielkonflikte zwischen KISS, DRY und YAGNI an Beispielen abwägen.

Nach dem letzten Artikel ist der Bestellabschluss der Kata lesbar: Die Variablen haben sprechende Namen, magische Zahlen sind Konstanten, die Kommentare stimmen. Trotzdem steckt in Stand 02 noch Ballast. Der Warenwert wird zweimal summiert, der Kopf der Bestätigungsmail steht zweimal im Code, ein Zwischenspeicher soll die Mehrwertsteuer „schneller“ machen, und die Methode abschliessen erwartet einen Parameter express, den sie nie liest. Nichts davon ist ein Fehler, alle Charakterisierungstests sind grün. Aber jede dieser Stellen macht den Code länger, als er sein müsste, und jede muss bei der nächsten Änderung mitgedacht werden.

Dieser Artikel stellt die vier Prinzipien vor, die gegen solchen Ballast helfen. Sie stehen auf der Landkarte der Prinzipien auf den Ebenen Ausdruck, Methode und Entwurf. Am Beispiel der Code-Kata der CSB-Rad GmbH räumst Du Stand 02 zu Stand 03 auf, wieder ohne das Verhalten zu ändern. Zu jedem Prinzip gehören auch seine Grenzen, denn die vier ziehen nicht immer in dieselbe Richtung. Zum Üben gibt es Übungsaufgaben zu einfachem Code.

Die Mindmap zeigt die vier Prinzipien mit ihrer Kernaussage und den Stellen, an denen sie in der Kata greifen:

Mindmap Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization mit Kernaussage, Herkunft, Beispiel aus der Kata und Grenzen (mit KI erstellt)

Warum einfacher Code?

Fred Brooks unterscheidet 1986 in seinem Aufsatz „No Silver Bullet“ zwei Arten von Komplexität. Die wesentliche Komplexität (essential complexity) steckt im Problem selbst: Versandkosten hängen nun einmal von Versandart, Gewicht und Warenwert ab. Die zufällige Komplexität (accidental complexity) bringen wir beim Programmieren selbst hinein: verschachtelte Bedingungen, doppelte Berechnungen, Vorsorge für Fälle, die nie eintreten. Die wesentliche Komplexität kannst Du nicht wegprogrammieren, die zufällige schon.

Einfacher Code zahlt auf den Wert Wandelbarkeit aus dem Überblicksartikel ein und dort vor allem auf das Teilmerkmal Modifizierbarkeit der Wartbarkeit nach ISO/IEC 25010: Was es nur einmal gibt, musst Du nur einmal ändern. Was es gar nicht gibt, kann auch nicht kaputtgehen.

Die vier Prinzipien dieses Artikels setzen an verschiedenen Stellen an. Die Tabelle ordnet ihnen die Stellen in Stand 02 der Kata zu, die Du in diesem Artikel umbaust:

Die vier Prinzipien und ihre Ansatzpunkte in der Kata
Prinzip Kernfrage Stelle in Stand 02
KISS Geht es einfacher, ohne dass etwas fehlt? (an einem eigenen Beispiel, die Kata ist hier schon schlicht)
YAGNI Wird das heute gebraucht? Parameter express, der nie gelesen wird
DRY Steht dieses Wissen schon woanders? zweite Warenwert-Schleife für den Rabatt, doppelt gebauter Mailkopf
Beware of Premature Optimization Ist gemessen, dass es zu langsam ist? Zwischenspeicher mehrwertsteuerCache

KISS: Keep it simple, stupid

Problem: verschachtelt oder trickreich

Ein eigenes kleines Beispiel aus dem Umfeld der Kata: Eine Methode soll entscheiden, ob für eine Bestellung keine Versandkosten anfallen. Bei Abholung ist das immer so, beim Paketversand ab 100 € Warenwert nach Rabatt, bei der Spedition nie. Die erste Fassung verschachtelt drei Bedingungen ineinander und verneint die äußerste. Die zweite ist kürzer, aber trickreich: Sie prüft die Freigrenze über eine ganzzahlige Division und nutzt in Java zusätzlich die nicht kurzschließenden Operatoren & und |.

public class VersandregelVorher {

    private static final int VERSANDKOSTENFREI_AB_CENT = 10000;

    public static boolean istVersandkostenfrei(String versandart, int warenwertNachRabattCent) {
        boolean frei = false;
        if (!versandart.equals("SPEDITION")) {
            if (versandart.equals("ABHOLUNG")) {
                frei = true;
            } else {
                if (warenwertNachRabattCent >= VERSANDKOSTENFREI_AB_CENT) {
                    frei = true;
                } else {
                    frei = false;
                }
            }
        }
        return frei;
    }

    // ebenfalls "kurz", aber trickreich
    public static boolean istVersandkostenfreiKurz(String versandart, int warenwertNachRabattCent) {
        return !versandart.equals("SPEDITION") & (versandart.equals("ABHOLUNG") | warenwertNachRabattCent / VERSANDKOSTENFREI_AB_CENT > 0);
    }
}

Beide Fassungen liefern für Paket, Spedition und Abholung dieselben Ergebnisse. Verstehen musst Du sie trotzdem erst. In der verschachtelten Fassung suchst Du, welcher Zweig für welche Versandart gilt; dass jede unbekannte Versandart wie ein Paket behandelt wird, steht nirgends. In der kurzen Fassung musst Du nachrechnen, warum die ganzzahlige Division durch 10000 genau dann mehr als null ergibt, wenn der Warenwert mindestens 10000 Cent beträgt.

Prinzip

KISS steht für Keep it simple, stupid (freundlicher auch Keep it short and simple). Die Formel wird dem Flugzeugingenieur Kelly Johnson zugeschrieben, der um 1960 die Entwicklungsabteilung „Skunk Works“ von Lockheed leitete: Ein Flugzeug sollte so gebaut sein, dass ein durchschnittlich ausgebildeter Mechaniker es unter Feldbedingungen mit einfachem Werkzeug reparieren kann. Übertragen auf Software heißt das: Wähle von zwei Lösungen, die beide die Anforderungen erfüllen, die, die leichter zu verstehen ist.

Einfach heißt dabei nicht kurz. Rich Hickey hat 2011 in seinem Vortrag „Simple Made Easy“ zwischen einfach (simple: wenig ineinander verflochten) und leicht (easy: naheliegend, schnell hingeschrieben) unterschieden. Der trickreiche Einzeiler oben ist leicht hingeschrieben, aber nicht einfach, weil er mehrere Gedanken in einen Ausdruck verknotet.

Ein messbares Indiz für zu viel Verschachtelung ist die zyklomatische Komplexität nach McCabe: die Zahl der Entscheidungen plus eins. Die verschachtelte Fassung hat drei Verzweigungen und damit die McCabe-Zahl 4.

Umbau: flach und direkt

Die vereinfachte Fassung beantwortet die Fälle nacheinander und kehrt sofort zurück (Guard Clauses, Wächterbedingungen). Jede Zeile entspricht einem Satz der Anforderung, und die Freigrenze steht als Vergleich da, wie sie gemeint ist:

public class VersandregelNachher {

    private static final int VERSANDKOSTENFREI_AB_CENT = 10000;

    public static boolean istVersandkostenfrei(String versandart, int warenwertNachRabattCent) {
        if (versandart.equals("ABHOLUNG")) {
            return true;
        }
        if (versandart.equals("PAKET")) {
            return warenwertNachRabattCent >= VERSANDKOSTENFREI_AB_CENT;
        }
        return false;
    }
}

Die McCabe-Zahl sinkt auf 3, vor allem aber kannst Du die Methode von oben nach unten vorlesen. Nebenbei wird die Annahme sichtbar, die vorher versteckt war: Eine unbekannte Versandart ist hier nicht versandkostenfrei (die alte Fassung hätte sie wie ein Paket behandelt). Für die drei gültigen Versandarten sind alle drei Fassungen gleichwertig; das hat ein kleiner Test mit je vier Warenwerten geprüft. Typische KISS-Umbauten:

  • verschachtelte if-Blöcke durch frühe Rückgaben ersetzen,

  • doppelte Verneinungen (!versandart.equals(...) mit else) auflösen,

  • if (bedingung) { return true; } else { return false; } durch return bedingung; ersetzen,

  • Rechentricks (Bitoperationen, Divisionen als Vergleich) durch den Ausdruck ersetzen, den die Anforderung meint,

  • Bordmittel der Sprache nutzen, statt sie nachzubauen (Math.min, max(), sum(), Standardbibliothek).

Grenzen und Zielkonflikte

  • Einfach für wen? Was einfach ist, hängt vom Team ab. Ein Stream-Ausdruck in Java oder eine List Comprehension in Python ist für Geübte einfacher als eine Schleife, für Einsteiger*innen nicht. Maßstab ist das Team, das den Code pflegt, und dessen Konventionen.

  • Einfach ist nicht primitiv. Wer alles in eine lange Methode schreibt, „damit es einfach bleibt“, verschiebt die Komplexität nur. Die lange Methode abschliessen der Kata bleibt in diesem Artikel bewusst lang; sie wird erst im Artikel Methoden gliedern aufgeteilt.

  • KISS gegen Entwurfsmuster: Ein Entwurfsmuster wie die Strategie ist mehr Code als ein if. Es lohnt sich erst, wenn die Varianten wirklich wachsen. Diesen Zielkonflikt löst die Kata später im Artikel Erweitern statt ändern, wenn neue Versandarten hinzukommen.

YAGNI: You Ain’t Gonna Need It

Problem: Code für später

Die Methode abschliessen erwartet in Stand 02 einen Parameter express. Vermutlich wollte jemand den Expressversand schon einmal vorbereiten. Gelesen wird der Parameter nirgends, und jeder Aufruf muss trotzdem einen Wert übergeben:

// Auszug aus src/kata/java/stand-02, Klasse Shop
public int[] abschliessen(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail, boolean express) {
    // ... express wird nirgends gelesen
}

// Aufruf, z. B. im Test:
shop.abschliessen(List.of(LICHTSET), "PAKET", null, MAIL, false);

Solcher Code ist nicht harmlos. Wer die Methode aufruft, fragt sich, was true bewirken würde. Wer die Methode ändert, traut sich nicht, den Parameter zu entfernen, weil ja vielleicht jemand darauf wartet. Und wenn der Expressversand tatsächlich kommt (in der Kata ist das ab Stand 08 so), sieht er ganz anders aus als vorbereitet: Express ist dann eine eigene Versandart und kein Schalter.

Prinzip

YAGNI steht für You Ain’t Gonna Need It („Du wirst es nicht brauchen“, auch You Aren’t Gonna Need It). Das Prinzip stammt aus dem Extreme Programming der späten 1990er Jahre um Kent Beck und Ron Jeffries. Jeffries fasst es so zusammen: Setze Dinge um, wenn Du sie tatsächlich brauchst, nie, wenn Du nur vorhersiehst, dass Du sie brauchen wirst. Im Artikel über Extreme Programming gehört YAGNI zum Wert Einfachheit und zur Praxis des einfachen Designs.

Martin Fowler hat 2015 die Kosten einer vorweggenommenen Funktion aufgeschlüsselt:

  • Kosten des Bauens: Zeit, die für die heute gebrauchten Funktionen fehlt.

  • Kosten der Verzögerung: Was heute gebraucht wird, kommt später.

  • Kosten des Mitschleppens: Jeder zusätzliche Code muss gelesen, getestet und bei Änderungen berücksichtigt werden.

  • Kosten der Reparatur: Kommt die Anforderung wirklich, ist sie meist anders als gedacht, und die Vorbereitung muss umgebaut werden.

Typische YAGNI-Verstöße sind ungenutzte Parameter, Konfigurationsschalter, die niemand setzt, abstrakte Basisklassen oder Interfaces mit nur einer Umsetzung „für spätere Erweiterungen“ und auskommentierter Code „für alle Fälle“. Im Überblicksartikel heißt der passende Code Smell Spekulative Allgemeinheit (Speculative Generality).

Umbau: Parameter entfernen

In Stand 03 ist der Parameter weg. Das ist der einzige Umbau dieses Artikels, der die öffentliche Schnittstelle ändert: Alle Aufrufer, hier die Charakterisierungstests, verlieren das Argument false. Ihre erwarteten Ergebnisse bleiben gleich.

// Auszug aus src/kata/java/stand-03, Klasse Shop
public int[] abschliessen(List<Position> positionen, String versandart, String gutscheincode, String kundenEmail) {
    // ...
}

// Aufruf, z. B. im Test:
shop.abschliessen(List.of(LICHTSET), "PAKET", null, MAIL);

Bevor Du einen Parameter einer öffentlichen Methode entfernst, suchst Du alle Aufrufer (in der IDE „Find Usages“ bzw. „Aufrufhierarchie“). Liegt die Methode in einer Bibliothek, die andere Teams nutzen, wird sie zuerst als veraltet markiert (@Deprecated in Java, warnings.warn(..., DeprecationWarning) in Python) und erst in einer späteren Version entfernt.

Grenzen und Zielkonflikte

  • YAGNI gilt für Funktionen, nicht für Pflege. Tests, sprechende Namen und Refactoring sind keine Vorratsfunktionen; sie halten den Code änderbar, damit Du die Anforderung von morgen morgen bauen kannst. Fowler betont, dass YAGNI nur mit dieser Disziplin funktioniert.

  • YAGNI gegen das Open Closed Principle: Ein Erweiterungspunkt (Interface, Strategie) für eine Variante, die es noch nicht gibt, ist YAGNI-verdächtig. Kommt die zweite oder dritte Variante tatsächlich, ist er richtig. Die Kata baut den Erweiterungspunkt für Versandarten deshalb erst, als nach der Kurierzustellung auch der Expressversand hinzukommt (Erweitern statt ändern).

  • Teure Entscheidungen vorab: Manches lässt sich später nur schwer ändern: öffentliche Schnittstellen, Datenformate und Datenbankschemata, Sicherheits- und Datenschutzanforderungen. Hier ist Nachdenken vorab kein Verstoß gegen YAGNI, sondern ein bewusster Entwurf (Vom Entwurf zum Code).

DRY: Don’t Repeat Yourself

Problem: dasselbe Wissen an zwei Stellen

Stand 02 summiert den Warenwert zweimal: einmal für die Bestellung, ein zweites Mal in der Variable warenwertFuerRabatt. Und der Kopf der Bestätigungsmail (Empfänger, Bestellnummer, Gesamtbetrag) steht wortgleich in beiden Zweigen der Fallunterscheidung:

// Auszug aus src/kata/java/stand-02, Klasse Shop, Methode abschliessen
int warenwert = 0;
for (Position position : positionen) {
    if (position.menge() <= 0) {
        throw new IllegalArgumentException("Menge muss positiv sein: " + position.artikelnr());
    }
    warenwert = warenwert + position.nettopreisCent() * position.menge();
}
int rabatt = 0;
if (gutscheincode != null && gutscheincode.equals(GUTSCHEINCODE_KURIER)) {
    int warenwertFuerRabatt = 0;
    for (Position position : positionen) {
        warenwertFuerRabatt = warenwertFuerRabatt + position.nettopreisCent() * position.menge();
    }
    // kaufmännisch gerundet
    rabatt = (warenwertFuerRabatt * GUTSCHEIN_RABATT_PROZENT + 50) / 100;
}
// ...
String mailtext;
if (versandart.equals("ABHOLUNG")) {
    mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
            + String.format("%02d", gesamtbetrag % 100) + " EUR";
    mailtext = mailtext + "\nAbholung in der Werkstatt Bielefeld-Sennestadt";
} else {
    mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
            + String.format("%02d", gesamtbetrag % 100) + " EUR";
    mailtext = mailtext + "\nVersand: " + versandbezeichnung;
}

Das Problem ist nicht die Zahl der Zeilen. Das Problem ist, dass die Regel „Warenwert = Summe aus Nettopreis mal Menge“ und das Format des Mailkopfs je zweimal festgelegt sind. Ändert sich die Regel (etwa weil Pfandartikel nicht rabattiert werden) oder das Format (etwa ein Eurozeichen statt „EUR“), musst Du beide Stellen finden. Übersiehst Du eine, rechnet der Shop mit zwei verschiedenen Warenwerten oder verschickt zwei Mailformate, und kein Compiler warnt Dich.

Prinzip

DRY steht für Don’t Repeat Yourself. Andrew Hunt und David Thomas formulieren es 1999 in „The Pragmatic Programmer“ so: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. Jedes Stück Wissen hat in einem System genau eine eindeutige, maßgebliche Darstellung.

Der entscheidende Begriff ist Wissen, nicht Text. DRY verbietet nicht, dass zwei Zeilen gleich aussehen; es verlangt, dass eine fachliche Regel, eine Formel, ein Format oder eine Grenze nur an einer Stelle festgelegt ist. Das gilt über den Code hinaus: Auch Datenbankschema und Datenklassen, Dokumentation und Code oder Konfiguration in mehreren Dateien können dasselbe Wissen doppelt halten. Das Gegenteil nennt man scherzhaft WET (Write Everything Twice oder We Enjoy Typing).

Umbau: zusammenführen

In Stand 03 nutzt der Rabatt den schon berechneten Warenwert, und der Mailkopf wird vor der Fallunterscheidung einmal gebaut. In den Zweigen steht nur noch, was sich wirklich unterscheidet:

// Auszug aus src/kata/java/stand-03, Klasse Shop, Methode abschliessen
int rabatt = 0;
if (gutscheincode != null && gutscheincode.equals(GUTSCHEINCODE_KURIER)) {
    // kaufmännisch gerundet
    rabatt = (warenwert * GUTSCHEIN_RABATT_PROZENT + 50) / 100;
}
// ...
String mailtext = "An: " + kundenEmail + "\nBestellung CSB-" + bestellnummer + "\nGesamtbetrag: " + (gesamtbetrag / 100) + ","
        + String.format("%02d", gesamtbetrag % 100) + " EUR";
if (versandart.equals("ABHOLUNG")) {
    mailtext = mailtext + "\nAbholung in der Werkstatt Bielefeld-Sennestadt";
} else {
    mailtext = mailtext + "\nVersand: " + versandbezeichnung;
}

Für Dopplungen gibt es eine Handvoll Standardumbauten. Die Namen in Klammern sind die englischen Bezeichnungen aus Martin Fowlers Refactoring-Katalog, mehr dazu im Artikel Refactoring:

Dopplungen und passende Umbauten
Dopplung Umbau Beispiel
Wert steht mehrfach im Code Konstante einführen (Replace Magic Literal) PAKETPAUSCHALE_CENT statt mehrfach 690 (Stand 02)
Ergebnis wird erneut berechnet vorhandenes Ergebnis wiederverwenden Rabatt aus warenwert statt warenwertFuerRabatt
gleiche Anweisungen in beiden Zweigen gemeinsame Anweisungen herausziehen (Slide Statements) Mailkopf vor dem if
gleicher Ablauf mit anderen Werten Methode extrahieren (Extract Method, in der 2. Auflage Extract Function), ggf. mit Parametern alsEuroText(betragCent)
gleiche Zeile für mehrere Werte Schleife über die Werte Belegzeilen für Warenwert, Rabatt, Versand

Die letzten beiden Zeilen zeigt ein eigenes kleines Beispiel: Ein Beleg listet Warenwert, Rabatt und Versandkosten auf, und die Formatierung von Cent als Euro steht dreimal da.

public class BelegVorher {

    public static String erstelle(int warenwertCent, int rabattCent, int versandkostenCent) {
        String beleg = "Warenwert: " + (warenwertCent / 100) + "," + String.format("%02d", warenwertCent % 100) + " EUR\n";
        beleg = beleg + "Rabatt: " + (rabattCent / 100) + "," + String.format("%02d", rabattCent % 100) + " EUR\n";
        beleg = beleg + "Versand: " + (versandkostenCent / 100) + "," + String.format("%02d", versandkostenCent % 100) + " EUR\n";
        return beleg;
    }
}

Nach dem Umbau steckt das Wissen „so sieht ein Eurobetrag aus“ in einer Methode, und die Zeilen entstehen in einer Schleife. Soll später „EUR“ einem Eurozeichen weichen, änderst Du genau eine Zeile:

public class BelegNachher {

    public static String erstelle(int warenwertCent, int rabattCent, int versandkostenCent) {
        String[] bezeichnungen = {"Warenwert", "Rabatt", "Versand"};
        int[] betraegeCent = {warenwertCent, rabattCent, versandkostenCent};
        StringBuilder beleg = new StringBuilder();
        for (int i = 0; i < bezeichnungen.length; i++) {
            beleg.append(bezeichnungen[i]).append(": ").append(alsEuroText(betraegeCent[i])).append("\n");
        }
        return beleg.toString();
    }

    /** Formatiert einen Betrag in Cent als Eurotext, z. B. 9821 als "98,21 EUR". */
    static String alsEuroText(int betragCent) {
        return (betragCent / 100) + "," + String.format("%02d", betragCent % 100) + " EUR";
    }
}

Auch die Bestätigungsmail der Kata formatiert ihren Gesamtbetrag so; wenn Methoden extrahiert werden (Refactoring), bekommt sie dieselbe Hilfsmethode alsEuroText.

Gleich aussehen ist nicht gleiches Wissen

Nicht jede Wiederholung ist eine Dopplung im Sinne von DRY. Zwei Codestellen können zufällig gleich aussehen und trotzdem verschiedenes Wissen darstellen. Ein Beispiel: Ein Onlineshop räumt Verbraucher*innen die gesetzliche Widerrufsfrist von 14 Tagen ein (§ 355 BGB). Angenommen, er gewährt Firmenkund*innen außerdem ein Zahlungsziel von 14 Tagen. Wer beide Werte zu einer Konstante VIERZEHN_TAGE zusammenlegt, koppelt zwei Regeln, die nichts miteinander zu tun haben: Verlängert die Firma ihr Zahlungsziel auf 30 Tage, ändert sich unbemerkt auch die Widerrufsfrist.

Die Prüffrage lautet deshalb: Ändern sich die beiden Stellen immer aus demselben Grund? Wenn ja, ist es dasselbe Wissen, und es gehört an eine Stelle. Wenn nein, ist die Ähnlichkeit zufällig, und die Stellen bleiben getrennt.

In der Kata gibt es beide Fälle. Rabatt und Mehrwertsteuer werden mit derselben Formel kaufmännisch gerundet: (betrag * prozent + 50) / 100. Das ist dasselbe Wissen („so runden wir Prozentbeträge“), und es wird im Artikel Refactoring zu einer Methode prozentKaufmaennischGerundet. Die Versandpauschale von 6,90 € und die Freigrenze von 100 € dagegen sind zwei verschiedene Regeln, auch wenn beide in der Paketberechnung stehen.

Grenzen: Rule of Three und die falsche Abstraktion

  • Rule of Three: Don Roberts’ Faustregel, die Martin Fowler 1999 in „Refactoring“ bekannt gemacht hat, lautet sinngemäß: Beim ersten Mal schreibst Du es einfach. Beim zweiten Mal zuckst Du bei der Wiederholung zusammen, wiederholst aber trotzdem. Beim dritten Mal baust Du um. Erst beim dritten Vorkommen siehst Du meist zuverlässig, was wirklich gemeinsam ist und was nur zufällig gleich.

  • Die falsche Abstraktion: Sandi Metz schreibt 2016: Duplication is far cheaper than the wrong abstraction, Wiederholung ist viel billiger als die falsche Abstraktion. Wird zu früh zusammengelegt, wächst die gemeinsame Methode mit jedem neuen Sonderfall um einen Parameter und ein if, bis niemand mehr versteht, welcher Aufrufer welchen Zweig braucht. Dann ist es besser, die Abstraktion wieder aufzulösen und den Code an die Aufrufer zurückzukopieren.

  • DRY gegen Kopplung: Wer zwei Module über gemeinsamen Code verbindet, koppelt sie. Arbeiten zwei Teams an getrennten Anwendungen, kann eine geteilte Hilfsbibliothek mehr Abstimmung kosten, als die Dopplung schadet. Gemeinsam genutzt werden sollte vor allem echtes fachliches Wissen, das sich für beide immer gleich ändert (siehe Kopplung und Kohäsion).

  • Tests dürfen sich wiederholen: In Testfällen ist Lesbarkeit wichtiger als Kürze. Dass jeder Charakterisierungstest seine Eingabe und sein erwartetes Ergebnis vollständig nennt, ist gewollt (man spricht auch von DAMP, Descriptive And Meaningful Phrases).

Beware of Premature Optimization

Problem: Optimierung nach Gefühl

Stand 02 merkt sich jede berechnete Mehrwertsteuer in einer Map bzw. einem Dictionary. Die Idee dahinter: Kommt derselbe Nettobetrag noch einmal vor, muss nicht neu gerechnet werden.

// Auszug aus src/kata/java/stand-02, Klasse Shop
private final Map<Integer, Integer> mehrwertsteuerCache = new HashMap<>();

// ... in abschliessen:
// Preise sind Nettopreise; die Mehrwertsteuer kommt auf Waren und Versand
int nettobetrag = warenwert - rabatt + versandkosten;
int mehrwertsteuer;
if (mehrwertsteuerCache.containsKey(nettobetrag)) {
    mehrwertsteuer = mehrwertsteuerCache.get(nettobetrag);
} else {
    mehrwertsteuer = (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;
    mehrwertsteuerCache.put(nettobetrag, mehrwertsteuer);
}

Klingt sparsam, ist es aber nicht. Die gesparte Rechnung besteht aus einer Multiplikation, einer Addition und einer Division. Für den Zwischenspeicher muss der Rechner dagegen einen Hashwert bilden, in der Tabelle nachsehen und bei einem neuen Betrag einen Eintrag anlegen (in Java zusätzlich die int-Werte in Integer-Objekte verpacken). Gleiche Nettobeträge sind in einem Shop außerdem selten, also wird fast immer gerechnet und gespeichert. Der Speicher wächst mit jeder Bestellung und wird nie geleert: In einem Shop, der monatelang läuft, ist das ein schleichendes Speicherleck. Und die Klasse Shop hat einen Zustand mehr, über den Du beim Testen und bei gleichzeitigen Zugriffen nachdenken musst.

Prinzip

Beware of Premature Optimization („Hüte Dich vor vorzeitiger Optimierung“) geht auf einen Satz von Donald Knuth aus seinem Aufsatz „Structured Programming with go to Statements“ (ACM Computing Surveys, 1974) zurück: We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. Wir sollten kleine Effizienzgewinne in etwa 97 % der Fälle vergessen, vorzeitige Optimierung ist die Wurzel allen Übels. Die kritischen 3 % sollten wir uns aber nicht entgehen lassen.

Der zweite Satz wird gern vergessen. Knuth sagt nicht, dass Laufzeit egal ist, sondern dass Du erst wissen musst, wo sie verloren geht. Programmierende schätzen das erfahrungsgemäß schlecht ein. Deshalb gilt: messen statt raten. Das Aktivitätsdiagramm zeigt den Ablauf:

Aktivitätsdiagramm Messen statt raten: Leistungsziel festlegen, messen, bei erreichtem Ziel fertig, sonst Engpass mit dem Profiler suchen, Laufzeitklasse prüfen, gezielt an einer Stelle optimieren, Tests ausführen und erneut messen (mit KI erstellt)
  1. Ziel festlegen: Eine Optimierung braucht eine Anforderung, etwa „Der Bestellabschluss antwortet in 95 % der Fälle in unter 300 ms“. Ohne Ziel weißt Du nicht, wann Du fertig bist.

  2. Messen: Unter realistischen Bedingungen messen, wie lange es tatsächlich dauert. Ist das Ziel erreicht, wird nichts optimiert.

  3. Engpass finden: Ein Profiler misst, wie viel Zeit das Programm in welcher Methode verbringt. Für Java gibt es den JDK Flight Recorder mit JDK Mission Control oder VisualVM, für Python das Modul cProfile der Standardbibliothek.

  4. Laufzeitklasse prüfen: Häufig liegt der Engpass nicht in einzelnen Rechenschritten, sondern im Algorithmus. Wie stark die Laufzeit mit der Datenmenge wächst, beschreibt die O-Notation (siehe Laufzeitverhalten und Komplexität). Eine Suche in einer Liste innerhalb einer Schleife (O(n^2)) durch eine Suche in einer Map (O(n) insgesamt) zu ersetzen, bringt bei großen Datenmengen mehr als jede Mikrooptimierung.

  5. Gezielt optimieren, testen, erneut messen: Nur die gefundene Stelle ändern, die Tests müssen grün bleiben, und die zweite Messung zeigt, ob es etwas gebracht hat.

Messen am Beispiel: Bringt der Zwischenspeicher etwas?

Ob der Zwischenspeicher hilft, lässt sich mit einer kleinen Messung klären. Das Programm berechnet die Mehrwertsteuer für viele zufällige Nettobeträge einmal mit und einmal ohne Zwischenspeicher. In Java wiederholt es die Messung mehrmals, weil der JIT-Compiler den Code erst während der Laufzeit optimiert; für belastbare Java-Messungen nimmt man das Werkzeug JMH (Java Microbenchmark Harness). In Python übernimmt das Modul timeit die Wiederholungen.

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/** Grobe Messung: Ist der Zwischenspeicher für die Mehrwertsteuer schneller als die direkte Rechnung? */
public class MessungMehrwertsteuer {

    private static final int MEHRWERTSTEUER_PROZENT = 19;
    private static final Map<Integer, Integer> cache = new HashMap<>();

    static int mitCache(int nettobetrag) {
        Integer gespeichert = cache.get(nettobetrag);
        if (gespeichert != null) {
            return gespeichert;
        }
        int mehrwertsteuer = (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;
        cache.put(nettobetrag, mehrwertsteuer);
        return mehrwertsteuer;
    }

    static int ohneCache(int nettobetrag) {
        return (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;
    }

    public static void main(String[] args) {
        int[] betraege = new Random(42).ints(1_000_000, 1000, 300_000).toArray();
        for (int runde = 0; runde < 5; runde++) { // erste Runden wärmen den JIT-Compiler auf
            long summe = 0;
            long start = System.nanoTime();
            for (int betrag : betraege) {
                summe += mitCache(betrag);
            }
            long mitte = System.nanoTime();
            for (int betrag : betraege) {
                summe += ohneCache(betrag);
            }
            long ende = System.nanoTime();
            System.out.printf("Runde %d: mit Cache %.1f ns, ohne Cache %.1f ns je Aufruf (Prüfsumme %d)%n",
                    runde, (mitte - start) / 1e6, (ende - mitte) / 1e6, summe);
        }
        System.out.println("Einträge im Cache: " + cache.size());
    }
}

Auf einem Testrechner (Java 21, Python 3.13) brauchte der Aufruf mit Zwischenspeicher in Java nach dem Aufwärmen rund 30 bis 40 ns, ohne unter 1 ns. In Python lag er bei rund 110 ns mit und 60 ns ohne Zwischenspeicher. Die genauen Zahlen hängen vom Rechner ab, das Verhältnis nicht: Der Zwischenspeicher macht die Rechnung langsamer und hält am Ende Hunderttausende Einträge im Speicher.

Und selbst wenn er schneller wäre: Ein Profil des ganzen Bestellabschlusses zeigt, dass sich die Frage gar nicht stellt. 20 000 Abschlüsse mit Stand 02 brauchen in Python zusammen rund 0,05 s, also etwa 2,5 µs je Bestellung, Mehrwertsteuer und alles andere eingerechnet. Speichern in einer Datenbank oder Versenden einer Mail dauert in einem echten Shop ein Vielfaches davon.

# Java: Aufzeichnung mit dem JDK Flight Recorder (60 s), danach in JDK Mission Control öffnen
java -XX:StartFlightRecording=duration=60s,filename=shop.jfr -jar shop.jar
jfr print --events jdk.ExecutionSample shop.jfr | head

Umbau: direkt rechnen

In Stand 03 ist der Zwischenspeicher samt Feld und Imports entfernt; die Mehrwertsteuer wird direkt berechnet:

// Auszug aus src/kata/java/stand-03, Klasse Shop, Methode abschliessen
// Preise sind Nettopreise; die Mehrwertsteuer kommt auf Waren und Versand
int nettobetrag = warenwert - rabatt + versandkosten;
int mehrwertsteuer = (nettobetrag * MEHRWERTSTEUER_PROZENT + 50) / 100;

Grenzen: Wann Optimierung gerechtfertigt ist

  • Wenn eine Messung es zeigt: Ein Leistungsziel wird verfehlt, und der Profiler zeigt die Stelle. Dann ist Optimieren keine Vorwegnahme mehr, sondern eine Anforderung.

  • Wenn die Laufzeitklasse nicht passt: Eine passende Datenstruktur (Map statt Liste für Suchen, ein Datenbankindex für häufige Abfragen) ist kein Verstoß gegen das Prinzip, sondern solides Handwerk. Sie macht den Code meist nicht komplizierter.

  • Wenn ein Zwischenspeicher wirklich teure Arbeit spart: Netzwerkaufrufe, Datenbankabfragen oder aufwendige Berechnungen dürfen zwischengespeichert werden, nachdem gemessen ist, dass es sich lohnt. Dann aber mit Obergrenze und einer Regel, wann Einträge veralten (in Python etwa functools.lru_cache(maxsize=...)).

  • Wenn die Umgebung es verlangt: Auf Mikrocontrollern mit wenigen Kilobyte Speicher oder in Echtzeitsystemen gehören Laufzeit- und Speichergrenzen von Anfang an zu den Anforderungen.

Umbau der Kata: von Stand 02 zu Stand 03

Zusammengenommen entfernt der Umbau vier Stellen, ohne am Verhalten etwas zu ändern: die zweite Warenwert-Schleife, den doppelten Mailkopf, den Zwischenspeicher und den Parameter express. Die Charakterisierungstests von Stand 03 prüfen dieselben 16 Fälle mit denselben Ergebnissen wie in Stand 02; geändert hat sich nur der Aufruf ohne das letzte Argument.

Die Klassendiagramme zeigen den Unterschied in der Struktur der Klasse Shop. Vorher hat sie ein zusätzliches Feld für den Zwischenspeicher, und abschliessen erwartet fünf Parameter:

UML-Klassendiagramm der Kata in Stand 02: Klasse Shop mit Feldern naechsteBestellnummer, bestellungen, postausgang und mehrwertsteuerCache sowie Operation abschliessen mit fünf Parametern einschließlich express, Datentyp Position (mit KI erstellt)

Nachher fehlen Feld und Parameter; die Klasse ist um genau den Ballast kleiner, den niemand brauchte:

UML-Klassendiagramm der Kata in Stand 03: Klasse Shop ohne mehrwertsteuerCache, Operation abschliessen mit vier Parametern, Datentyp Position unverändert (mit KI erstellt)

Was noch fehlt: Die Methode abschliessen ist weiterhin rund 70 Zeilen lang und erledigt Berechnung, Nummernvergabe, Mail und Speichern in einem Stück. Bevor sie aufgeteilt wird, kommen im Artikel Refactoring die Charakterisierungstests in den Testordner und sichern jeden weiteren Schritt ab.

Zielkonflikte abwägen

Die vier Prinzipien sind Heuristiken. Meist ziehen sie in dieselbe Richtung, manchmal nicht. Die Tabelle fasst typische Konflikte und eine Abwägung zusammen:

Typische Zielkonflikte zwischen den Prinzipien
Konflikt Beispiel Abwägung
DRY gegen KISS Zwei ähnliche Berechnungen werden zu einer Methode mit drei Schaltern zusammengelegt. Gemeinsam nur, was dasselbe Wissen ist. Braucht die gemeinsame Methode Schalter für jeden Aufrufer, sind zwei einfache Methoden besser.
DRY gegen Kopplung Zwei Teams teilen eine Hilfsbibliothek für Formatierungen. Teilen lohnt sich für Wissen, das sich für beide gleich ändert; sonst Kopie mit klarer Zuständigkeit.
YAGNI gegen Erweiterbarkeit Ein Interface für Versandarten, obwohl es heute nur das Paket gibt Erweiterungspunkt erst bei der zweiten oder dritten Variante; bis dahin den Code so sauber halten, dass er sich leicht einbauen lässt.
KISS gegen Entwurfsmuster Strategie-Muster statt if-Kette Muster, wenn Varianten wachsen und unabhängig getestet werden sollen; bei zwei stabilen Fällen genügt das if.
Premature Optimization gegen Laufzeitklasse Liste statt Map für viele Suchzugriffe, „weil es einfacher ist“ Die passende Datenstruktur ist keine vorzeitige Optimierung; Mikrotricks ohne Messung schon.

Für die Abwägung hilft immer dieselbe Frage aus dem Überblicksartikel: Welche Lösung macht den Code heute verständlich und morgen am leichtesten änderbar? Wer sich gegen ein Prinzip entscheidet, schreibt den Grund in einen Kommentar oder in die Beschreibung des Merge Requests.

In Java und Python

  • Ungenutzte Parameter finden: Der Java-Compiler warnt nicht vor ungenutzten Parametern, IDEs wie IntelliJ IDEA oder Eclipse markieren sie aber, und die statische Analyse PMD kennt die Regel UnusedFormalParameter. In Python meldet der Linter Ruff ungenutzte Argumente mit den Regeln ARG001 (Funktionen) und ARG002 (Methoden), wenn diese Regelgruppe eingeschaltet ist.

  • Zwischenspeicher, wenn sie sich lohnen: Python bringt mit functools.lru_cache und functools.cache fertige Zwischenspeicher für Funktionsergebnisse mit; lru_cache(maxsize=...) begrenzt die Größe. Java hat dafür kein Sprachmittel; einfache Fälle löst Map.computeIfAbsent, für begrenzte Zwischenspeicher gibt es Bibliotheken wie Caffeine. In beiden Sprachen gilt: erst messen, dann einbauen.

  • Profiler: Python liefert cProfile und timeit in der Standardbibliothek. Java bringt mit dem JDK Flight Recorder (-XX:StartFlightRecording) einen eingebauten Profiler mit; Mikromessungen gehören wegen des JIT-Compilers in JMH.

  • Signatur ändern: In Java findet der Compiler jeden Aufruf mit falscher Parameterzahl. In Python fällt ein vergessener Aufrufer erst zur Laufzeit auf (TypeError), außer ein Typprüfer wie mypy oder die Tests decken ihn vorher auf. Ein Grund mehr, vor dem Umbau Tests zu haben.

Fazit

Einfacher Code ist kein Zufall, sondern das Ergebnis vieler kleiner Entscheidungen gegen Ballast. KISS fragt, ob es einfacher geht; YAGNI, ob es heute gebraucht wird; DRY, ob dieses Wissen schon woanders steht; und Beware of Premature Optimization, ob gemessen ist, dass es zu langsam ist. In der Kata hat das vier Stellen entfernt, ohne eine einzige Anforderung zu verlieren. Ebenso wichtig sind die Grenzen: Gleich aussehender Code ist nicht immer gleiches Wissen, Wiederholung ist billiger als eine falsche Abstraktion, und die passende Laufzeitklasse ist keine vorzeitige Optimierung. Mit den Übungsaufgaben zu einfachem Code kannst Du die Prinzipien an der Kata und an der Mitfahr-App Mitfahrlabor anwenden.

Leitfragen

  1. Was besagt KISS, und woran erkennst Du eine unnötig komplizierte Lösung?

Keep it simple, stupid (Kelly Johnson, um 1960) verlangt, von gleichwertigen Lösungen die verständlichste zu wählen. Anzeichen für zu viel Komplexität sind tief verschachtelte Bedingungen, doppelte Verneinungen, Rechentricks statt des gemeinten Vergleichs und eine hohe zyklomatische Komplexität. Abhilfe schaffen frühe Rückgaben, direkte Ausdrücke und Bordmittel der Sprache. siehe Abschnitt „KISS: Keep it simple, stupid“

  1. Was bedeutet YAGNI, und welche Kosten verursacht Code „für später“?

You Ain’t Gonna Need It aus dem Extreme Programming: Baue eine Funktion erst, wenn sie gebraucht wird. Vorweggenommener Code kostet Zeit beim Bauen, verzögert das Wichtige, muss mitgepflegt werden und passt selten zur späteren echten Anforderung. Beispiele sind ungenutzte Parameter wie express, Schalter, die niemand setzt, und Interfaces mit nur einer Umsetzung. siehe Abschnitt „YAGNI: You Ain’t Gonna Need It“

  1. Wie lautet DRY, und mit welchen Umbauten führst Du doppelten Code zusammen?

Nach Hunt und Thomas (1999) hat jedes Stück Wissen genau eine eindeutige, maßgebliche Darstellung im System. Doppelte Werte werden zu Konstanten, erneut berechnete Ergebnisse zu wiederverwendeten Variablen, gleiche Anweisungen in Zweigen werden herausgezogen, gleiche Abläufe zu einer Methode extrahiert und gleiche Zeilen für mehrere Werte zu einer Schleife. siehe Abschnitt „DRY: Don’t Repeat Yourself“

  1. Wie unterscheidest Du zufällig gleich aussehenden Code von echter Wissensdopplung?

Du fragst, ob sich beide Stellen immer aus demselben Grund ändern. Die gemeinsame Rundungsformel für Rabatt und Mehrwertsteuer ist dasselbe Wissen; eine Widerrufsfrist und ein Zahlungsziel von zufällig 14 Tagen sind verschiedene Regeln und bleiben getrennt. siehe Abschnitt „Gleich aussehen ist nicht gleiches Wissen“

  1. Wann ist Wiederholung das kleinere Übel?

Solange erst zwei Vorkommen da sind und noch unklar ist, was wirklich gemeinsam ist (Rule of Three), wenn eine gemeinsame Methode nur mit Schaltern für jeden Aufrufer funktionieren würde (falsche Abstraktion nach Sandi Metz), wenn das Teilen zwei unabhängige Module oder Teams koppeln würde und in Testfällen, deren Lesbarkeit wichtiger ist als Kürze. siehe Abschnitt „Grenzen: Rule of Three und die falsche Abstraktion“

  1. Was meint Knuth mit „premature optimization is the root of all evil“, und wann ist Optimierung gerechtfertigt?

Kleine Effizienzgewinne ohne Kenntnis des Engpasses machen Code komplizierter, ohne messbar zu helfen; die kritischen 3 % soll man aber nicht verschenken. Gerechtfertigt ist Optimierung, wenn ein Leistungsziel verfehlt wird und Messung und Profiler die Stelle zeigen, wenn die Laufzeitklasse nicht zur Datenmenge passt oder wenn die Umgebung harte Grenzen setzt. siehe Abschnitt „Beware of Premature Optimization“

  1. Welche Zielkonflikte gibt es zwischen KISS, DRY und YAGNI, und wie wägst Du sie ab?

DRY kann KISS verletzen (gemeinsame Methode mit vielen Schaltern) und Module koppeln; YAGNI widerspricht verfrühten Erweiterungspunkten; KISS spricht gegen Entwurfsmuster für zwei stabile Fälle. Maßstab ist, welche Lösung heute verständlich und morgen am leichtesten änderbar ist; eine bewusste Abweichung wird begründet. siehe Abschnitt „Zielkonflikte abwägen“


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Einfach bleiben: KISS, YAGNI, DRY und Beware of Premature Optimization” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/einfacher-code veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-prinzipien/clean-code. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 10.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: