SOLID Prinzipien mit Beispielen: Die Kunst, Clean Code zu schreiben

SOLID Prinzipien sind fünf Entwurfsregeln, die objektorientierten Code dauerhaft änderbar halten. Seit 2012 arbeite ich an Webprojekten, Shopsystemen und internen Verwaltungsoberflächen. Wie entspannt sich ein Projekt im zweiten Jahr anfühlt, hängt nach meiner Erfahrung stark davon ab, wie ernst das Team diese fünf Regeln am ersten Tag genommen hat. In diesem Leitfaden zeige ich jedes Prinzip mit kurzen Python Beispielen, zuerst fehlerhaft und dann korrigiert.
Ich setze voraus, dass Sie die Grundlagen der objektorientierten Programmierung kennen: Klassen, Vererbung, Kapselung und Polymorphie. Diese Begriffe erkläre ich hier nicht erneut. Stattdessen konzentriere ich mich darauf, welche Entwurfsentscheidungen Ihren Code später festnageln und welche ihn beweglich halten.
Was sind SOLID Prinzipien und warum sind sie für Clean Code noch wichtig?
SOLID Prinzipien sind fünf Leitlinien für objektorientiertes Design, die Robert C. Martin zusammengetragen hat. Ihre Anfangsbuchstaben bilden das Akronym: Single Responsibility, Open Closed, Liskov Substitution, Interface Segregation und Dependency Inversion. Das gemeinsame Ziel lautet, neue Funktionen einzubauen, ohne funktionierenden Code zu beschädigen.
Warum sind sie also noch relevant? Weil die eigentlichen Kosten von Software in der Wartung entstehen, nicht im ersten Entwurf. Eine Funktion schreiben Sie einmal, aber Sie lesen, ändern und testen sie jahrelang. Genau diese zweite Phase sollen die SOLID Prinzipien günstiger machen.
Zudem sind die Ideen sprachunabhängig. Meine Beispiele sind in Python geschrieben; dieselbe Logik gilt allerdings auch für Java, C#, TypeScript, PHP oder Kotlin. Es ändert sich nur die Syntax.
Woher stammen die SOLID Prinzipien?
Die meisten Ideen sind älter als Robert C. Martins Zusammenfassung. Bertrand Meyer beschrieb das Open Closed Principle bereits 1988 in seinem Buch über objektorientierte Softwarekonstruktion. Das Liskovsche Substitutionsprinzip geht auf einen Vortrag von Barbara Liskov aus dem Jahr 1987 zurück, außerdem auf den Fachartikel zur verhaltensbasierten Subtypisierung, den sie 1994 mit Jeannette Wing veröffentlichte.
Martin fasste diese Gedanken im Jahr 2000 in seinem Aufsatz "Design Principles and Design Patterns" zusammen. Einige Jahre später schlug Michael Feathers das Akronym SOLID vor. Anders gesagt: SOLID ist keine Erfindung einer einzelnen Person, sondern eine einprägsame Zusammenfassung jahrzehntelanger Praxis.
- S: Single Responsibility Principle, das Prinzip der einzigen Verantwortung.
- O: Open Closed Principle, offen für Erweiterung und geschlossen für Änderung.
- L: Liskov Substitution Principle, das Liskovsche Substitutionsprinzip.
- I: Interface Segregation Principle, die Trennung von Schnittstellen.
- D: Dependency Inversion Principle, die Umkehr von Abhängigkeiten.
Wie hängen Clean Code und SOLID zusammen?
Clean Code ist Code, den eine Leserin ohne große Mühe versteht und mit gutem Gefühl ändert. SOLID ist ein Weg, dieses Ziel auf Ebene von Klassen und Modulen zu erreichen. Gute Namen, kurze Funktionen und sinnvolle Tests sorgen für saubere Zeilen. SOLID regelt dagegen, wie die Teile miteinander verbunden sind.
Stellen Sie es sich so vor: Code mit wunderschönen Variablennamen ist trotzdem nicht sauber, wenn jede Klasse von jeder anderen abhängt. Ebenso ermüdet ein gut strukturiertes System mit Funktionen von 200 Zeilen seine Leser. Sie brauchen deshalb beides.
Das häufigste Problem, das ich in Projekten sehe: Das Team achtet auf Namen, ignoriert aber Abhängigkeiten. Die Folge ist bekannt. Tests werden mühsam, jede kleine Änderung berührt fünf Dateien, und das Team bekommt Angst vor Veränderungen.
Was besagt das Single Responsibility Principle wirklich?
Die klassische Definition lautet: Eine Klasse sollte nur einen einzigen Grund haben, sich zu ändern. Martin hat das 2014 in einem Beitrag zum Single Responsibility Principle präzisiert. Dort erklärt er, dass "Verantwortung" eigentlich einen Stakeholder meint, also eine Person oder ein Team, das Änderungen anfordert.
Diese Unterscheidung ist wichtig, denn viele lesen das Prinzip als "eine Klasse soll nur eine Sache tun". Die bessere Frage lautet: Wer würde mich bitten, diese Klasse zu ändern? Wenn Buchhaltung, Reporting und Datenbankadministration alle Änderungen an derselben Klasse verlangen, trägt diese Klasse drei Verantwortungen.
Ein Beispiel: Eine Rechnungsklasse berechnet Summen, erzeugt ein PDF und speichert sich selbst in der Datenbank. Drei verschiedene Stakeholder bearbeiten also dieselbe Datei. Somit kann die Änderung der einen Person die Funktion einer anderen zerstören.
Wie setzen Sie das Prinzip der einzigen Verantwortung im Code um?
Zunächst eine Klasse, die gegen die Regel verstößt. Sie mischt Berechnung, Formatierung und Speicherung an einer Stelle:
class Rechnung:
def __init__(self, posten, steuersatz):
self.posten = posten
self.steuersatz = steuersatz
def summe(self):
netto = sum(p.preis * p.menge for p in self.posten)
return netto * (1 + self.steuersatz)
def pdf_erzeugen(self):
... # Layout, Schriften, Logo
def speichern(self, verbindung):
verbindung.execute("INSERT INTO rechnungen ...")
Danach teilen Sie die Klasse in drei Teile. Die Berechnung bleibt als Geschäftsregel erhalten; Formatierung und Speicherung ziehen in eigene Klassen um:
class Rechnung:
def __init__(self, posten, steuersatz):
self.posten = posten
self.steuersatz = steuersatz
def summe(self):
netto = sum(p.preis * p.menge for p in self.posten)
return netto * (1 + self.steuersatz)
class RechnungPdfRenderer:
def rendern(self, rechnung): ...
class RechnungRepository:
def __init__(self, verbindung):
self.verbindung = verbindung
def speichern(self, rechnung): ...
Ändert sich jetzt das PDF Layout, fassen Sie nur den Renderer an. Außerdem testen Sie die Berechnung in Millisekunden, ganz ohne Datenbank. Kurz gesagt: Der erste Gewinn dieses Prinzips ist Testbarkeit.
Warum sagt das Open Closed Principle "erweitern statt ändern"?
Das Open Closed Principle besagt, dass eine Softwareeinheit offen für Erweiterung, aber geschlossen für Änderung sein sollte. Wenn Sie neues Verhalten brauchen, fügen Sie ein neues Teil hinzu, statt funktionierenden und getesteten Code zu öffnen und umzuschreiben.
Das klingt zunächst unmöglich. In der Praxis ist die Bedeutung allerdings einfach. Sie erkennen die Stellen, die sich häufig ändern, und verstecken sie hinter einer Abstraktion. Kommt dann eine Änderung, schreiben Sie eine neue Klasse und lassen die alte in Ruhe.
Eine Warnung gehört dazu: Alles erweiterbar machen zu wollen, ist ebenfalls ein Fehler. Wenden Sie das Prinzip nur dort an, wo sich tatsächlich etwas ändert oder wo es konkrete Hinweise darauf gibt. Sonst entstehen Schichten, die niemand braucht.
Was ändert sich, wenn Sie eine neue Zahlungsart hinzufügen?
Der Verstoß gegen dieses Prinzip, den ich in E-Commerce Projekten am häufigsten sehe, ist eine Funktion, die die Zahlungsart über eine if Kette auswählt:
def zahlung_annehmen(art, betrag):
if art == "karte":
return karte_belasten(betrag)
elif art == "ueberweisung":
return ueberweisung_erfassen(betrag)
elif art == "nachnahme":
return nachnahme_erfassen(betrag)
raise ValueError("Unbekannte Zahlungsart")
Jede neue Zahlungsart zwingt Sie, diese Funktion zu öffnen. Deshalb legen Sie die Zahlungsarten hinter eine gemeinsame Schnittstelle:
from abc import ABC, abstractmethod
class Zahlungsart(ABC):
@abstractmethod
def bezahlen(self, betrag): ...
class Kartenzahlung(Zahlungsart):
def bezahlen(self, betrag): ...
class Ueberweisung(Zahlungsart):
def bezahlen(self, betrag): ...
def zahlung_annehmen(art: Zahlungsart, betrag):
return art.bezahlen(betrag)
Eine digitale Geldbörse bedeutet jetzt genau eine neue Klasse. Zudem bleiben die bestehenden Tests für Karte und Überweisung unberührt. Details zu abstrakten Basisklassen finden Sie in der offiziellen Dokumentation des abc Moduls. Den größeren Rahmen eines Bestellprozesses beschreibe ich auf meiner Seite zur E-Commerce Beratung.
Was schützt das Liskovsche Substitutionsprinzip?
Das Liskovsche Substitutionsprinzip besagt: Ein Objekt einer Unterklasse muss überall funktionieren, wo die Oberklasse erwartet wird, ohne die Korrektheit des Programms zu gefährden. Anders gesagt, eine Unterklasse muss die Versprechen ihrer Oberklasse halten.
Diese Versprechen lassen sich in Gruppen einteilen. Eine Unterklasse darf bei Eingaben nicht strenger sein, bei Ausgaben nicht nachlässiger, und sie darf die Regeln der Oberklasse nicht brechen. Sagt die Oberklasse etwa "der Kontostand ist nie negativ", dann darf die Unterklasse das nicht aufweichen.
- Vorbedingungen: Die Unterklasse akzeptiert jede Eingabe, die auch die Oberklasse akzeptiert.
- Nachbedingungen: Sie garantiert mindestens das, was die Oberklasse garantiert.
- Invarianten: Der Zustand, der immer gelten muss, bleibt intakt.
- Ausnahmen: Keine neuen Fehlertypen, die die Oberklasse nie auslöst.
Ein Verstoß gegen LSP passiert meist problemlos den Compiler und rutscht oft durch die Tests. Das Problem zeigt sich erst im Betrieb, an dem Tag, an dem jemand die Unterklasse an einer unerwarteten Stelle nutzt. Daher halte ich diesen Verstoß für den heimtückischsten der fünf.
Wie verletzt das Beispiel mit Quadrat und Rechteck das Prinzip?
In der Geometrie ist jedes Quadrat ein Rechteck. Also wirkt diese Vererbung zunächst vernünftig:
class Rechteck:
def breite_setzen(self, b): self.b = b
def hoehe_setzen(self, h): self.h = h
def flaeche(self): return self.b * self.h
class Quadrat(Rechteck):
def breite_setzen(self, b): self.b = self.h = b
def hoehe_setzen(self, h): self.b = self.h = h
def test_flaeche(r: Rechteck):
r.breite_setzen(5)
r.hoehe_setzen(4)
assert r.flaeche() == 20
Der Test läuft mit einem Rechteck durch und scheitert mit einem Quadrat, weil das Quadrat beim Setzen der Höhe auch die Breite ändert. Das Versprechen des Rechtecks, dass Breite und Höhe unabhängig sind, gilt nicht mehr.
Die Lösung besteht darin, Vererbung am Verhalten auszurichten. Quadrat und Rechteck werden Geschwister unter einer gemeinsamen Abstraktion "Form". Beide berechnen eine Fläche, aber keines behauptet, das andere ersetzen zu können. Konkret bauen Sie "ist ein" Beziehungen nach dem Verhalten im Code, nicht nach der realen Welt.
Was ist das Interface Segregation Principle?
Das Interface Segregation Principle besagt, dass kein Client von Methoden abhängen sollte, die er nicht nutzt. Statt einer großen Schnittstelle, die alles abdeckt, entwerfen Sie kleine Schnittstellen für bestimmte Rollen.
Eine aufgeblähte Schnittstelle hat allerdings ihren Preis. Jede Klasse, die sie umsetzt, schreibt leere Methoden oder Fehler mit dem Hinweis "nicht unterstützt". Zudem müssen Sie Clients neu bauen und testen, sobald sich ein Teil der Schnittstelle ändert, den diese Clients nie verwenden.
Zudem ist ISP eng mit LSP verwandt. Wirft eine Klasse bei einer Methode nur "nicht unterstützt", haben Sie vermutlich gleichzeitig eine zu große Schnittstelle und ein Substitutionsproblem.
Wie setzen Sie die Trennung von Schnittstellen in Python mit Protocol um?
Nehmen wir ein System für Bürogeräte. Eine große Schnittstelle verlangt Drucken, Scannen und Faxen zugleich:
class Buerogeraet(ABC):
@abstractmethod
def drucken(self, dokument): ...
@abstractmethod
def scannen(self, dokument): ...
@abstractmethod
def faxen(self, dokument): ...
class EinfacherDrucker(Buerogeraet):
def drucken(self, dokument): ...
def scannen(self, dokument): raise NotImplementedError
def faxen(self, dokument): raise NotImplementedError
Der einfache Drucker kann zwei Methoden nicht sinnvoll umsetzen. Stattdessen trennen Sie die Rollen. In Python eignet sich dafür typing.Protocol, weil es strukturelle Subtypisierung erlaubt:
from typing import Protocol
class Druckbar(Protocol):
def drucken(self, dokument) -> None: ...
class Scanbar(Protocol):
def scannen(self, dokument) -> bytes: ...
class EinfacherDrucker:
def drucken(self, dokument) -> None: ...
def bericht_drucken(geraet: Druckbar, bericht):
geraet.drucken(bericht)
Die Berichtsfunktion hängt jetzt nur noch von der Fähigkeit zu drucken ab. Somit funktionieren ein Multifunktionsgerät und ein einfacher Drucker gleichermaßen, ohne tote Methoden.
Was bedeutet das Dependency Inversion Principle?
Das Dependency Inversion Principle hat zwei Teile. Erstens sollen Module auf hoher Ebene nicht von Modulen auf niedriger Ebene abhängen; beide sollen von Abstraktionen abhängen. Zweitens sollen Abstraktionen nicht von Details abhängen, sondern Details von Abstraktionen.
Ein konkreter Fall: Ein Bestellservice enthält Geschäftsregeln und liegt damit auf hoher Ebene. Eine MySQL Verbindung oder ein SMS Anbieter sind Details. Erzeugt der Bestellservice die MySQL Klasse direkt, dann müssen Sie bei einem Datenbankwechsel auch die Geschäftslogik öffnen.
class BestellRepository(Protocol):
def speichern(self, bestellung) -> None: ...
class BestellService:
def __init__(self, repo: BestellRepository):
self.repo = repo
def bestellen(self, bestellung):
# Geschäftsregeln stehen hier
self.repo.speichern(bestellung)
class MySQLBestellRepository:
def speichern(self, bestellung) -> None: ...
Der Abhängigkeitspfeil zeigt jetzt in die andere Richtung. Die Geschäftsregel definiert die Schnittstelle, und die Datenbankklasse richtet sich danach. Folglich können Sie im Test statt einer echten Datenbank eine einfache Attrappe im Arbeitsspeicher übergeben.
Sind Dependency Inversion und Dependency Injection dasselbe?
Nein, sie sind nicht dasselbe, ergänzen sich aber. Dependency Inversion ist ein Entwurfsprinzip und beschreibt, in welche Richtung Abhängigkeiten zeigen sollen. Dependency Injection ist eine Technik: Ein Objekt erhält benötigte Teile von außen, statt sie selbst zu erzeugen.
Die Übergabe des Repositorys über den Konstruktor im obigen Beispiel ist Injection. Allerdings bedeutet Injection allein noch nicht, dass Sie dem Prinzip folgen. Ist der injizierte Typ weiterhin die konkrete MySQL Klasse, hat sich die Richtung der Abhängigkeit nicht geändert.
Einen verbreiteten Irrtum möchte ich zudem korrigieren. Für Dependency Inversion brauchen Sie kein schweres Framework. In kleinen und mittleren Projekten genügt es meist, die Objekte am Einstiegspunkt der Anwendung von Hand zu verbinden; das ist auch leichter zu lesen.
Wie lassen sich die SOLID Prinzipien in einer Tabelle zusammenfassen?
Alle fünf nebeneinander zu sehen, hilft beim Erinnern, welches Prinzip welches Problem löst. Die Tabelle eignet sich außerdem als schnelle Checkliste im Code Review:
| Prinzip | Kurzfassung | Typisches Warnsignal | Übliche Lösung |
|---|---|---|---|
| SRP | Ein Änderungsgrund pro Klasse | Verschiedene Teams kollidieren in derselben Datei | Klasse nach Stakeholdern aufteilen |
| OCP | Offen für Erweiterung, geschlossen für Änderung | Eine if/elif Kette wächst mit jedem neuen Typ | Abstraktion und Polymorphie |
| LSP | Unterklassen ersetzen ihre Oberklasse | isinstance Prüfungen, NotImplementedError | Vererbung am Verhalten ausrichten |
| ISP | Keine Abhängigkeit von ungenutzten Methoden | Leere Methodenrümpfe | Kleine, rollenbasierte Schnittstellen |
| DIP | Geschäftsregeln hängen von Abstraktionen ab | Services erzeugen Datenbankobjekte direkt | Schnittstellen plus Dependency Injection |
Die Warnsignale sind allerdings kein Beweis. Sie zeigen nur, wo Sie genauer hinsehen sollten. Eine if Kette ist zum Beispiel manchmal völlig harmlos, besonders bei zwei oder drei festen Optionen.
Wann schießen SOLID Prinzipien über das Ziel hinaus?
Die erste Falle nach dem Lernen der Prinzipien besteht darin, sie überall anzuwenden. Schnittstellen mit nur einer Implementierung, fünf Klassen für eine Aufgabe von drei Zeilen und schwer nachvollziehbare Umwege sind typische Ergebnisse. Sie wollen den Code flexibler machen und machen ihn stattdessen schwerer lesbar.
Meine einfache Regel: Ich führe eine Abstraktion ein, wenn der zweite konkrete Bedarf auftaucht, nicht schon beim ersten. Entwickler nennen das oft "Rule of Three" oder YAGNI (You Aren't Gonna Need It). Sie bauen Flexibilität also rund um echte Änderungen, nicht um Vermutungen.
- Zwingen Sie SOLID nicht in kleine Skripte und Einmalwerkzeuge.
- Im Prototyp zählt Tempo; Struktur folgt, sobald das Produkt steht.
- Sehen Sie eine Schnittstelle mit nur einer Implementierung, prüfen Sie ihren Nutzen.
- Je mehr Abstraktionen Sie einführen, desto sorgfältiger müssen Ihre Namen sein.
Martin selbst betont in seinem Beitrag "Solid Relevance" aus dem Jahr 2020, dass die Prinzipien Orientierung bieten und keine Gesetze sind. Das sehe ich genauso: SOLID ist kein Selbstzweck, sondern ein Werkzeug, um Wartungskosten zu senken.
Wie wenden Sie SOLID Prinzipien Schritt für Schritt auf bestehenden Code an?
Ein laufendes Projekt komplett neu zu schreiben, ist fast nie die richtige Entscheidung. Stattdessen gehen Sie in kleinen, sicheren Schritten vor. Diese Reihenfolge nutze ich in Kundenprojekten:
- Halten Sie das aktuelle Verhalten mit Charakterisierungstests rund um den betroffenen Bereich fest.
- Finden Sie über die Versionsgeschichte die Dateien, die sich am häufigsten ändern, und beginnen Sie dort.
- Teilen Sie große Klassen nach Stakeholdern auf; beginnen Sie also mit SRP.
- Legen Sie externe Abhängigkeiten wie Datenbank, E-Mail und Zahlungen hinter Schnittstellen.
- Verwandeln Sie wiederkehrende if Ketten in Polymorphie, sobald neue Typen hinzukommen.
- Lassen Sie nach jedem Schritt die Tests laufen und committen Sie in kleinen Paketen.
Der Grund für diese Reihenfolge ist einfach: SRP und DIP verbessern die Testbarkeit sofort. Wer ohne Sicherheitsnetz aus Tests an OCP oder LSP arbeitet, riskiert dagegen neue Fehler während des Aufräumens.
Wie erkennen Sie Verstöße gegen SOLID im Code Review?
Das Code Review ist der wirksamste Ort, um die Prinzipien zur Teamgewohnheit zu machen. Im Review stelle ich einige Fragen der Reihe nach, und sie fangen die meisten Verstöße früh ab.
- Wie viele verschiedene Personen oder Teams würden diese Klasse ändern wollen?
- Muss man für einen neuen Typ eine bestehende Funktion öffnen?
- Gibt es Verzweigungen über isinstance oder andere Typprüfungen?
- Setzt eine Klasse manche Schnittstellenmethoden leer oder mit Fehler um?
- Erzeugt eine Geschäftsregel ihren eigenen Datenbank oder HTTP Client?
Diese Fragen eröffnen ein Gespräch, keine Anklage. Außerdem beschreibe ich lieber die konkrete Wirkung, statt das Prinzip zu nennen. "Diese Klasse verletzt SRP" überzeugt weniger als "Wenn sich das Berichtsformat ändert, müssen wir auch die Rechnungslogik neu testen". So bleibt die Diskussion bei echten Wartungskosten statt bei Lehrbuchbegriffen.
Wo begegnen Ihnen SOLID Prinzipien in Webprojekten?
Unternehmenswebsites und Verwaltungsoberflächen sind die Orte, an denen SOLID täglich auf die Probe gestellt wird. Ein Kontaktformular versendet heute etwa eine E-Mail, legt morgen einen CRM Eintrag an und soll übermorgen eine WhatsApp Nachricht auslösen. Liegen die Benachrichtigungskanäle hinter einer Schnittstelle, ist jeder neue Kanal nur eine neue Klasse.
Im Frontend ist es also nicht anders. Architekturen, in denen große Teams das Frontend in Teile zerlegen, habe ich im Artikel über Micro Frontends behandelt. Die Grenzziehung dort ist im Grunde SRP auf Systemebene.
Saubere Architektur wirkt sich zudem indirekt auf die Performance aus, denn in Code mit klaren Zuständigkeiten finden Sie Engpässe schneller. Für die Messung helfen Ihnen meine Beiträge zum Lighthouse Performancetest und zur Prüfung der Mobilfreundlichkeit.
Warum werden Tests mit SOLID einfacher?
Das größte Hindernis für Unit Tests ist Code, der seine Abhängigkeiten intern selbst erzeugt. Verbindet sich ein Bestellservice mit der Datenbank, versendet Nachrichten per E-Mail und ruft eine API auf, dann brauchen Sie für eine einzige Geschäftsregel drei externe Systeme.
DIP und SRP beseitigen dieses Hindernis. Da Abhängigkeiten von außen kommen, übergeben Sie im Test Attrappen. Weil die Klassen klein sind, braucht jeder Test nur wenige Zeilen Vorbereitung. Deshalb laufen die Tests schnell, und das Team führt sie tatsächlich aus.
class SpeicherRepository:
def __init__(self):
self.eintraege = []
def speichern(self, bestellung):
self.eintraege.append(bestellung)
def test_bestellung_gespeichert():
repo = SpeicherRepository()
BestellService(repo).bestellen({"id": 1})
assert len(repo.eintraege) == 1
Dieser Test endet in Millisekunden und braucht kein externes System. Dieselbe Schnittstelle kann im Betrieb auf MySQL zeigen, im Test auf den Arbeitsspeicher und später vielleicht auf eine Clouddatenbank. Die Geschäftsregel bekommt von keinem dieser Wechsel etwas mit. Nach meiner Erfahrung sind solche schnellen Tests der Hauptgrund, warum Teams ihre Testsuite vor jedem Commit wirklich laufen lassen. Langsame Tests überspringt man dagegen, und das Sicherheitsnetz verschwindet.
Wie hängen SOLID und Entwurfsmuster zusammen?
Entwurfsmuster sind fertige Anwendungen der SOLID Ideen auf wiederkehrende Probleme. Das Strategiemuster ist genau das Zahlungsbeispiel aus dem Abschnitt zum Open Closed Principle: eine gemeinsame Schnittstelle und austauschbare Umsetzungen. Das Adaptermuster unterstützt DIP, indem es eine externe Bibliothek an Ihre eigene Schnittstelle anpasst.
Ebenso erlaubt das Dekorierermuster, einer Klasse Verhalten hinzuzufügen, ohne sie zu öffnen; auch das ist eine Form des Open Closed Principle. Das Fabrikmuster bündelt die Objekterzeugung an einer Stelle und hält Geschäftsregeln von konkreten Klassen fern.
- Strategie: versteckt einen wechselnden Algorithmus hinter einer Schnittstelle; passt zu OCP.
- Adapter: bindet eine externe Bibliothek an Ihre Abstraktion; unterstützt DIP.
- Dekorierer: ergänzt Verhalten ohne Änderung der Klasse; passt zu OCP.
- Fabrik: zentralisiert die Objekterzeugung; hilft bei SRP und DIP.
Trotzdem ist es nur eine weitere Form der Übertreibung, Muster auswendig zu lernen und überall einzubauen. Ich empfehle, zuerst das Problem zu beschreiben und dann das passende Muster zu wählen. Gibt es kein Problem, brauchen Sie auch kein Muster.
Gelten SOLID Prinzipien auch in der funktionalen Programmierung?
Die Prinzipien stammen aus der objektorientierten Welt, ihr Kern ist allerdings unabhängig vom Paradigma. In funktionalem Code sprechen Sie von Modulen und Funktionen statt von Klassen. Dennoch bringen einzige Verantwortung, als Parameter übergebene Abhängigkeiten und kleine Verträge denselben Nutzen.
Eine Funktion, die eine andere Funktion als Parameter annimmt, ist zum Beispiel die funktionale Form von DIP. Ebenso trägt die Erwartung, dass jede Funktion mit passender Signatur das versprochene Verhalten zeigt, denselben Gedanken wie LSP. Die Konzepte bleiben somit, nur die Mechanik ändert sich.
In einer Sprache mit mehreren Paradigmen wie Python nutze ich beides. Für Konzepte mit Zustand und klarem Verantwortlichen schreibe ich Klassen, für reine Transformationen einfache Funktionen. Diese Balance hält den Code flexibel und schlicht zugleich.
Wie prüfen Sie, ob ein Entwicklerteam SOLID wirklich beherrscht?
Wenn Sie ein externes Entwicklerteam beauftragen, ist das Aufsagen der fünf Begriffe kein starkes Signal. Aussagekräftig ist vielmehr, ob das Team erklären kann, wie es die Prinzipien im eigenen Code anwendet und wo es sie bewusst lockert.
Im Gespräch können Sie zum Beispiel fragen: "Wenn wir eine neue Zahlungsart wollen, wie viele Dateien fassen Sie an?" Lautet die Antwort "eine neue Klasse und eine Zeile zur Registrierung", ist die Architektur vermutlich gesund. Eine Antwort wie "kommt darauf an" kündigt meist ein längeres Gespräch an.
Bei Webprojekten kläre ich solche technischen Entscheidungen gern gleich zum Start; die Codestruktur gehört bei meinem Webdesign zum Lieferumfang. Wie sich Technik und Nutzererlebnis gegenseitig beeinflussen, lesen Sie im Beitrag über SEO und UX.
Fazit: Ist Clean Code ein Regelwerk oder eine Gewohnheit?
Für mich ist Clean Code eine Gewohnheit, und SOLID bildet ihr Gerüst. Die Prinzipien auswendig zu lernen, dauert ein paar Stunden. Sie an der richtigen Stelle anzuwenden und an der falschen wegzulassen, dauert dagegen Jahre. Probieren Sie deshalb jedes Prinzip zuerst an einem kleinen Beispiel aus und testen Sie es dann an einem Modul Ihres echten Projekts.
Schließlich noch ein Gedanke: Das Ziel ist keine perfekte Architektur, sondern dass Änderungen günstig bleiben. Kann Ihr Team auf eine neue Anforderung ohne Sorge "gern" sagen, sind Sie auf dem richtigen Weg. Weitere Beiträge finden Sie in meinem Blog.




