SOLID Principles: Die fünf goldenen Regeln des OOP
Die SOLID Principles bestehen aus fünf Praktiken und Richtlinien für sauberen, wartbaren und flexiblen Code beim objektbasierten Programmieren. Die Anwendung und Einhaltung der Prinzipien ermöglicht ein leicht verständliches Software-Design über lange Entwicklungszeiträume hinweg. Auf diese Weise lässt sich nicht nur besserer Code schreiben, sondern auch bestehender Code besser pflegen.
Was sind die SOLID Principles?
Am Anfang eines guten Quellcodes stehen Regeln, Programmierparadigmen und ein angemessener Programmierstil für einen effizienten und sauberen Code. Genau dafür sorgen die fünf SOLID Principles, die von Robert C. Martin, Bertrand Meyer und Barbara Liskov geprägt wurden. Wer bestimmte Prinzipien in der objektorientierten Programmierung (OOP) mit Sprachen wie Python oder Java beachtet, schreibt nicht nur besseren Code, sondern sorgt langfristig auch für eine effizientere Code-Pflege, ein nachhaltiges und flexibles Software-Design und mehr Sicherheit.
Die Bezeichnung SOLID steht zum einen für die solide Entwicklungsbasis für alle, die Programmieren lernen wollen. Zum anderen setzt sich die Abkürzung aus den jeweils ersten Buchstaben der fünf Prinzipien (nach Michael Feathers) zusammen:
- Single Responsibility Principle: Eine Klasse soll nur eine Verantwortung (nicht Aufgabe) und nur einen Grund für eine Änderung haben!
- Open Closed Principle: Klassen sollen für Erweiterungen offen und für Änderungen geschlossen sein!
- Liskov Substitution Principle: Subklassen sollen alle Methoden und Eigenschaften der Superklasse erben und implementieren können!
- Interface Segregation Principle: Schnittstellen sollen nicht mehr Methoden enthalten, als für implementierende Klassen erforderlich sind!
- Dependency Inversion Principle: Klassen sollen nicht von anderen Klassen, sondern von Schnittstellen oder abstrakten Klassen abhängen!
Welche Vorteile bieten die SOLID Principles?
Wo keine Regeln gelten, tritt Chaos ein – das wird beim Programmieren deutlich. Bereits kleine Fehler, Ungenauigkeiten und Lücken können „unbehandelt“ auf längere Sicht einen guten Quellcode völlig unbrauchbar machen. Dafür braucht es nicht viel: Es genügen schon komplexe Klassen, die eine Implementierung erschweren, oder Subklassen, denen einzelne Eigenschaften ihrer Superklassen fehlen. Die SOLID Principles sorgen dafür, dass es möglichst wenig Reparatur von Code durch Refactoring braucht.
Die Vorteile einer Anwendung der SOLID Principles umfassen:
- Klar, sauber und schön: Software und Codes sind leichter verständlich, nachvollziehbarer, effektiver und einfach schöner.
- Leicht zu pflegen: Durch die übersichtliche, klare Struktur lässt sich sowohl neuer als auch historisch gewachsener Code selbst mit mehreren Beteiligten einfacher warten und pflegen.
- Anpassbar, erweiterbar, wiederverwendbar: Durch die bessere Lesbarkeit, geringere Komplexitäten und Verantwortungen sowie reduzierte Abhängigkeiten von Klassen lässt sich Code besser bearbeiten, anpassen, durch Schnittstellen erweitern und flexibel wiederverwenden.
- Weniger fehleranfällig: Durch sauberen Code mit einfacher Struktur wirken sich Änderungen an einem Teil nicht ungewollt auf andere Bereiche oder Funktionen aus.
- Sicherer und zuverlässiger: Durch weniger oder gar keine Schwachstellen, Inkompatibilitäten oder Fehler verbessert sich die Funktionalität und Zuverlässigkeit von Systemen und somit auch die Sicherheit.
Die SOLID Principles im Überblick
Die SOLID Principles zählen zu den goldenen Regeln für gutes Programmieren und sollten daher allen, die sich mit objektbasierter Programmierung beschäftigen, geläufig sein. Wir stellen die fünf Prinzipien im Detail vor.
SRP: Single Responsibility Principle (Prinzip der eindeutigen Verantwortlichkeit)
Die Original-Definition gemäß Robert C. Martin in „Agile Software Development: Principles, Patterns and Practices“ besagt:
„Es sollte nie mehr als einen Grund dafür geben, eine Klasse zu ändern.“
Das SRP besagt, dass für jede Klasse in der OOP nur eine Verantwortung gelten soll. Für Änderungen an der Klasse darf es somit nur einen Grund geben. Entgegen häufigen Missverständnissen bedeutet das nicht, dass eine Klasse oder jedes Modul nur genau eine Aufgabe haben darf. Sie sollte jedoch nur für spezifische Aufgaben die Verantwortung tragen, die sich möglichst nicht mit anderen Bereichen überschneiden.
Ein Überschneiden von Verantwortlichkeiten, zum Beispiel durch das Bereitstellen von Funktionen für unterschiedliche Geschäftsbereiche, kann im Fall von Änderungen an einem Bereich, Auswirkungen auf die Funktionalität der Klasse haben. Durch mehr als eine Verantwortung und zu viele Abhängigkeiten kann eine Änderung in einem Bereich mehrere weitere Änderungen oder Code-Fehler nach sich ziehen.
Das SRP hat somit zum Ziel, kohärente Module zu entwickeln, die für eine klar verständliche Aufgabe oder klar definierte Objekte zuständig sind. Durch die klar strukturierte Form mit reduzierten Abhängigkeiten und entkoppelten Implementierungen lassen sich spätere Änderungen und Modulationen einfacher, schneller und ohne Komplikationen durchführen.
Klassen, auch als Objekttypen bekannt, bilden das zentrale Element in der objektorientierten Programmierung (OOP). Sie lassen sich als Bauplan mit Attributen für reale, ähnliche und zu konstruierende Objekte in Software-Objekten verstehen. Klassen, auch als Module bekannt, werden daher oft mit Dateitypen verglichen.
OCP: Open Closed Principle (Prinzip der Offen- und Verschlossenheit)
Gemäß Bertrand Meyer und Robert C. Martin in „Object Oriented Software Construction“ besagt das OCP:
„Software-Entitäten (Klassen, Module, Funktionen etc.) sollten sowohl offen für Erweiterungen als auch geschlossen für Modifikationen sein.“
Das OCP sorgt dafür, dass Sie eine Software nicht im Kern umschreiben müssen, um Änderungen zu implementieren. Sind tiefgreifende Code-Modulationen erforderlich, entstehen Risiken für subtile Fehler und Code Smell. Ein gut strukturierter Code sollte sich mit Schnittstellen (Interfaces) versehen lassen, über die er sich um zusätzliche Funktionen erweitern lässt. Das Stichwort lautet hier Klassen-Vererbung.
Neue Features und Erweiterungen mit übersichtlichen neuen Funktionen und Methoden, die es zu implementieren gilt, lassen sich einfach per Schnittstelle an eine Superklasse in Form von Subklassen andocken. Auf diese Weise müssen Sie nicht am geschriebenen, stabilen Code „herumpfuschen“. Das erleichtert die Wartung und Pflege von Programmen und gestaltet die Wiederverwendbarkeit stabiler Code-Elemente durch Schnittstellen deutlich effizienter.
LSP: Liskovsches Substitutionsprinzip (Ersetzbarkeitsprinzip)
Gemäß Barbara H. Liskov und Jeannette M. Wing in „Behavioral Subtyping Using Invariants and Constraints“ besagt das LSP:
„Sei q (x) eine Eigenschaft des Objekts x vom Typ T, dann sollte q (y) für alle Objekte des Typs S gelten, wobei S ein Subtyp von T ist.“
Was auf den ersten Blick kryptisch klingt, lässt sich leicht verstehen: Verknüpfte oder erweiterte Subklassen müssen wie ihre Superklassen oder Basisklassen funktionieren. Das bedeutet, dass jede Subklasse mittels Vererbung die Eigenschaften ihrer jeweiligen Superklasse erhalten muss und diese Eigenschaften in der Subklasse keine Änderungen erfahren dürfen. Sie müssen vom Prinzip her ersetzbar sein – daher Ersetzbarkeitsprinzip. Superklassen hingegen dürfen sich durch Änderungen modifizieren lassen.
Zur Veranschaulichung dient das klassische Beispiel von Robert C. Martin vom Rechteck und Quadrat. Im Geometrieunterricht gilt die Erkenntnis: Jedes Quadrat ist ein Rechteck, aber nicht jedes Rechteck ist ein Quadrat. Ein Quadrat teilt sich vielmehr die Eigenschaft „Rechtwinklige Seiten“ mit der Oberklasse der Rechtecke, weist jedoch die zusätzliche Eigenschaft „Gleich lange Seiten“ auf.
Anders beim Programmieren: Hier führt die Annahme, dass ähnliche oder scheinbar identische Klassen sich aufeinander beziehen oder voneinander abhängig sind, zu Fehlern, Missverständnissen und unklarem Code. Aus diesem Grund ist beim Programmieren die Klasse „Rechteck“ kein Quadrat und die Klasse „Quadrat“ kein Rechteck. Beide werden „entkoppelt“ und getrennt implementiert. Ohne integrierte Verbindung zwischen den Klassen kann ein Missverständnis nicht zu klassenübergreifenden Fehlern führen. Das erhöht die Sicherheit und Stabilität beim Austausch von Implementierungen in Subklassen oder Superklassen ohne Auswirkungen.
ISP: Interface Segregation Principle (Schnittstellenaufteilungsprinzip)
Das ISP definiert sich nach Robert C. Martin in „The Interface Segregation Principle“:
„Clients sollten nicht dazu gezwungen werden, von Interfaces abzuhängen, die sie nicht verwenden.“
Das ISP besagt, dass Anwenderinnen und Anwender nicht gezwungen sein sollen, Schnittstellen (Interfaces) zu verwenden, die sie nicht brauchen. In anderen Worten: Um Clients die Funktionen von bestimmten Klassen zur Verfügung zu stellen, werden neue, kleinere Schnittstellen auf konkrete Anforderungen zugeschnitten. So lässt sich verhindern, dass Interfaces zu groß werden und starke Abhängigkeiten zwischen Klassen entstehen. Der Vorteil: Software mit entkoppelten Klassen und mehreren kleinen, auf konkrete Forderungen zugeschnittenen Interfaces lässt sich leichter pflegen.
DIP: Dependency Inversion Principle (Abhängigkeits-Umkehr-Prinzip)
Das fünfte und letzte der SOLID-Principles lautet nach Robert C. Martin in „The Dependency Inversion Principle“ wie folgt:
„A. Module hoher Ebenen sollten nicht von Modulen niedriger Ebenen abhängen. Beide sollten von Abstraktionen abhängen. B. Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.“
Das DIP sorgt dafür, dass konkrete Funktionen und Abhängigkeiten in Quellcode-Ebenen auf abstrakten Schnittstellen und nicht aufeinander aufbauen. Zur Erklärung: Software-Architekturen lassen sich grob in höhere Benutzerebenen und niedrigere oder tiefere abstrakte Ebenen unterscheiden. Rein logisch lässt sich annehmen, dass die abstrakte Basis das Verhalten der höheren Ebenen bestimmt. Das DIP sieht hierin jedoch eine Fehleranfälligkeit, da es zu Abhängigkeiten der höheren Ebenen von tieferen Ebenen kommt.
Statt höhere Ebenen an niedrigere Ebenen zu koppeln, sollten Klassen in hohen und niedrigen Ebenen von abstrakten, zwischengeschalteten Schnittstellen abhängen. Die Schnittstellen rufen Funktionalitäten, die in höheren Ebenen benötigt werden, von unteren Ebenen ab und stellen sie bereit. Auf diese Weise lässt sich eine „Bottom-to-Top-Hierarchie“ aus Abhängigkeiten vermeiden, die mit der Zeit zu Fehlern im Code führen kann. Das erleichtert die Wiederverwendbarkeit von Modulen und ermöglicht Änderungen an unteren Klassen ohne Auswirkungen auf höhere Ebenen.
- Flexibel: Hosting, das jedem Website-Traffic standhält
- Verlässlich: Inklusive 24/7-Support und persönlicher Beratung
- Sicher: Kostenloses SSL-Zertifikat, DDoS-Schutz und Backups
Was passiert bei Nichteinhaltung der SOLID Principles?
Ein schöner, leicht lesbarer Code, der Pflege so leicht wie möglich macht, sollte das Ziel jeder Software-Entwicklung sein. Geraten Richtlinien wie die SOLID Principles jedoch in Vergessenheit, altert Code durch Schwachstellen, Redundanzen, angehäufte Fehler und zu viele Abhängigkeiten sehr schlecht. Im schlimmsten Fall wird der Code mit zunehmender Zeit unbrauchbar. Das stellt vor allem in der agilen Software-Entwicklung ein großes Problem dar, denn hier arbeiten meist viele Beteiligte an einem komplexen Code.
Zu den Folgen, die unsauberer Code oder schlechte Codepflege mit sich bringen, zählen:
- Code Smell: Historisch gewachsene Schwachstellen aus unsauberem Code, bekannt als Code Smell oder „müffelnder Code“, führen zu Funktionsfehlern und inkompatiblen Programmen.
- Code Rot: Kommt es nicht zur Wartung oder Reparatur durch Refactoring oder ein kostspieliges Code Review, kann Code im übertragenen Sinne „verfaulen“ und seine Funktionalität vollständig einbüßen. Eine andere Bezeichnung für unlesbaren, verworrenen Code ist Spaghetti-Code.
- Sicherheitslücken: Nicht nur Ausfälle, komplizierte Pflege oder Inkompatibilitäten sind eine Folge, sondern auch Sicherheitslücken, die Schadsoftware die Chance auf Exploits sowie Zero-Day-Exploits eröffnen.
Wer hat die SOLID Principles entwickelt?
Der Ursprung der SOLID Principles liegt in mehreren Prinzipien, die erstmals von Robert C. Martin („Uncle Bob“), einem der Initiatoren der agilen Programmierung, in seinem Essay „Design Principles and Design Patterns“ im Jahr 2000 eingeführt wurden. Die Gruppe der fünf SOLID Principles wurde von Robert C. Martin, Bertrand Meyer und Barbara Liskov mitgeprägt. Das eingängige Akronym bestehend aus den fünf Anfangsbuchstaben der Prinzipien wiederum wurde von Michael Feathers propagiert, indem er fünf der wesentlichen Prinzipien neu arrangierte.
Welche ähnlichen Programmierprinzipien gibt es?
In der Softwareentwicklung stehen Prinzipien für allgemeine oder sehr konkrete Leitlinien und Handlungsempfehlungen ähnlich wie die SOLID Principles, die ein Set aus Prinzipien für das Paradigma der objektorientierten Programmierung bieten. Weitere Programmierprinzipien für Clean Code sind etwa:
- DRY-Prinzip (Don’t repeat yourself) für Funktionen mit einer einzigen, eindeutigen Darstellung
- KISS-Prinzip (Keep it simple, stupid) für einen möglichst einfach konstruierten Code