Eins meiner Architekturprinzipien beim Entwurf von Softwaresystemen lautet: Nur klare, einfache, robuste Technologien einsetzen, keine "Rocket Science". Doch woher weiß ich, ob etwas Rocket Science enthält oder ob ich lediglich zu dumm bin, es richtig zu nutzen?
Definition: Was ist Rocket Science?
Ich bezeichne damit eine Technologie, die ich nicht beherrsche. Sie braucht mehr als 30% meiner Gehirnkapazität, und zwar jedesmal, wenn ich damit arbeite. Es gibt Frameworks oder Systeme, die meistens korrekt arbeiten, doch im Ernstfall, wenn es zufällig gerade Dienstag ist und beim User im Zimmer ein Hund anwesend ist, dann funktionieren sie nicht oder produzieren unerwartete Ergebnisse.
Das liegt meist an für mich nicht erkennbaren Ursache-Wirkungs-Beziehungen. Als Softwerker habe ich eine ausgeprägte Input/Output-Denkweise: Bei welchem Input produziert eine Software welchen Output? Schwierig wird es für mich immer dann, wenn der Output nicht nur vom Input, sondern auch noch von einem Zustand oder einer Umgebungsbedingung abhängt, besonders, wenn ich diese nicht kenne.
Beispiele
Ein paar Beispiele. Raten Sie mit!
Classpath-abhängige Features
Im manchen Frameworks gibt es Klassen, die abfragen, ob auf dem Classpath noch bestimmte, weitere Klassen vorhanden sind und dann automatisch sofort deren Initialisierungssequenzen aufrufen. Äußerst schwer zu entdecken, wenn man nicht gerade Experte in diesem Framework ist. Noch schwerer ist es, herauszubekommen, welche Konfigurationsschalter man anwenden muss, um dieses Verhalten abzuschalten.
War das jetzt Rocket Science oder Inkompetenz?
Layout von Boxen in CSS
Wenn man versucht, schöne Layouts für das Frontend zu erstellen, kommt man um die Frage, anhand von welchen Kriterien etwas auf der Seite positioniert werden soll, nicht herum. Bedingt durch die lange Historie, die CSS schon hat, gibt es unzählige Möglichkeiten, das Layout zu steuern.
Da wird so manche Anfängererwartung enttäuscht. Beispiel: Der CSS-Anfänger hat herausgefunden, dass er Breite und Höhe von Elementen in Prozent und die Justierung über float
angeben kann:
<html>
<style>
body {
display: block;
border: solid 1px black;
}
.left {
width: 50%;
height: 100%;
color: white;
background-color: purple;
float: left;
}
.right {
}
</style>
<body>
<div class="left">hello</div>
<div class="right">world</div>
</body>
</html>
Er freut sich über das nette Ergebnis:
Danach wird er mutig. Er möchte moderne Frameworks verwenden, die mit Flexbox-Layout anstelle von Block-Layout arbeiten. Um Flexbox erst einmal kennenzulernen, ersetzt er in Zeile 4 versuchsweise display: block
durch display: flex
. Wie erklärt man ihm, dass die Zeile 9 mit height: 100%
jetzt nicht mehr wirkt wie erwartet?
Sehr lange denkt er über dieses Ergebnis nach:
Wenn er das jetzt nicht versteht, ist dann CSS ein Fall von Rocket Science oder ist er lediglich inkompetent?
Dieser Anfänger (ich kenne ihn persönlich, er schreibt nämlich dieses Devlog) suchte den Fehler zunächst bei sich und konnte nach einer Weile schon ganz gut mit Flexbox umgehen. Doch die Fehlschläge wurden immer komplizierter, je anspruchsvoller seine Layouts wurden. Es gab stets einen Fall, den er nicht beherrschte und bei dem er die Verbindung von Ursache zu Wirkung nicht erkennen konnte.
Er fing an, fertige meisterhafte Beispiele, die er funktionieren sah, in seinem Kontext zu testen. Auch sie schlugen fehl, obwohl sie im Original-Kontext funktionierten (der Grund waren Vererbung und Spezifizität in CSS).
Also hielt er CSS Flexbox nach einer Weile für klare Rocket Science. Allerdings eine unvermeidbare.
Scopes bei Dependency Injection
Dependency Injection ist ja an sich eine prima Sache. Es muss mich nicht interessieren, woher eine konkrete Klasse kommt, gegen deren Interface ich programmieren möchte. Die Zahl der Instanzen dieser Klassen interessiert mich allerdings schon.
Beispiel: In meiner Webanwendung SaaS für Trainer möchte ich, dass pro HTTP-Request ein und nur ein eigener EntityManager
angelegt wird. Die Repository-Instanzen, die im selben Aufruf des Dependency Injectors angelegt werden, sollen mit dem EntityManager
dieses einen Requests arbeiten.
Es hat bei früheren Anwendungen, als ich noch Spring einsetzte, sehr lange gedauert bis ich verstanden hatte, warum das funktioniert. Spring Boot verbirgt dieses Detail komplett vor mir (was super ist, so lange alles funktioniert, aber wehe, wenn nicht!). Ich halte Spring deshalb für Rocket Science, weil ich so viel Kompetenz, wie ich dafür bräuchte, gar nicht aufbauen will!
Bei Dagger, den ich nun einsetze, verstehe ich wesentlich besser, was passiert. Ich muss Dagger von Hand sagen, er soll ein Scope
anlegen, das ich RequestScoped
nenne. Die Service- und Repository-Klassen annotiere ich dann mit @RequestScoped
und siehe da: es wird nur eine Instanz pro Request angelegt. Verschiedene Requests bekommen verschiedene Instanzen. Ich kann sogar den von Dagger dafür generierten Code lesen und verstehen.
Architekturprinzip
Also, noch einmal mein Prinzip:
- keine Rocket Science im System
- wenn unvermeidbar, dann nur mit definiertem Ausweg einsetzen
Ursachen für Rocket Science
Es gibt drei mögliche Fälle, warum Rocket Science ins Spiel kommt:
- Die verwendete Technologie ist tatsächlich zu kompliziert gebaut (Spring)
- Sie ist lediglich zu kompliziert beschrieben (CSS Flexbox)
- Ich bin einfach nur inkompetent (JavaScript und ich, vor ca. fünf Jahren)
Auswirkungen
Egal, welche Ursache, sie wirkt sich gleich aus:
- reduzierte Wartbarkeit der Software
- erhöhter Zeitaufwand beim Ändern
- Folgefehler, wenn ich etwas ändere
Auswege
- Vermeidung: Ich könnte einfach die Finger davon lassen (Spring)
- Kapselung: Ich kann ein Framework nehmen, das die Rocket Science hinter guten, nicht leckenden Abstraktionen verbirgt (Hibernate, naja, manchmal leckt es schon etwas...)
- Spezialistentum: Jemand anderes, dem das Spaß macht, soll sich darum kümmern (z.B. gibt es in der Open Source-Gemeinde erstaunliche Leute!)
- Auflösung durch Lernen: Ich knie mich halt rein und beiße mich durch (siehe meinen Open Source Flexbox Explorer)
Das Problem des Lernens
Als ich über (In-)Kompetenz länger nachdachte, fiel mir auf, dass es beim Menschen zwei Arten des Lernens gibt:
- en bloc
- inkrementell
Manche Dinge kann man sich en bloc "reinziehen". Wenn man sie dann noch eine Weile praktiziert, klappt das.
Andere Dinge lernt man nur inkrementell. Ich habe z.B. drei Jahre gebraucht, um Single Page Apps mit ReactJS sauber funktionierend erstellen zu können. Heute fällt es mir leicht, neue Konzepte (z.B. React Hooks) schnell zu verstehen und aufzugreifen und existierenden Code, der auf alten Konzepten basiert, umzustellen. Das schaffe ich aber nur, weil das Lernen in kleinen Portionen auf mich zukommt.
Würde ich versuchen, React einem kompletten Newbie, der ich die Jahre vorher war, zu erklären, hätte ich Mühe (und er wahrscheinlich auch).
Das Problem ist nun, dass ausgefeilte Technologien (wie CSS) von inkrementell lernenden MeisterInnen erschaffen werden, denen das leichtfällt. Diese Meister sind jedoch in der Regel nicht fähig, jemandem en bloc zu erklären, was er wissen muss. Ganz selten treffe ich jemanden, der das trotzdem kann (was für ein Glücksfall – diesmal!).
Das ist der Grund, warum die Beherrschung von Technologien so schwierig ist und sehr leicht der Eindruck von Rocket Science entstehen kann. Und wirklich schwer ist es, Rocket Science von der eigenen Inkompetenz zu unterscheiden.
Was meinen Sie?
Schreiben Sie mir Feedback in die Kommentar-Box weiter unten. Es interessiert mich, welche Erfahrungen Sie mit diesem Thema gemacht haben.
Titelfoto
Danke an die Leute von SpaceX, die ihre Falcon so schön fotografiert haben: