Zurück zu den Artikeln
📄
clean codeSOLIDKISSDRYYAGNIsoftware designcode maintainability

Was sind Clean Code, SOLID und KISS – und wie helfen sie?

Sauberer Code verwandelt lauffähige Software in dauerhaften Geschäftswert. Erfahren Sie, wie das Zusammenspiel aus SOLID-Prinzipien, DRY, KISS und YAGNI Codebases lesbar, wartbar und langfristig refaktorisierbar hält – ohne in dogmatische Überarchitektur zu verfallen.

Was sind Clean Code, SOLID und KISS – und wie helfen sie?

Das Schreiben von funktionsfähigem Code ist anspruchsvoll, aber die Herausforderung besteht darin, Code zu erstellen, den sowohl du als auch deine Teamkollegen nach sechs Monaten noch verstehen können. Gerade weil technische Schulden sich im Laufe der Zeit unbemerkt anhäufen, wird diese Langzeitlesbarkeit zur eigentlichen Prüfsteine für die Qualität.

Clean Code geht nicht darum, Eleganz für sich selbst zu schaffen. Eleganz allein reicht nicht, wenn sie die Wartbarkeit behindert. Es zielt darauf ab, Änderungen sicherer zu gestalten, Fehler leichter zu finden und neue Teammitglieder rasch einzuwärmen. Durch klare Struktur lässt sich ein Defekt schneller lokalisieren, weil die betroffenen Stellen eindeutig erkennbar sind. Dazu dienen mehrere ergänzende Prinzipien.

Grundlagen von Clean Code

Clean Code weist einige sichtbare Eigenschaften auf:

  • Lesbar: Absicht und Struktur sind aus Namen und Aufbau ersichtlich. Es sollten keine Kommentare erforderlich sein, um die Funktionalität des Codes zu erklären. Wenn ein Entwickler den Namen einer Methode liest, kann er sofort die zugrunde liegende Aufgabe rekonstruieren, ohne den Quellcode zeilenweise zu studieren.
  • Änderbar: Neue Funktionen können hinzugefügt werden, ohne dass Kaskaden von Umbauten notwendig sind. Durch klare Trennung der Verantwortlichkeiten bleibt die betroffene Komponente isoliert, sodass ein Update keine unbeabsichtigten Seiteneffekte auslöst.
  • Testbar: Das Verhalten ist gut genug isoliert, um unabhängig getestet zu werden. Dadurch lassen sich Unit‑Tests schreiben, die exakt ein einzelnes Modul prüfen und sofortige Rückmeldung über Regressionen geben.
  • Fokussiert: Jede Einheit erledigt eine einzige Aufgabe. Ein solcher Fokus verhindert das Entstehen von „God‑Objects“, die das System unübersichtlich machen.

Teams profitieren von Clean Code, wenn sie einheitliche Formate, gemeinsame Namenskonventionen und eine einfach strukturierte Code-Review-Checkliste einhalten. Wichtige Prüfpunkte sind konsistente Einrückungen, sprechende Bezeichner und das Vermeiden von tief verschachtelten Kontrollstrukturen.

SOLID: Fünf Entwurfsprinzipien

SOLID, das durch Robert C. Martin populär gemacht wurde, bietet Leitlinien für den Entwurf von Code, der Änderungen aufnehmen kann, ohne unnötige Fehler zu verursachen.

Single Responsibility Principle (SRP)

Eine Klasse sollte nur einen Grund für Änderungen haben.

// ❌ Verletzt SRP: Daten, Darstellung und Speicherung in einer Klasse
class UserManager {
  getUser(id: string) { /* ... */ }
  formatForDisplay(user: User) { return `${user.name} (${user.email})`; }
  saveToDatabase(user: User) { /* ... */ }
}

// ✅ SRP: Jede Klasse erledigt eine Aufgabe
class UserRepository {
  getById(id: string): User { /* ... */ }
  save(user: User): void { /* ... */ }
}

class UserFormatter {
  format(user: User): string { return `${user.name} (${user.email})`; }
}

Soll das Datenbankschema geändert werden, wird UserRepository angepasst. Bei Änderungen der Darstellung wird UserFormatter bearbeitet. Diese Trennung sorgt dafür, dass ein Datenbankwechsel keine Auswirkungen auf die Anzeige hat und umgekehrt. So bleibt die Codebasis stabil, selbst wenn mehrere Teams parallel an unterschiedlichen Aspekten arbeiten.

Open/Closed Principle (OCP)

Softwareentitäten sollten für Erweiterungen offen, aber für Änderungen am bestehenden Code geschlossen sein.

// ❌ Verletzt OCP: Eine neue Zahlungsart erfordert Änderungen am vorhandenen Code
class PaymentProcessor {
  process(type: 'credit' | 'debit' | 'paypal') {
    if (type === 'credit') { /* ... */ }
    if (type === 'debit') { /* ... */ }
  }
}

// ✅ OCP: Neue Zahlungsarten ohne Änderung des Prozessors
interface PaymentMethod {
  process(): void;
}

class CreditCardPayment implements PaymentMethod {
  process() { /* credit card logic */ }
}

class PayPalPayment implements PaymentMethod {
  process() { /* PayPal logic */ }
}

class PaymentProcessor {
  process(method: PaymentMethod) {
    method.process();
  }
}

Für CryptoPayment wird eine neue Klasse geschrieben, ohne PaymentProcessor zu verändern. Damit bleibt der bereits getestete Zahlungsfluss intakt, und man kann die neue Variante isoliert verifizieren.

Liskov Substitution Principle (LSP)

Untertypen müssen ohne unerwartetes Verhalten anstelle ihrer Basistypen eingesetzt werden können.

// ❌ Verletzt LSP: Bird verspricht Fliegen, Penguin kann es nicht
class Bird {
  fly(): void { console.log('Flying'); }
}

class Penguin extends Bird {
  fly() { throw new Error('Penguins cannot fly'); }
}

// ✅ Fähigkeit ausdrücklich modellieren
interface Flyable {
  fly(): void;
}

class Eagle implements Flyable {
  fly() { console.log('Soaring'); }
}

class Penguin {
  swim() { console.log('Swimming'); }
}

Wenn eine Unterklasse den Vertrag der Elternklasse nicht erfüllen kann, sollte sie nicht von ihr erben. Andernfalls würde ein Aufruf der Basisklasse plötzlich eine Ausnahme werfen und das gesamte Programm destabilisieren.

Interface Segregation Principle (ISP)

Clients sollen nicht von Methoden abhängen, die sie nicht verwenden.

interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
}

class Robot implements Worker {
  work() { /* ... */ }
  eat() { throw new Error('Robots do not eat'); }
  sleep() { throw new Error('Robots do not sleep'); }
}

interface Workable { work(): void; }
interface Restable { rest(): void; }

class Human implements Workable, Restable {
  work() { /* ... */ }
  rest() { /* ... */ }
}

class Robot implements Workable {
  work() { /* ... */ }
}

Durch das Aufteilen des Interfaces kann jede Implementierung nur die tatsächlich benötigten Methoden bereitstellen, was das Risiko von Laufzeitfehlern reduziert.

Dependency Inversion Principle (DIP)

Hochgradige Module sollten nicht von niedriggradigen Modulen abhängen. Beide sollten von Abstraktionen abhängen.

interface Database {
  insert(entity: any): void;
}

class OrderService {
  constructor(private db: Database) {}

  save(order: Order) {
    this.db.insert(order);
  }
}

const db = new MySQLDatabase();
const service = new OrderService(db);

Die Geschäftslogik kennt die konkrete Datenbank nicht. Dies macht sie testbar und austauschbar. Im Test kann man deshalb eine In‑Memory‑Implementierung einspeisen und die Geschäftslogik ohne echten Datenbankzugriff prüfen.

DRY: Don't Repeat Yourself

Jedes Wissenselement sollte eine einzige, eindeutige Repräsentation haben.

function validateEmail(email: string): boolean {
  return email.includes('@');
}

function validateEmailForSignup(email: string): boolean {
  return validateEmail(email);
}

DRY bedeutet nicht, dass man niemals etwas wiederholt. Vielmehr bedeutet es, dass Geschäftsregeln, schwierige Logik und Verhalten, das sich gemeinsam weiterentwickeln muss, nicht mehrfach gepflegt werden.

KISS: Keep It Simple, Stupid

Wähle die einfachste Lösung, die das Problem klar löst.

// ❌ Überkompliziert: Rekursion, obwohl eine Schleife genügt
function sum(numbers: number[]): number {
  if (numbers.length === 0) return 0;
  return numbers[0] + sum(numbers.slice(1));
}

// ✅ Einfach und auf einen Blick verständlich
function sum(numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

Vor dem Ausliefern des Codes solltest du fragen: Ist der einfachste Weg auch der sichtbarste? Erzählen die Namen und Strukturen der Code‑Blöcke die Geschichte, auch wenn Kommentare fehlen? Ein klarer Name ersetzt oft mehrere erklärende Kommentare und reduziert die kognitive Belastung beim Lesen.

YAGNI: You Aren't Gonna Need It

Füge Funktionen nicht hinzu, bevor du sie wirklich brauchst.

Spekulative Abstraktionen sind kostspielig. Sie erfordern Wartung, erhöhen die kognitive Belastung und weisen sich oft als falsch heraus, sobald die tatsächlichen Anforderungen klarer sind. YAGNI ist keine Entschuldigung für schlechtes Design, sondern ein Gegenmittel gegen „Future‑Proofing“, das nie den gewünschten Nutzen bringt.

Alles zusammenführen

Diese Prinzipien unterstreichen einander:

  • KISS macht Code leichter testbar.
  • DRY hält Regeln konsistent.
  • YAGNI hält die Oberfläche klein und reduziert den Test‑ und Sicherungsaufwand.

Das Kerninsight: Diese Prinzipien sind keine Regeln, die man nur für Code‑Reviews befolgt. Sie verdichten harte Lektionen aus den Erfahrungen von Ingenieuren, die zusahen, wie Systeme zusammenbrachen, und die Ursachen herausfanden.

Wann welches Prinzip anwenden?

Situation Prinzip
Eine Funktion erledigt zu viele Dinge SRP
Für eine Erweiterung muss bestehender Code editiert werden OCP
Eine Unterklasse kann das Verhalten der Elternklasse nicht erfüllen LSP
Eine Klasse hängt von ungenutzten Interface-Methoden ab ISP
Geschäftslogik ist an konkrete Infrastruktur gebunden DIP
Dieselbe Logik steht an drei Stellen DRY
Eine einfache Aufgabe wird überdesignt KISS
Funktionen werden „für alle Fälle“ gebaut YAGNI

Ein bewusster Blick auf diese Fragen hilft, strukturierten, wartbaren Code zu produzieren.

Quellen & Referenzen

  1. [1]
  2. [2]
  3. [3]
  4. [4]
  5. [5]

Comments (0)

Loading comments...