„Schaut mal, wie clever ich das gelöst habe!“ – Warum nicht jeder Lösungsansatz in der Softwareentwicklung clever ist

Softwareentwicklung ist eine Disziplin in einem äußerst komplexen Umfeld – sowohl fachlich, als auch technisch und natürlich auf Ebene der Kommunikation und Teamgefüge. In diesem Umfeld gibt es immer wieder Entwickelnde, die sogenannte „clevere Lösungen“ für Probleme finden. Clever heißt hier nicht, dass ein komplexes Problem simpel gelöst wurde, sondern umgekehrt. Das Problem wurde gelöst mit einem Lösungsansatz, die selbst unnötig komplex ist: „Schaut, was ich tolles entwickelt habe!“.

Wenn Lösungsansätze "zu kompliziert" sind

Entwickelnde möchten zeigen, dass sie gut sind, in dem was sie tun und finden dabei Lösungswege, welche die Anforderung erfüllen und das gewünschte leisten, aber teilweise schwer nachvollziehbar sind. Das führt zu Lösungsansätzen, die zwar funktionieren aber nicht praktikabel sind. Ein weiterer Effekt, der zu komplizierten Lösungsansätzen führt, ist Optimierung auf eine Anforderung hin, die noch gar nicht existiert: „Wenn wir hier noch Option Z brauchen, habe ich das schon vorbereitet, indem ich ein Rule-Framework entwickelt habe!“. Ebenso ist auch die Automatisierung von unkritischen Prozessen mit minimalem oder unkritischem Aufkommen Ursache für zu komplexe Codings. Frei nach dem beliebten Motto: „Why spend 3 hours on a task, when you can spend 3 days automating it.“ Und manchmal gibt es auch sehr einfache Lösungsoptionen, die aber verworfen werden. Stattdessen wird ein (externes) Framework eingesetzt, das interessant ist. Dieses erfüllt die gewünschten Anforderungen, ist aber  Overengineering.

Das ist auf menschlicher Ebene absolut nachvollziehbar. Entwickelnde knobeln gerne an komplexen Problemen und suchen nach cleveren Lösungen. Im Sinne von Lean Management handelt es sich hier aber um Waste –  Verschwendung durch Überbearbeitung. Das ist so, wie wenn ihr einem Kunden Features liefert, die er nicht bestellt hat. Und dafür auch noch Support leisten müsst.

Folgen unnötig komplexer Lösungswege in der Softwareentwicklung

Was bedeutet das für den Rest des Teams, wenn „über das Ziel hinausgeschossen wird“?
Solche Ansätze führen zu einer Vielzahl von Problemen:

  • Komplexe Problemlösungen sind schwer verständlich: Aktuell hat der Entwickelnde ein klares Bild seiner Lösung und versteht diese. Auch mit guter Kommentierung mag dies aber bereits in 4 Wochen nicht mehr der Fall sein, wenn ein Bugfix oder eine Erweiterung notwendig ist.
  • Für andere Mitglieder des Teams ist die Lösung um so schwerer nachvollziehbar, da sie – falls nicht pair oder ensemble-programming genutzt wurde – diese nicht mit erschaffen haben. Diese werden sich noch schwerer tun, die Lösung zu warten.
  • Je komplexer Code ist, desto anfälliger ist er für logische Bugs und vergessende Edge-Cases. Hier entstehen unnötige Risiken und Qualitätsprobleme für den Kunden für eine Funktion, die auch einfacher hätte umgesetzt werden können.
  • Je komplexer der Code ist, desto schwieriger ist er in der Regel automatisiert testbar. Es entstehen weitere Folgeprobleme, denn nur mit Hilfe von automatisierten Tests lässt sich die Korrektheit einer Lösung komfortabel auf Entwicklerebene prüfen.
  • Komplexe Problemlösungen im Sinne einer Optimierung ohne Anforderung, gerne auch „dynamische“ oder „flexible“ Lösung genannt, zielt darauf ab, dass man heute bereits weiß, dass an dieser Stelle genau diese Änderung notwendig werden wird. Das ist aber oft nicht vorhersehbar. So wird Zeit in eine vermeintliche Problemlösung investiert, aber das zugrundeliegende Problem, dass der Entwickelnde antizipiert hat, tritt nur selten ein.
  • Zusätzliche Technologien und Service führen zu neuen technischen Abhängigkeiten und notwendigem KnowHow-Aufbau.
  • Erhöhung des Bus-Faktors – der negativen Auswirkung auf das Team, falls eine Person ausfällt.
  • Komplexe Lösungen arbeiten gegen die Prinzipien von KISS des Clean Code, welchen viele Entwicklerteams folgen.

Aus diesen Problemstellungen resultieren wiederum eine Vielzahl von Folgeproblemen für den Anwendenden oder den Sponsor: In vielerlei Hinsicht entstehen Kosten. Beispielsweise durch sinkende Qualität, d.h. für die Behebung von Bugs aufgrund der gesteigerten Komplexität, für die Zeit, die die Entwickelnden benötigen, um die Lösung zu verstehen oder die der Entwickelnde investiert hat, um die komplexe Lösung zu erstellen. Außerdem entstehen Kosten durch die Verzögerung von neuen Features, die wichtiger wären als eine „unnötige Optimierung“. Darüber hinaus bedeutet das Lernen neuer Technologien für das Team einen Mehraufwand, genauso wie die Wartung des komplexen Lösungsansatzes  um nur einen Teilbereich aufzuzählen. 

Man kann durchaus entgegenhalten, dass viele clevere Lösungen „doch nur ganz kurz“ dauern. Jeder kennt die Situation: „Kannst du bitte nicht ganz kurz mal X einbauen?“. Oft stellt sich dabei heraus, dass mit Nachdenken, Implementierung, Test, Rücksprachen, Produktivnamen dann doch wieder eine oder mehrere Stunden vergangen sind. Wenn es schlecht läuft, sogar mehr, weil das Problem doch komplizierter war als gedacht. Und diese Zeiten summieren sich über die Laufzeit eines Projektes auf.

Zu komplizierter Softwarecode: Das sind die Gründe

All die oben genannten Probleme sind vermeidbar und die Argumentation durchaus nachvollziehbar. Also wieso kommt es oft zu diesen Situationen? Meiner Erfahrung nach gibt es hierfür mehrere Gründe:
Entwickelnde empfinden die Domäne, in der sie arbeiten, als uninteressant oder wollen sich nicht in diese Einarbeiten. Daher suchen sie ihre Herausforderungen in anderen Bereichen – Technologien und Algorithmen. Außerdem möchten Entwickelnde beweisen, dass sie „besser“ sind als andere Entwickelnde und suchen dabei ihren Weg in komplexen Lösungen – Konkurrenzempfinden statt Teamwork. Manche Entwickelnde leiden ggf. am Impositor Syndrom (oder auch: Hochstapler-Syndrom), das heißt sie haben Selbstzweifel, die sie bewusst oder unbewusst bekämpfen. Schließlich ergaben Studien, dass ca. 2 von 5 erfolgreichen Menschen daran (zeitweise) leiden. Oder Entwickelnde haben etwas neues gelernt (z.B. Rekursion) und versuchen es so oft wie möglich oder ggf. übertrieben einzusetzen. Überall sind plötzlich Design Patterns, neue Befehle und Bibliotheken, egal ob es zur Problemstellung passt. Wenn man einen Hammer hat, ist jedes Problem ein Nagel.

So lassen sich komplexe Softwarelösungen umgehen

Wie geht man mit dieser Situation um, damit diese Probleme umgangen werden können?
Hierfür empfehle ich die folgenden fünf Möglichkeiten:

1. Objektive Bewertung
Ist die Lösung, die ich anstrebe, verständlich und nachhaltig im Verhältnis von Aufwand und Business Value? Dabei hilft es sich an nicht-funktionalen Anforderungen der Software zu orientieren. Diese können z.B. im Rahmen eines ATAM-Workshops ermittelt werden. Eine Optimierung auf Performance ist nicht nötig, wenn die angestrebten Responsezeiten bereits erfüllt sind. Eine Skalierbarkeit ist (noch) nicht notwendig, wenn es in einem User perspektivisch nur wenige User gibt. Vermeidung von Optimierung auf Zustände, von denen man nicht weiß, ob sie jemals eintreten.

2. Innovationsprojekte im Unternehmen
Innovationprojekte innerhalb der Firma können helfen, um neue Technologien zu erproben und Entwickelnden die Möglichkeit zu geben, ihren Forscherdrang hier auszuleben. Dabei ergeben sich auch oft Ansatzpunkte, die Technologien an Stellen zu verwenden, an denen sie sinnvoll gezielt einsetzbar sind.

3. Code Katas
Code Katas bieten die Möglichkeiten, außerhalb von auch „clevere“ Lösungen zu implementieren, z.B. mit einer konkreten Zielvorgabe das Kata möglichst flexibel zu lösen oder mit einem Minimum an Code. Nicht jedes Kata muss auf die klassischen Themen TDD und Clean Code abzielen. Auch Varianten sind möglich und gewünscht. In der Intense AG bieten die Clean Code Advocates hierzu den Ninja Times an.

4. Frühzeitig Probleme identifizieren
Probleme erzeugen die wenigsten Aufwände und Folgekosten, wenn sie frühzeitig identifiziert werden. Hierzu gibt es verschiedene Zeitpunkte, an denen dies stattfinden kann. Übliche Lösungen sind z.B. eine Definition of Ready und das Refinement, in der das Team auch übermäßig komplexe fachliche Anforderungen identifizieren kann. Im weiteren Verlauf sind Architekturreviews eine Option, Probleme auf hohem Level zu identifizieren. Weiterhin helfen Code-Reviews, in denen clevere oder überkomplexe Lösungen identifiziert werden können. Ebenso durch GUI-Prototypes und Usability Tests im Bereich von UI-Lösungen mit denen direkt Feedback durch den Kunden erhalten werden kann.

5. Agile Entwicklungsmethoden anwenden
Wenn die Entwickelnden in einem stark phasenbezogenen Prozess arbeiten, haben sie wenig Möglichkeiten neues zu lernen. In einer klassischen „Feature Factory“ wird den Entwickelnden ein fertiges Konzept übergeben und anschließend das Coding an eine QA Abteilung übergeben. Eine Kommunikation findet nicht oder nur wenig statt. Das führt zu starken Wissens-Silos und damit zu wenig Entfaltungsmöglichkeiten – die Entwickelnden suchen als Lösung clevere Lösung, um sich nicht zu langweilen und die Arbeit abwechslungsreicher zu gestalten. Eine Alternative ist hierzu ein stark kooperatives Modell, wie es eigentlich alle agilen Methoden empfehlen. Sei es Pair Programming oder Ensemble Programming, wichtig ist das „live miteinander“ gearbeitet wird statt „jeder allein für sich, am Ende führen wir es zusammen“ (Scatter-Gather).

Sie haben Fragen zu diesen Lösungsvorschlägen? Gerne unterstützen wir von der INTENSE Sie in Ihren Fragen zu Clean Code Softwareentwicklung und agilen Methoden. Zögern Sie nicht, uns zu kontaktieren.

Dominik Panzer

Dominik Panzer

Dominik verantwortet als Entwicklungsleiter die technische Produktentwicklung der INTENSE und unterstützt Teams als Technical Agile Coach in den Bereichen Prozesse, Testautomatisierung, Methodiken und Clean Code. Er ist Initiator der Clean Code Advocate Initiative.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.