

Discover more from INUVEON
Komplexität vermeiden
Die Digitalisierung ist inzwischen in nahezu jedem Unternehmen angekommen. Deshalb ist es auch für Unternehmer essenziell, sich mit dem Thema nachhaltiger Softwareentwicklung auseinanderzusetzen.
Heute wird es möglicherweise wieder etwas philosophischer. Ich möchte diesen Blog/ Newsletter sehr gerne dazu nutzen, um meine Gedanken und Erfahrungen über die alltäglichen Problemstellungen in der Softwareentwicklung mit euch zu teilen. Diesmal geht es um ein Thema, über das ich schon seit vielen Jahren immer wieder spreche: nämlich um Komplexität. Komplexität und fehlende Zuständigkeiten (Single Responsiplity Principle - kurz SRP) führen in der Realität des Software Developments scheinbar immerfort zum selben Problem: der Code wird unwartbar und Änderungen stellen ein Risiko dar. Da hat sich in den letzten Jahrzehnten nicht wirklich viel geändert. Das heißt im Umkehrschluss, wir sollten Komplexität so gut wie möglich vermeiden. Es ist also ein guter Zeitpunkt, wieder einmal über Komplexität zu sprechen.
Die größte Herausforderung in der Entwicklung von Software scheint stets, komplexe Dinge so einfach wie möglich abzubilden. Dafür müssen wir ständig hinterfragen, was wir tun und uns kontinuierlich verbessern. Oft sehe ich, dass sich Teams mit Projektbeginn zusammenfinden, einen Lösungsweg besprechen und skizzieren, den sie Architektur nennen. Allerdings handelt es sich nicht selten um ein starres Konstrukt, welches vor allem auf allerlei Annahmen und einer Sammlung persönlich bevorzugter Technologien und Frameworks beruht. Dieser einmal eingeschlagene Weg wird nur ungern verlassen. Er gilt häufig als unantastbar. Leider hat das alles nichts mit einer belastbaren Architektur oder Strategie zu tun.
Ich glaube, dass viele Entwicklerteams zielführendere Strategien etablieren und dafür weniger an starren Festlegungen, die auf Annahmen oder Unternehmensrichtlinien beruhen, festhalten sollten. Entscheidend erscheinen mir auch mehr Flexibilität sowie der Wille und die Bereitschaft, Entscheidungen wieder über Bord zu werfen. Und zwar zu jeder Zeit. Eine Entscheidung sollte immer dann hinterfragt werden, wenn neue Erkenntnisse vorliegen. Wir sollten uns bewusst werden, dass wir zu Beginn eines Projektes niemals alles wissen können. Und wir dürfen dies nicht als etwas Schlimmes oder Dramatisches sehen. Es ist eine natürliche Sache. Der zweite Indikator, Entscheidungen zu überdenken ist, wenn die Implementierung zu komplex wird. Nämlich dann beispielsweise, wenn zu viele Workarounds notwendig werden oder Deployments zu lange dauern und riskant werden.
Die Lösung besteht darin, permanent ein Auge auf die Komplexität zu werfen. Ich meine damit, dass es wichtig ist, ständig zu reflektieren und bereits getroffene Entscheidungen bei Bedarf zu revidieren. Ich habe es oft erlebt, dass sehr lange an eine Entscheidung oder eine bestimmte Technologie geklammert wird, obwohl deren Verwendung mit dem aktuellen Kenntnisstand nicht länger zielführend ist.
Manchmal habe ich den Eindruck, dass der Drang zur Pflichterfüllung größer ist als die Identifikation mit der Idee oder dem Projekt. Oftmals geht es unglücklicherweise mehr darum, die Sprintziele zu erreichen und eine schöne Kurve im Burn-Down Chart zu haben, als qualitativ hochwertige, robust und wartbare Software zu erzeugen. Die Anzahl der Features ist leider der einzig gültige Indikator für den Erfolg eines Projektes aus Sicht des Managements. Oft wird dann von technischen Schulden gesprochen, die irgendwann mal beglichen werden sollten. Das passiert in der Realität selten, weil es oftmals gar nicht ohne Risiken möglich ist, sich scheinbar keine Notwendigkeit ergibt, weil es ja offensichtlich auch mit den ganzen Workarounds funktioniert und dann auch schon wieder viele neue Features in der Pipeline sind.
Ich möchte deshalb gerne von einem notwendigen Kulturwandel sprechen, wie Software entwickelt werden sollte, um nicht immer wieder in dasselbe Problem zu laufen. Viele Projekte werden mit guten Vorsätzen gestartet: “Diesmal machen wir es besser.”
Oftmals geht es dann darum, die perfekte Architektur zu entwerfen, in der Probleme gelöst werden sollen, die noch keiner wirklich kennt. Die meisten Szenarien sind Annahmen, die basierend auf Erfahrungen getroffen werden. Das ist nicht grundsätzlich verwerflich, weil es absolut richtig ist zu reflektieren und aus den Fehlern vorherigen Projekt zu lernen. An dieser Stelle besteht allerdings schnell die Gefahr des Over-Engineerings. Es gut zu meinen, heißt dann nicht es gut zu machen. Ich habe das selbst praktiziert, weil ich es diesmal perfekt wollte und um jeden Preis keinen Fehler wiederholen wollte. Im Ergebnis entstand unnütze Komplexität und eben keine leichtgewichtige und flexible Architektur.
Ich meine, wir müssen ein gesundes Gefühl für sinnvolle Vorüberlegungen und Auseinandersetzung mit den Problemstellungen und der zügigen Implementierung finden. Wir sollten uns also von dem Gedanken lösen, dass wir uns wasserfallartig zuerst die vermeintlich perfekte Architektur ausdenken können, um danach Technologien auszuwählen, von denen wir erwarten, sie würden uns noch unbekannte Probleme in der Zukunft lösen und dann nur noch auf Teufel komm raus Features implementieren müssen.
Ich breche es auf eine allgemeine Formel herunter, um die Problemstellung besser zu veranschaulichen.
1) Komplexer Code ist schwer verständlich. (Z.B. Klassen, die sich um mehr als eine Sache kümmern)
2) Zuviele Abhängigkeiten machen Änderungen riskant und zeitaufwendig.
Beides führt unweigerlich zu schlechtem Code. Das wollen wir vermeiden.
Ich möchte an dieser Stelle an die SOLID Prinzipien aus der objektorientierten Softwareentwicklung erinnern. Sie sind eine essenzielle Möglichkeit, um sauberen, wartbaren Code zu produzieren. Daneben sind zudem vor allem strategische Methoden aus dem Domain-Driven Design wie Strategic und Tactical Design wichtig. Unabhängig davon aber auch vor allem generell im Team eine Kultur der Flexibilität zu erschaffen und es nicht als tragisch zu empfinden, wenn Komponenten wieder über Bord geworfen werden, weil man durch neue Erkenntnisse zu neuen Lösungsansätzen gelangt ist.
Die SOLID-Prinzipien wurden von durch Rober C. Martin in seinem Buch “Design Patterns” von 1994 definiert.
Falls du dich noch nicht mit diesem grundlegenden Prinzip der objektorientierten Entwicklung beschäftigt haben solltest, dann solltest du dies unbedingt tun.
Für was steht SOLID eigentlich?
S - Single Responsibility
O - Open/Closed
L - Liskovsches Substitutions Prinzip
I - Interface Segregation
D - Dependency Inversion
Wenn wir von Single Responsibility sprechen, ist damit gemeint, dass jedes Ding (z.B. eine Klasse) immer exakt nur eine einzige Aufgabe erfüllen sollte.
A class should have one, and only one reason to change.
Das ist auch ein Grund, warum ich kein Freund von statischen Sammelklassen wie Utils oder Helpers bin. Derartiges fühlt sich nach fehlender Zuordnung und schnell mal gemacht an, aber weniger nach Strategie und Notwendigkeit. Ich hatte das hier bereits näher ausgeführt.
Ok, aber wie ist es nun möglich, Komplexität zu beherrschen? Ich denke vor allem durch die Bereitschaft ständig zu reflektieren und zu handeln, wenn Klassen, Komponenten oder Services zu mächtig werden. Es ist nicht tragisch, einfach zu starten, um zu sehen, wie sich die Dinge entwickeln, um dann zum richtigen Zeitpunkt zu refaktorisieren und damit einen Service beispielweise in zwei oder mehrere aufzusplitten. Was ich damit meine ist, wir sollten es als normal empfinden, die Dinge wachsen zu lassen und wieder neu zu schneiden. Softwareentwicklung ist nichts Statisches. Es ist ein Prozess. Vor allem ein sehr kreativer. Deshalb scheint mir der stetige Austausch zwischen den Team-Mitgliedern essenziell. Reflektion sollte kein formeller Prozess sein, sondern sich aus der Notwendigkeit und dem Wunsch ableiten, das bestmögliche Produkt abliefern zu wollen.
Cheers!
Nachtrag: Inzwischen habe ich dieses Thema mit Ralf Westphal in einem Webcast ausführlich besprochen.
Komplexität vermeiden
Das ist wahr: Komplexität ist der Feind der Softwareentwicklung.
> der Code wird unwartbar und Änderungen stellen ein Risiko dar
Aber für mich ist es nicht mit SOLID oder DDD getan. We need to dig deeper. "Tackling complexity in the heart of software" ist nur sekundär ein Problem von Prinzipien. Die sind wichtig und SOLID ist gleichzeitig überbewertet und missverstanden ;-) Dennoch bleibt die Frage: Wenn denn Prinzipien und Praktiken so lange schon auf dem Tisch liegen, warum ist Komplexität immer noch ein solches Problem?
Eric Evans hätte sagen sollen "Tackling complexity of software in the heart of software development". Denn Komplexität wurzelt im Mindset und damit in den Gewohnheiten und Reflexen.
"Es gibt nichts Gutes, außer man tut es" sagt der Volksmund. Genau: Es muss getan werden. Das fehlt. Warum? Warum wird das Gute nicht getan? Warum werden Prinzipien nicht angewandt? Warum erfüllt der Architekt nicht seine Hauptaufgabe: design for reversibility of decisions?
"It's the values, stupid!" :-D Es liegt am Wertesystem der Softwareentwicklung. Oder wenn nicht der Softwareentwicklung, dann derer, die die Softwareentwicklung antreiben.
Das Wertesystem ist aber eines von Süchtigen. Da nützen Kenntnisse von SOLID etc. nichts. Der Alkoholiker, der Raucher weiß auch, was besser wäre. Aber dieses Wissen entfaltet keine Kraft. Aufzuklären geht im Grunde am Wesentlichen vorbei.
Erst wenn die Sucht nicht mehr geleugnet wird, erst wenn aus dem Herzen Wunsch nach Erlösung daraus aufsteigt, dann ist die Softwareentwicklung bereit für Prinzipien und Praktiken. Hier eine ausführliche Auseinandersetzung damit: https://ralfwestphal.substack.com/p/there-is-no-such-thing-as-technical-debt-44cf4ab3ce91?s=w