Wer in letzter Zeit viel mit KI-Unterstützung programmiert – sei es über Cursor, GitHub Copilot oder Windsurf –, kennt das Phänomen: Die Generierung funktioniert verblüffend schnell, aber wirft man einen Blick auf den produzierten Code, schleicht sich oft ein ungutes Gefühl ein. Der Code ist nicht zwingend falsch, aber er ist… aufgebläht.
KIs neigen extrem zu defensivem Over-Engineering. Sie bauen Sicherheitsnetze, Abstraktionslayer und manuelle Prüfungen ein, die in der Realität kein menschlicher Entwickler so schreiben würde.
Schauen wir uns an einem konkreten Beispiel an, warum das passiert und wie wir den Modellen dieses Verhalten nachhaltig abtrainieren können.
Das Phänomen: Ein harmloser React-State
Kürzlich stolperte ich über eine von einer KI generierte Zeile Code, die ein perfektes Beispiel für diesen “LLM-Bloat” liefert:
setVisibleNodeId(current => (current === nodeId ? current : nodeId));
Gleiches Ergebnis, ein Bruchteil des kognitiven Overheads. Warum wählt das LLM trotzdem den komplizierten Weg?
Occam’s Razor (Ockhams Rasiermesser)
🗡️ Pluralitas non est ponenda sine necessitate – Vielfalt sollte nicht ohne Notwendigkeit angenommen werden.
Ein philosophisches Prinzip aus dem 14. Jahrhundert, das heute relevanter ist denn je: Von mehreren logischen Lösungen für dasselbe Problem ist die einfachste oft die beste. Wendest du dieses Prinzip auf KI-Code an, bedeutet das: Schneide alle defensiven “Was-wäre-wenn”-Abstraktionen rigoros weg. Wenn ein Framework (wie React beim State-Bailout) einen Fall bereits nativ im Hintergrund löst, vertraue darauf, anstatt die Logik in User-Land-Code nachzubauen. [mehr]
Auf den ersten Blick sieht das nach solider React-Schule aus. Es nutzt die funktionale Update-Form von useState, um den vorherigen Zustand (current) abzugreifen, und prüft per Ternary-Operator (? :), ob die angeklickte nodeId bereits aktiv ist. Wenn ja, wird current zurückgegeben, ansonsten die neue nodeId.
Aber halt. Was passiert hier wirklich?
Wenn die angeklickte ID bereits aktiv ist (current === nodeId), gibt die Funktion einfach den identischen Wert zurück. React vergleicht im Hintergrund den alten und neuen Wert (Bailout-Mechanismus) und bricht das Re-Rendering sowieso ab.
Die gesamte Zeile lässt sich also ohne jeglichen Performance- oder Logikverlust so abkürzen:
setVisibleNodeId(nodeId);
Gleiches Ergebnis, ein Bruchteil des kognitiven Overheads. Warum wählt das LLM trotzdem den komplizierten Weg?
Warum KIs “defensiv” programmieren
Modelle wie Claude oder GPT-4o sind darauf trainiert, Fehler um jeden Preis zu vermeiden. In ihren Trainingsdaten haben sie gelernt, dass asynchrone State-Updates in React (wie Zähler oder Listen-Kombinationen) zu Race Conditions führen können, wenn man nicht die funktionale Form (prev) => ... nutzt.
Da die KI im Moment der Generierung lieber auf Nummer sicher geht, wählt sie die “eierlegende Wollmilchsau”-Schablone. Sie baut ein funktionales Update um eine stinknormale, primitive Zuweisung, weil das theoretisch in jedem erdenklichen Kontext fehlerfrei läuft – selbst wenn es im konkreten Fall völlig übertrieben ist. Es entsteht Code-Rauschen.
Die Lösung: Prinzipien statt Symptombekämpfung
Wer versucht, der KI solche Muster einzeln auszutreiben (z. B. durch Regeln wie “Nutze nie funktionale Updates bei IDs”), rennt schnell in eine Sackgasse. Erstens wird die Instruktions-Datei (.cursorrules, agents.md oder ai-instructions.md) unendlich lang, und zweitens besteht das Risiko der Überoptimierung: Die KI rationalisiert plötzlich notwendige funktionale Updates bei Countern weg.
Der smartere Weg ist, das Denkmuster der KI auf einer abstrakteren Ebene anzupassen. Wir müssen ihr die Erlaubnis – und die Pflicht – geben, einfach zu denken.
Hier ist ein Auszug aus einer abstrakten Konfiguration, die das Problem an der Wurzel packt:
# Core Coding Principles
## 1. Radical Simplicity & Occam's Razor
- Always choose the simplest, most direct implementation that fulfills the requirement.
- Avoid defensive over-engineering. Do not add abstraction layers, defensive checks, or complex structures "just in case" unless there is a concrete, immediate technical necessity.
- If a native language or framework mechanism handles a case automatically (like React's state bailout), rely on it instead of rewriting it in user land.
## 2. Eliminate Code Bloat & Noise
- Prioritize readability and low cognitive load. Fewer lines of clean code are strictly better than explicit but redundant logic.
- Before writing a conditional ternary, an extra function wrapper, or a hook, ask: "Can this be expressed as a direct assignment or a simple expression?"
Warum dieser Ansatz funktioniert
Indem wir Konzepte wie Occam’s Razor und Radical Simplicity einfordern, verschieben wir die internen Prioritäten des Modells während des Generierungsprozesses:
- Vertrauen in das Framework: Die KI wird explizit angewiesen, den Bordmitteln des Frameworks zu vertrauen (wie dem automatischen Re-Render-Stopp von React), statt diese in “User-Land-Code” nachzubauen.
- Kognitive Last verringern: Das Modell optimiert nicht mehr nur auf “Fehlerfreiheit durch maximale Absicherung”, sondern balanciert dies mit dem Faktor “Lesbarkeit für Menschen” aus.
- Breitenwirkung: Eine solche Regel greift nicht nur bei React-States. Sie verhindert gleichzeitig verschachtelte
if-else-Wüsten in Vanilla JS, unnötige Interface-Abstraktionen in TypeScript oder redundante Validierungen in Backend-APIs.
Fazit
KI-Assistenten sind geniale Werkzeuge, aber ohne klare architektonische Leitplanken neigen sie dazu, Codebasen mit defensiver Bürokratie zu überfluten. Eine kompakte, im Projekt-Root hinterlegte Instruktions-Datei (z. B. .cursorrules) mit klaren, abstrakten Coding-Prinzipien wirkt hier Wunder.
Am Ende gilt beim AI-driven Development genau dasselbe wie beim klassischen Programmieren: Der beste Code ist der, den man gar nicht erst schreiben (oder lesen) muss.