Eine Benachrichtigungsvorlage wurde vor sechs Monaten eingeführt. Sie ermöglichte es jedem Mandanten, eigene Mitteilungen an seine Kund:innen zu personalisieren – ohne dafür jedes Mal den Backend-Code anpassen zu müssen.
Der Code-Reviewer erkannte zwar, dass das Design schwer nachzuvollziehen war, besonders der Weg von der Vorlage zum finalen Wert. Doch wie lässt sich eine solche Einschätzung konkret begründen, wenn die Funktion funktioniert, alle Tests grün sind und keine offensichtlichen Sicherheitslücken bestehen? Das Risiko der Architektur war real, aber es fehlte ein handfester Ansatzpunkt für die Kritik.
Die Qualitätssicherung gab grünes Licht, und die Funktion ging in den Produktivbetrieb.
Dann traf ein Bug-Report ein: Ein:e Mandant:in hatte eine Benachrichtigung erhalten, die Daten einer anderen Person enthielt. Irgendwo im Benachrichtigungssystem war sensible Information unbefugt geflossen.
Zunächst klang die Lösung simpel: Sicherstellen, dass Benachrichtigungen nur Daten des vorgesehenen Empfängers enthalten.
Doch der zuständige Entwickler – nicht der ursprüngliche Autor – begann mit der Fehlersuche. Die Vorlagen waren in der Datenbank gespeichert. Sechs verschiedene Vorlagentypen füllten ihre Platzhalter an unterschiedlichen Stellen im Code. Manche Werte stammten aus kundenbezogenen Datenbanktabellen, andere aus internen Workflow-Zuständen oder spezifischen Template-Logiken. Die Zuordnung von Platzhaltern zu tatsächlichen Werten fand an einer anderen Stelle statt. E-Mail- und SMS-Kanäle teilten sich Teile des Renderings, aber nicht alle.
Bevor der Entwickler entscheiden konnte, wo der Fehler behoben werden musste, musste er zunächst präzisere Fragen beantworten:
- Welcher Platzhalter zeigte den falschen Wert an?
- Woher stammte dieser Wert?
- Welche Vorlagentypen nutzten diesen Platzhalter?
- Wurde der Fehler bei E-Mail und SMS auf dieselbe Weise behoben?
- Welche Beweise würden zeigen, dass der Fehler vollständig behoben war?
Das System war schwer zu ändern, weil sein Verhalten schwer zu verstehen war.
Was der Entwickler benötigte, war nicht nur „sauberer Code“. Er brauchte vertrauenswürdige Signale, die ihm halfen, komplexere Fragen zu beantworten: Wo genau lebte das unerwünschte Verhalten? Durch welche Pfade flossen die Daten? Welche Daten durften durch den Code fließen? Und wann war die Suche abgeschlossen, um eine sichere Änderung vorzunehmen?
In solchen Situationen reicht es nicht, funktionierenden Code zu schreiben. Das System muss auch so gestaltet sein, dass es der nächsten Entwicklergeneration genug Beweise liefert, um Änderungen sicher und nachvollziehbar durchzuführen.
Gutes Design schafft Beweisgrundlagen
Der Rendering-Fehler enthüllte ein tieferliegendes Problem: Das System machte den Benachrichtigungspfad nicht ausreichend nachvollziehbar, um ihn sicher zu ändern. Der Entwickler musste verstehen, wie aus einer Vorlage eine fertige Nachricht wurde – bevor er entscheiden konnte, wo eine sichere Korrektur möglich war.
Solche Herausforderungen sind im Software-Engineering keine Seltenheit. Oft ist der schwierigste Teil nicht die eigentliche Code-Änderung, sondern das Verständnis des umliegenden Verhaltens: Wo gehört die Änderung hin? Ist sie sicher?
Um dieses Verständnis aufzubauen, suchte der Entwickler nach Signalen im Code, denen er vertrauen konnte.
Ein Paketname kann Entwickler:innen einen glaubwürdigen Startpunkt liefern. Ein Klassenname kann andeuten, welche Logik sich darin verbirgt. Ein gemeinsames Enum kann zeigen, welche Konzepte zusammengehören. Eine explizite Abhängigkeit macht Seiteneffekte leichter erkennbar. Ein Test kann ein Verhalten benennen, das das System verspricht zu erhalten.
Doch diese Signale sind nicht automatisch Beweise. Ein Paketname kann eine Sammelstelle sein. Eine Klasse kann sich von ihrem ursprünglichen Zweck entfernt haben. Ein Testname kann einen Fall beschreiben, der etwas anderes testet. Ein Signal wird erst dann zum Beweis, wenn der Code das Versprechen des Signals einhält. Erst dann kann die Entwickler:in darauf aufbauen.
Gutes Design macht Code nicht nur lesbarer – es macht die Signale des Systems so vertrauenswürdig, dass Entwickler:innen daraus ableiten können, wo Verhalten lokalisiert ist, was zusammengehört, welche Pfade unwahrscheinlich relevant sind und wie weit sich eine Änderung auswirkt.
Die Signale, auf die sich Entwickler:innen verlassen, ändern sich im Laufe einer Untersuchung. Anfangs brauchen sie Hilfe, um Verhalten zu lokalisieren und Logik zu analysieren. Später benötigen sie Unterstützung, um Konsequenzen zu verfolgen und zu entscheiden, ob das verbleibende Risiko klein genug ist, um die Suche zu beenden.
Verständlichkeit besteht aus mehreren Schichten
Entwickler:innen durchlaufen diese Schichten nicht in einer starren Reihenfolge. Sie beginnen vielleicht mit der Suche, öffnen eine Datei, merken, dass sie am falschen Ort ist, folgen einer Abhängigkeit und suchen erneut. Doch die Schichten beschreiben verschiedene Arten von Signalen, die das System liefert, während die Entwickler:in genug Wissen aufbaut, um zu entscheiden, wem sie vertrauen kann.
Wahrnehmung: Kann ich erkennen, was ich vor mir habe?
Bevor eine Entwickler:in über einen Benachrichtigungsleak nachdenken kann, muss sie den vorliegenden Code überhaupt erst erfassen. Form, Gruppierung, Einrückungen, Leerzeichen und Hierarchien zeigen ihr, was zusammengehört und wo eine Idee endet.
Wenn der Rendering-Code aus einem undurchsichtigen Block von Bedingungen, Platzhalter-Ersetzungen und kanalspezifischen Verzweigungen besteht, muss die Entwickler:in zunächst seine Struktur rekonstruieren, bevor sie den Leak analysieren kann. Sie verbraucht Aufmerksamkeit für die Form, bevor sie sie für die inhaltliche Analyse einsetzen kann.
Scheitert diese Schicht, muss die Entwickler:in kognitive Ressourcen darauf verwenden, die Code-Struktur zu entschlüsseln – statt sich auf die eigentliche Fehlersuche zu konzentrieren.
Lokales Denken: Kann ich die relevanten Ideen im Kopf behalten?
Lokales Denken setzt ein, sobald die Entwickler:in eine Code-Stelle gefunden hat und beginnt zu prüfen, ob sie verstehen kann, wofür diese Einheit zuständig ist.
Eine Methode namens renderTemplate klingt nach einer klaren Aufgabe. Sie suggeriert, dass hier nur das Rendern einer Vorlage stattfindet.
Doch wenn dieselbe Methode Vorlagenladung, Empfängerabfrage, Mandantenregeln, Platzhalterersetzung, Kanalformatierung, Verhalten bei fehlenden Werten und Skip-Bedingungen in einem Block vereint, hält der Code sein Versprechen nicht ein. Jeder Schritt mag irgendwo im Rendering-Prozess notwendig sein, aber sie gehören nicht auf dieselbe Abstraktionsebene.
Gute Abgrenzungen reduzieren die kognitive Last, weil sie der Leser:in signalisieren, welche Art von Logik sich innerhalb befindet. Rendern, Empfängerabfrage, Mandantenregeln, Kanalformatierung und Zustellungsentscheidungen erfordern unterschiedliche mentale Modelle – selbst wenn sie technisch verbunden sind.
Fazit: Design als Investition in die Zukunft
Die Erfahrung zeigt: Ein System, das schwer zu verstehen ist, wird auch schwer zu warten sein. Code, der keine klaren Signale sendet, zwingt Entwickler:innen dazu, Zeit in das Entschlüsseln von Architektur zu investieren – statt in die Lösung echter Probleme.
Die gute Nachricht: Verständlichkeit lässt sich gestalten. Durch klare Verantwortlichkeiten, explizite Abhängigkeiten und nachvollziehbare Tests entsteht ein System, das nicht nur funktioniert, sondern auch Vertrauen schafft. Eine solche Architektur zahlt sich aus, wenn neue Funktionen hinzukommen oder Fehler behoben werden müssen – und spart langfristig Zeit und Nerven.
Die nächste Generation von Entwickler:innen wird es Ihnen danken – und das System wird robuster sein, als es je durch bloßen „sauberen Code“ hätte sein können.
KI-Zusammenfassung
Kod değişikliklerinde güvenilir kanıtlar üreten sistemler nasıl tasarlanır? İyi tasarım, gelecekteki geliştiricilere davranışların nerede olduğunu ve nasıl değiştirileceğini gösteren kanıtlar sunar.