John Ousterhout, Erfinder von Tcl und seit 2008 Professor am Department of Computer Science der Stanford University, sieht Softwaredesign als zentrale Kompetenz, die großartige von normalen Programmierern und Programmiererinnen unterscheidet - eine Kompetenz, die aber kaum gelehrt und vermittelt wird. In seinem Buch gibt er persönliche Erfahrungen weiter, die er durch das Schreiben und Lesen von mehr als 250.000 Zeilen Code gewonnen hat. Seine nahezu philosophischen Überlegungen, bewährten Prinzipien und eingängigen Codebeispiele sind Ergebnis eines Kurses, den er an der Stanford University mehrfach gehalten hat.
Als Data Engineer mit Quereinstiegs-Hintergrund versuche ich, durch praxisnahe Bücher regelmäßig mein Wissen und Verständnis zu theoretischen Fundamentals der Software-Entwicklung zu vertiefen. In diesem Sinne hat mir der „lehrbuchartige“ Stil mit vielen konkreten Beispielen auf jeden Fall geholfen, die Konzepte zu verstehen und zu verinnerlichen.
Ich denke, dass es mir dabei auch geholfen hat, die deutsche O’Reilly-Fassung des sehr erfolgreichen englischen Originals A Philosophy of Software Design zu lesen. Außerdem fand ich, dass dieses Buch eine gute ergänzende Perspektive zum reinen Clean-Code-Ansatz liefert und diesen einordnet.
Der Bezug zu den Clean-Code-Ideen von Robert Cecil Martin beginnt schon beim Verständnis von „guter“ bzw. „cleaner“ Software. Während Martin den Schwerpunkt der Definition auf möglichst gut lesbaren, erweiterbaren und wartbaren Code setzt, legt Ousterhout seinen Schwerpunkt auf die Reduzierung von Komplexität und kognitiver Last.
Hier gibt es zwar deutliche Schnittmengen, allerdings sind die Konzepte nicht identisch und widersprechen sich in einigen Bereichen. So kann man sagen, dass Ousterhout Software eher auf einer systemischen Makro-Ebene betrachtet, obwohl er durchaus daran interessiert ist, wie Entscheidungen auf Mikro-Ebene auf das Gesamtsystem wirken. Martin hingegen richtet seinen Fokus stärker auf die Mikro-Ebene von Code im „täglichen“ Entwickleralltag.
Inhaltlich wird Komplexität als „all das, was mit der Struktur eines Softwaresystems in Zusammenhang steht und dieses schwer verständlich und schwer anpassbar macht“ definiert. Diese Komplexität führt zu drei Kernsymptomen:
1. Ausweitung von Änderungen – wenn man für eine einfache Änderung viele Stellen anpassen muss bzw. es viele Abhängigkeiten gibt.
2. Erhöhte kognitive Last – also die Tendenz, zunehmend mehr Informationen über Verhalten, Abhängigkeiten und Implementationsdetails im Kopf behalten zu müssen, um sinnvoll mit dem Code zu arbeiten.
3. Unbekannte Unbekannte – also Dinge, die man wissen müsste, um sinnvolle Änderungen und Erweiterungen vorzunehmen, die aber aufgrund fehlender Informationen, Abhängigkeiten oder Side-Effects verborgen bleiben.
Daraus folgend sieht Ousterhout die Ursachen für Komplexität auf hoher Ebene vor allem in Unklarheiten und zu vielen Abhängigkeiten.
Im Buch werden verschiedene Methoden und Ansätze vorgestellt, um der Komplexität durch gutes Design schon von Anfang an „wegzudesignen“. Ein erster Vorschlag ist ein strategischer bzw. defensiver Programmierstil: sich beim Entwerfen und Implementieren von Softwaresystemen Zeit zu lassen und technische Schulden möglichst zu vermeiden, indem man keine Abkürzungen nimmt – im Gegensatz zu einem taktischen/aggressiven Programmierstil wie Facebooks „Move fast and break things“.
Ein zentraler Punkt bei Ousterhout ist die Bedeutung von Schnittstellen im Code, was er anhand seines Modulbegriffs erläutert. Ein Modul ist im Kontext des Buches jede funktionale Bündelung von Logik bzw. allgemein die Code-Implementation einer Abstraktion, also eine Funktion, eine Klasse, ein Subsystem oder ein ganzer Service. Ousterhouts Schnittstellenbegriff umfasst sowohl formale als auch informelle Aspekte.
Zu den formalen Aspekten gehören klassische Artefakte wie die Signatur einer Funktion – also Name, Rückgabetyp und Parameter. Bei einer Klasse wären es die Signaturen der öffentlichen Methoden.
Die informellen Aspekte von Schnittstellen umfassen alles, was zum notwendigen Wissen für die korrekte Nutzung eines Moduls gehört: das High-Level-Verhalten der Implementation, Doc-Strings und Kommentare sowie beispielsweise auch die Exceptions, die geworfen werden, was sie bedeuten und wie mit ihnen umzugehen ist.
Ein sehr wichtiger Vorschlag Ousterhouts ist, dass Module tief sein sollen – also möglichst viel Funktionalität bereitstellen sollen bei gleichzeitig möglichst kleinen und einfachen Schnittstellen. Ein „flaches“ Modul hat im Vergleich dazu eine relativ komplexe Schnittstelle gemessen an der Funktionalität, die es bereitstellt.
Eine Methode, um tiefe Module zu schaffen, ist Information Hiding, also das Verstecken von Komplexität und Details, die für die Nutzung eines Moduls irrelevant sind. Tiefe Module können laut Ousterhout auch dadurch entstehen, dass man sie möglichst vielseitig gestaltet – also möglichst viele Fälle abdecken lässt, während die Schnittstelle vergleichsweise einfach bleibt. So vermeidet man viele flache Spezialfall-Module.
Eine weitere Empfehlung ist es, unvermeidbare Komplexität „nach unten“ zu ziehen. Bei einer Klasse bedeutet das beispielsweise, komplexe Logiken in private Methoden oder tiefere Module zu kapseln, die wiederum von einfacheren öffentlichen Methoden aufgerufen werden, die näher an der Anwenderin oder dem Anwender sind.
Mit seiner These, dass eine einfache Schnittstelle wichtiger ist als eine einfache Implementierung, widerspricht Ousterhout an einigen Stellen den Empfehlungen aus Martins Clean Code, etwa bezüglich der sehr kurzen Funktionen.
In der zweiten Hälfte des Buches folgen weniger tiefgreifende, aber praxisnahe Empfehlungen: dass man ruhig zweimal designen soll, um zu einem möglichst guten Design zu kommen; dass gute Doc-Strings und Kommentare beim Entwickeln und Managen von Komplexität helfen; dass gute Namen und Konsistenz im Stil und Aufbau von Code wichtig sind. Zusätzlich betont Ousterhout, dass sowohl beim Entwerfen von Abstraktionen als auch bei deren Implementierung die Fähigkeit zentral ist, Wichtiges von Unwichtigem zu unterscheiden – das Wichtige sichtbar zu machen und das Unwichtige zu verstecken.
Abschließend habe ich das Buch als sehr wertvoll empfunden und kann es jedem empfehlen, der mehr über die Ursachen und Auswirkungen von Komplexität in Software sowie über Methoden im Software-Design erfahren möchte, die helfen, Komplexität zu reduzieren.