Ich arbeite schon eine ganze Weile mit Dark Factories, die jede Woche sechsstellige Lines Of Code produzieren. Nicht in der Linked-In-Variante „Ich parallisiere meine Claude Codes”, sondern als wirklich autonome Coding-Pipelines in Kubernetes, in denen Agenten Specs erstellen, Code schreiben, selbst reviewen, testen und mergen, in denen es weder Terminals noch Claude Code gibt. Das Schöne ist, dass das seit Opus 4.5 und GPT 5.4 funktioniert.
Das Unschöne ist, dass hier tatsächlich der Mensch fehlt, der aufs Terminal schaut und sagt: „Moment mal – kann es sein, dass es gerade den dritten Datumsparser gebaut hat?“
Genau da liegt ein Problem.
m w5 Kurze Unterbrechung · mAIstack Von PoC zu Produktion. In Wochen, nicht Quartalen. Median-Time-to-first-Usecase: 5 Wochen · On-Premise · 100+ Connectors mAIstack Ansehen →Solange ein Mensch dabei ist, kann es erkannt werden. Im vorbeistreamen, im Review, im Pair, in der Stichprobe, im Flurgespräch.
In einer echten Dark Factory, die auch morgens um 3:00 Uhr ohne menschliche Schicht weiterläuft, fängt es niemand ab.
AI Code Smells
Code Smells kennen wir seit Jahrzehnten, und wir wissen, wie man sie erkennt und beseitigt. AI erzeugt neue Code Smells, weil sie anders arbeitet als Menschen. Dies sind einige der Muster, die wir am häufigsten in generiertem Code sehen – und die in den Dark Factories ein echtes und tägliches Problem sind.
1. Oversimplification: Regex statt Parser, If-Kette statt State Machine
Die Aufgabe ist real und sehr komplex, die Lösung ist eine zwanzigzeilige Regex unter einem Kommentar, der „handles most cases“ sagt.
Vielfältige Workflowzustände, die in eine State Machine gehörten, aber als verwurschtelte If-Treppe implementiert wurden.
Der HTML-”Parser” ist ein einfacher Stapel String-Funktionen, und der Mail-Classifier ist in Wahrheit ein einfaches Keyword-Matchig mit 20 Suchworten.
Das Tückische daran: Der Code funktioniert – zumindest für die drei Beispiele, mit denen das Modell konfrontiert wurde. Er funktioniert aber nicht für den vierten Grenzfall, den niemand im Prompt erwähnt hat, und erst recht nicht im produktiven Einsatz.
Aber weil die Tests aus der gleichen Spec kommen, sind sie alle grün.
2. Overfitting auf Beispielinput
Verwandt, aber subtiler. Der generierte Code löst die Beispiele aus Spezifikation, Prompt und Test – aber nicht die allgemeine Aufgabe. Harte Literale, enge Regex, Positionsannahmen in Listen, First-Element-only-Logik. Die Funktion erwartet die Eingabe sortiert. Sie erwartet, dass die Liste nicht leer ist, dass der Input so aussieht wie im Beispiel.
Eigentlich sind das zwei Probleme: Es fehlen auch hier die relevanten Testfälle, und die Randkriterien und Ausnahmen, die in der originalen Spec noch nicht bekannt waren. Und es fehlt die Implementierung, die damit umgehen kann. Die Reihenfolge ist deshalb: Erst Randfälle erzeugen, dann Implementierung robust machen.
3. Semantische Zwillinge
Im Repo gibt es slugify(). Drei Verzeichnisse weiter wird toUrlSafe() neu geschrieben. Eine Version trimmt Strings, die andere nicht. Eine Version handhabt Umlaute, die andere übersetzt sie zu Fragezeichen. Beide wollen dasselbe. Beide sind unabhängig voneinander entstanden, beide werden aktiv benutzt.
Solche Zwillinge findet kein Linter, weil sie syntaktisch verschieden sind. Man findet sie nur, wenn man Funktionskörper als Embeddings vergleicht, Call-Graphen abgleicht oder Input/Output-Sampling macht. Und selbst dann ist die Reparatur heikel: Manchmal sind die kleinen Unterschiede Absicht. Manchmal sind sie der eigentliche Bug, der seit Monaten still in Production lebt.
4. Not-Invented-Here
Das LLM hat alle Repositories gelernt, und deshalb kann sie leicht eine eigene Funktion für ein lange gelöstes Problem erstellen. URL-Parsing per String-Split. Pfadnormalisierung von Hand. CSV-Parsing mit split(',') – was an dem ersten Komma in einem zitierten Feld zerbricht. Datumslogik mit Subtraktion von Magic Numbers und Tagen, die immer genau 84600 Sekunden haben.
Eigentlich existiert aber seit Jahren die bessere Lösung, und die passende Library liegt sogar schon im Manifest. Aber das Modell hat sie nicht gesehen … oder gesehen und ignoriert.
5. Catch-and-ignore: die Illusion von Robustheit
Code, der nach Robustheit aussieht, weil überall try/except steht. Bei genauerem Hinsehen verschluckt er Fehler still. Leere Catch-Blöcke. Defaultwerte als Fallback. Logging ohne anschließende Behandlung. Mehrere Fehlerarten in einem Block. Retry ohne Diagnose, was eigentlich kaputtgegangen ist.
Für das Generieren von Code ist der Ansatz perfekt, für Debugging, Wartung und Analyse nicht.
6. Utility-Slop: processData, handleInput, formatValue
Immerhin: manchmal merkt der Coding Harness, wenn eine Klasse oder Datei zu groß wird. Und er lagert Funktionen aus, und legt Dateien wie utils.py, helpers.ts oder common.go an. Vage Namen, denn es soll ja auch anderes hineinpassen, und viele optionale Parameter, Flags, die das Verhalten umschalten. Die Funktion wird an fünf fachlich verschiedenen Stellen verwendet und kennt Details aus drei Domänen. Das ist nicht nur hässlich, das ist strukturell gefährlich. Solche Helper werden zu Knoten, an denen Refactoring schwierig wird, weil die Intention und die Aufrufer so verteilt sind. Sie sind das technische Äquivalent zu der Schublade, in der alles landet, was sonst nirgendwo passt.
Ist das alles?
Nein, natürlich nicht. In der Praxis sehen wir
- Addition ist sicherer als Subtraktion: unused imports, tote Variablen, redundante Bedingungen, nicht erreichbare Branches.
- Klassiche Code-Smells: lange Funktionen, hohe zyklomatische Komplexität, zu viele Parameter, tiefe Verschachtelung, Gott-Klassen.
- Near-Duplikate: derselbe Mapper in zwei Modulen, dieselbe Validierung in Controller und Service.
- Unklare Namen:
data,item,result,value. Plausibel klingend, nichtssagend. Boolean-Funktionen ohne Prädikat-Namen. Erfundene Domänenbegriffe, die nirgendwo sonst im Projekt vorkommen. - Fragmentierte Domänenmodelle: neue Ad-hoc-DTOs neben bestehenden Typen, String-Enums neben echten Enums, parallele Status-Codes. Das Domänenmodell erodiert von innen, ohne dass ein einzelner Commit dafür verantwortlich wäre.
- API-Missbrauch: ungefähr richtige, aber semantisch falsche Verwendung von Libraries. Async ohne await, falsche Defaults, fehlendes Resource Disposal, halluzinierte Methoden, die einmal funktionieren und beim zweiten Aufruf wegrennen.
Also brennt da doch noch Licht in der Dark Factory?
Die einfache Antwort ist: mehr Reviews. Das funktioniert, solange Menschen reviewen.
In der Praxis ist das weder leistbar noch sinnvoll bei 50.000 Zeilen Code pro Tag. Ab Review 10 gilt “looks good, merge it.”
Die ehrliche Antwort ist: Reparatur muss selbst Teil der Fabrik sein. Kodifizierte Fitness-Funktionen, programmatische Erkennung und eine zweite Schicht von Agenten, die nicht Tickets ziehen, sondern den Code der ersten Schicht inspizieren. Keine ästhetischen KPI-Beautifier, sondern als gezielter Reparaturmechanismus mit klarer Beweislast.
Drei Prinzipien, die ich dafür inzwischen für nicht-verhandelbar halte:
Erkennen vor Reparieren. Viele Muster lassen sich erkennen, ohne sofort einen Diff zu schreiben. Kandidaten finden, Belege sammeln, dann entscheiden. Das sollte, soweit es geht, verlässlich – und damit programmatisch, nicht agentisch – passieren.
Tests vor Code. Erst Randfälle erzeugen, fehlende Tests schreiben, dann die Implementierung robust machen. Reparatur ohne ergänzte Testabdeckung erzeugt nur die nächste Generation Slop, diesmal mit anderer Signatur.
Klein und validierbar. Jeder Reparatur-Diff muss so klein sein, dass er sich vollständig verstehen lässt. Lieber zehn winzige PRs als einen „big refactor“, den niemand mehr nachvollzieht. In der Dark Factory gibt es keinen Senior, der noch mal drüberschaut.
Die Rolle von Qualität
Dark Factories sind nicht das Ende von Code-Qualität. Verlässlichkeit und Geschwindigkeit gibt es auch in ihnen nur dann, wenn Qualität gegeben ist. Wenn die Fabrik nachts läuft, muss sie ihre eigene Reparaturstrecke mitbringen – sonst produziert sie mit hoher Geschwindigkeit präzise das, was niemand will: preiswert erzeugten, lokal plausiblen, aber in Wahrheit global inkonsistenten Code.
Slop entsteht nicht, weil die KI dumm ist. Er entsteht, weil lokale Plausibilität ohne Systemverständnis billiger zu produzieren ist als saubere Integration. Genau dort liegt der Ansatzpunkt: nicht erkennen, ob Code „nach KI aussieht“. Sondern erkennen, welche konkreten, lokalen, belegbaren Qualitätsverletzung vorliegt und ob man sie mit einem kleinen, validierbaren Diff reparieren kann.



Schreibe einen Kommentar