Der häufigste Befund in generiertem Code ist nicht der Fehler. Es ist der Aufwand. Eine Aufgabe, die eine Funktion und ein Feld gebraucht hätte, kommt als Interface plus Implementierung plus Fabrik plus Konfigurationsobjekt zurück. Alles funktioniert, alles ist getestet, nichts davon ist falsch.
Genau deshalb ist es unangenehm. Ein Fehler wird gefunden. Eine überflüssige Abstraktion wird gemergt, weil sie aussieht wie das, was man in Büchern als gutes Handwerk gezeigt bekommt.
Warum der Diff der falsche Ort ist
Im Diff sieht das Ergebnis vertretbar aus. Jede einzelne Datei ist sauber, benannt wie es sich gehört, mit Tests versehen. Der Schaden liegt nicht in einer Zeile, sondern in der Summe: fünf Dateien für etwas, das in eine gehört hätte.
Solche Befunde haben eine unangenehme Eigenschaft: Sie sind teuer zu benennen und billig zu übersehen. Wer sie im Review anspricht, verlangt faktisch, dass die Arbeit weggeworfen wird, und muss das gegen eine funktionierende, getestete Lösung begründen. Bei eigenem Code kommt Trägheit dazu, bei generiertem Code kommt hinzu, dass niemand emotional daran hängt und es trotzdem niemand freiwillig löscht.
Dazu kommt, dass automatische Prüfungen hier nichts beitragen. Linter, Typprüfung und Tests messen Korrektheit, nicht Angemessenheit. Eine Fabrik mit genau einem Produkt ist syntaktisch tadellos und bleibt in jeder Pipeline grün. Auch ein Review-Durchgang gegen architektonische Leitplanken findet sie nur, wenn eine der Leitplanken genau diesen Fall beschreibt, und "nicht mehr Struktur als nötig" ist als Regel schwer zu formulieren, ohne beliebig zu werden.
Die Verschiebung: prüfen, bevor Code existiert
Meine Konsequenz ist nicht, den Diff gründlicher zu lesen. Es ist, die Frage vorzuziehen. Bevor eine Aufgabe an einen Agenten geht, steht eine kurze Spezifikation und daraus ein Umsetzungsplan, und geprüft wird der Plan, nicht das Ergebnis.
Das ist der Punkt, an dem überflüssige Struktur billig ist. Wenn im Plan steht, dass ein Interface und eine Registry entstehen sollen, kostet es einen Satz, das zu streichen. Steht dieselbe Struktur bereits als Code da, mit Tests, kostet es eine Diskussion und einen Umbau.
Der zweite Teil derselben Entscheidung: Aufgaben klein schneiden. Kleine Aufgaben werden nicht besser gelöst. Sie halten nur die Menge an Code klein, die ich am Ende beurteilen muss. Bei zweihundert Zeilen sehe ich, dass eine Abstraktion zu viel ist. Bei zweitausend lese ich Struktur und prüfe Korrektheit, und die Frage nach der Angemessenheit stellt sich nicht mehr, weil ich mit dem Nachvollziehen beschäftigt bin.
Was ich im Plan konkret suche
Drei Dinge, und alle laufen auf dieselbe Frage hinaus: Lässt sich diese Struktur aus einer Anforderung herleiten, die tatsächlich gestellt wurde?
Ein Interface mit genau einer Implementierung. Legitim, wenn absehbar eine zweite kommt oder wenn die Grenze zum Testen gebraucht wird. Nicht legitim, weil Abhängigkeitsumkehr allgemein gut klingt. Die Frage lautet: Wer ist die zweite Implementierung, und wann kommt sie.
Konfigurierbarkeit ohne Anforderung. Parameter, Strategien, Schalter für Fälle, die niemand genannt hat. Jede Option ist ein Zustand, der später getestet, dokumentiert und beim Umbau berücksichtigt werden muss. Konfigurierbarkeit ist keine Eigenschaft, sondern eine Schuld, die man aufnimmt.
Verteilung ohne Grund. Aus einer Sache werden vier Dateien in vier Ordnern, weil die Schichten der Architektur so heißen. Schichten sind dafür da, Abhängigkeitsrichtungen zu erzwingen, nicht dafür, jeden Gedanken auf vier Orte zu verteilen.
Ein Beispiel, gekürzt
Die Aufgabe: eine Funktion, die für einen Termin bestimmt, wie lange vorher erinnert wird. Zwei Fälle, per Konfiguration umschaltbar. Was zurückkam, war im Kern das hier:
Vier Typen, eine Registry, ein eigener Fehlertyp und ein Zeichenketten-Schlüssel, der zur Laufzeit fehlschlagen kann. Was die Aufgabe verlangt hat, war das hier:
Der Unterschied ist nicht die Zeilenzahl. Es ist, dass die zweite Fassung keinen Laufzeitfehler kennt, keinen Registrierungsschritt beim Start braucht und dass ihre Fallunterscheidung an einer Stelle sichtbar ist. Die erste Fassung ist auf einen dritten Fall vorbereitet, den niemand angefordert hat, und bezahlt diese Vorbereitung sofort.
Fällt der dritte Fall später doch an, ist die Umkehrung billig: Aus einer Funktion mit zwei Zweigen eine Strategie zu machen, ist eine überschaubare Änderung. Der umgekehrte Weg, eine falsch geratene Abstraktion wieder einzureißen, ist es nicht, weil sich inzwischen Aufrufer daran gewöhnt haben.
Was die Vorarbeit kostet
Die Vorarbeit ist der Preis. Spezifikation und Plan vor jeder Aufgabe zu schreiben, kostet spürbar Zeit, und diese Zeit fällt an, bevor irgendetwas Sichtbares entsteht. Bei kleinen Änderungen ist das Verhältnis unangenehm: Die Beschreibung ist länger als die Lösung.
Der zweite Preis: Kleine Aufgaben bedeuten mehr Aufgaben. Mehr Übergaben, mehr Zusammenführungen, mehr Verwaltung. Der Aufwand verschwindet nicht, er wandert vom Lesen ins Zuschneiden.
Und ich bin ehrlich: Nicht jede überflüssige Abstraktion, die es doch bis in den Code schafft, entferne ich. Wenn sie funktioniert, getestet ist und an einer Stelle liegt, die selten angefasst wird, bleibt sie stehen. Das ist keine Konsequenz, sondern eine Rechnung, die ich fallweise mache.
Wo mehr Struktur richtig ist
Wenn klar ist, dass gerade das Gerüst für ein größeres Vorhaben entsteht, gehört Erweiterbarkeit von Anfang an hinein. Dann ist das Interface keine Spekulation, sondern eine Anforderung, und die zweite Implementierung hat einen Namen und einen Termin.
Der Unterschied ist nicht die Menge an Struktur, sondern woher sie kommt. Struktur, die aus einer bekannten Anforderung folgt, ist Planung. Dieselbe Struktur ohne diese Anforderung ist eine Wette darauf, wie sich das Produkt entwickelt, und diese Wette geht meistens verloren.
Nicht die Liste, der Zeitpunkt
Bei generiertem Code hat sich für mich nicht die Prüfliste geändert. Geändert hat sich ihr Zeitpunkt. Am Diff ist überflüssige Struktur schwer zu bestreiten, weil sie funktioniert. Am Plan ist sie ein Satz, den man streicht.
Was dabei bleibt, ist eine Frage, die kein Werkzeug für mich beantwortet: Welche Anforderung hat diese Abstraktion verlangt. Wenn ich darauf keine Antwort finde, gibt es sie nicht.