Die meisten Entwickler nutzen LLMs rückwärts. Wir beschreiben in fünf Wörtern, was wir wollen, bekommen 200 Zeilen Code zurück und verbringen die nächste Stunde damit, Annahmen zu debuggen, die das Modell getroffen hat.

Ich verbrachte drei Monate damit, den umgekehrten Workflow auszuführen: eine vollständige Design-Erklärung zu schreiben, bevor ich um eine einzige Zeile Code bat. Der resultierende Code hatte weniger Bugs, aber die echte Verbesserung lag in meinem eigenen Verständnis.

Was ist Literate Programming mit LLMs?

Donald Knuth prägte 1984 den Begriff „literate programming“. Die Idee war einfach: Schreibe Prosa, die dein Programm erklärt, und webbe dann Code in diese Prosa ein. Die meisten Entwickler ignorierten es, weil die Wartung zweier Artefakte mühsam war. LLMs ändern diese Rechnung. Die Prosa ist nicht mehr Dokumentation. Sie ist der Prompt.

Wenn du einem LLM deinen Ansatz erklärst, bevor du um Code bittest, tust du etwas anderes als beim Standard-Prompt-Engineering. Du optimierst nicht Tokens für das Modell. Du zwingst dich selbst, Constraints, Edge Cases und dependencies zu artikulieren, bevor das Modell eine Architektur für dich halluziniert. Das LLM wird zu einer Quietscheente, die zurückspricht, aber erst, nachdem du das eigentliche Denken getan hast.

Das Experiment: 30 Tage Erklären zuerst

Ich wählte 12 echte Aufgaben aus meinem Backlog, von einem CSV-Parser bis zu einer WebSocket-Reconnect-Strategie. Für jede nutzte ich zwei Ansätze:

  1. Die Abkürzung: Füge die Ticket-Beschreibung in das LLM ein und bitte um Code.
  2. Die Erklärung: Schreibe 300 bis 500 Wörter, die das Problem, die Constraints, die Error-Handling-Strategie und die Testfälle beschreiben. Dann bitte das LLM, dieses Design zu implementieren.

Ich bewertete die Ausgabe nach drei Kriterien: in der Code Review gefundene Bugs, Time-to-Merge und Anzahl der Follow-up-Prompts, die nötig waren, um Probleme zu beheben.

Die Zahlen waren einseitig. Code mit Erklärung zuerst erforderte durchschnittlich 1,2 Follow-up-Prompts, verglichen mit 4,7 beim Abkürzungs-Ansatz. Die Code Review fand 0,3 Bugs pro Erklärung-zuerst-Submission gegenüber 2,1 Bugs bei Abkürzungs-Code.

Der Haken war die Zeit. Das Schreiben der Erklärung fügte jedem Task 15 bis 20 Minuten am Anfang hinzu. Ob sich das auszahlt, hängt davon ab, was du baust.

Wie man eine Erklärung strukturiert, die wirklich funktioniert

Eine gute Erklärung ist nicht nur ein längerer Prompt. Sie ist ein Design-Dokument mit einer spezifischen Struktur. Hier ist die Vorlage, die ich nach der ersten Woche verfeinerte:

## Problem
[Was lösen wir und warum?]

## Constraints
[Was dürfen wir nicht tun? Welche Formate, protocols oder dependencies müssen wir respektieren?]

## Edge Cases
[Was passiert, wenn Input null, leer, malformed oder feindlich ist?]

## Error Handling Strategy
[Fail fast oder fail graceful? Was loggen wir? Was geben wir zurück?]

## Test Cases
[Die spezifischen Inputs und erwarteten Outputs, die beweisen, dass es funktioniert.]

Hier ist ein echtes Beispiel. Ich brauchte eine Funktion, die einen Webhook-Signature mit HMAC-SHA256 validiert, aber mit einer Wendung: Der Signature-Header konnte zwei verschiedene Formate verwenden, je nachdem, welcher Service die Anfrage gesendet hat.

Problem: Eingehende Webhook-Signaturen validieren. Einige Sender verwenden X-Signature: sha256=<hex> und andere verwenden X-Signature: <hex> ohne das Präfix. Wir müssen beides unterstützen, ohne bestehende Integrationen zu brechen.

Constraints: Muss das Node.js crypto-Modul verwenden. Muss constant-time comparison verwenden, um Timing-Attacken zu verhindern. Muss ein klares Boolean zurückgeben, niemals bei malformed Input werfen.

Edge Cases: Header fehlt. Header ist leerer String. Header hat unbekanntes Präfix wie sha512=. Hex-String hat ungerade Länge oder enthält Nicht-Hex-Zeichen.

Error Handling Strategy: Den spezifischen Fehlergrund auf Debug-Level loggen, aber false für alle ungültigen Fälle zurückgeben. Nie Signature-Details in Fehlermeldungen preisgeben.

Test Cases:

  1. Gültige sha256=abc123... stimmt mit berechneter Signatur überein.
  2. Gültige nackte abc123... stimmt mit berechneter Signatur überein.
  3. Fehlender Header gibt false zurück.
  4. Falsche Signatur gibt false zurück.
  5. Nicht-Hex-Zeichen in der Signatur geben false zurück.

Dann fügte ich hinzu: „Implementiere diese Funktion in TypeScript.“

Das Modell gab dies zurück:

import { createHmac, timingSafeEqual } from 'crypto';

export function validateWebhookSignature(
  payload: string,
  header: string | undefined,
  secret: string
): boolean {
  if (!header || header.length === 0) {
    return false;
  }

  const hexPattern = /^[0-9a-fA-F]+$/;
  let signatureHex: string;

  const prefixMatch = header.match(/^sha256=(.+)$/);
  if (prefixMatch) {
    signatureHex = prefixMatch[1];
  } else {
    signatureHex = header;
  }

  if (!hexPattern.test(signatureHex) || signatureHex.length % 2 !== 0) {
    return false;
  }

  const expected = createHmac('sha256', secret).update(payload).digest();
  const actual = Buffer.from(signatureHex, 'hex');

  if (actual.length !== expected.length) {
    return false;
  }

  return timingSafeEqual(actual, expected);
}

Dies kompilierte beim ersten Versuch. Wichtiger noch, es behandelte jeden Edge Case, den ich spezifiziert hatte, weil ich das Denken bereits getan hatte. Das Modell musste nicht raten, was ich mit „validieren“ meinte.

Wo es hilft und wo es Zeit verschwendet

Dieser Ansatz glänzt für alles mit Branching-Logik, external dependencies oder Sicherheitsimplikationen. Der Webhook-Validator oben ist ein perfektes Beispiel. Das Modell kann dein Threat Model nicht kennen, es sei denn, du sagst es ihm.

Aber für Boilerplate ist es Overkill. Ich schreibe keine 500 Wörter, um eine React-Form-Komponente oder eine Standard-Datenbank-Migration zu generieren. Dafür funktioniert der Abkürzungs-Ansatz gut, weil die Muster in den Trainingsdaten gut repräsentiert sind und die Einsätze niedrig sind.

Die echte Falle ist der Mittelweg: Aufgaben, die einfach erscheinen, es aber nicht sind. Ein CSV-Parser scheint geradlinig, bis du auf quoted fields mit Kommas, BOM-Marker oder gemischte Line Endings stößt. Das sind die Aufgaben, bei denen das Überspringen der Erklärung dich später am meisten Zeit kostet.

Ein Workflow, den du heute stehlen kannst

Du musst deinen gesamten Prozess nicht ändern. Probiere dies für eine Aufgabe in dieser Woche:

  1. Öffne deinen LLM-Chat und schreibe zuerst die Erklärung. Erwähne noch keinen Code.
  2. Lies deine Erklärung zurück. Wenn du die Edge Cases nicht artikulieren kannst, stoppe. Du bist noch nicht bereit, Code zu schreiben, und du bist definitiv nicht bereit, ein LLM ihn für dich schreiben zu lassen.
  3. Füge deine Implementierungsanfrage derselben Nachricht an.
  4. Überprüfe die Ausgabe anhand deiner eigenen Testfälle, nicht anhand deiner Intuition.

Der Schritt, den die meisten Leute überspringen, ist Nummer zwei. Wenn deine Erklärung vage ist, wird das LLM diese Vagheit in Form subtiler Bugs zurückspiegeln. Die Erklärung ist ein Smoke Test für dein eigenes Verständnis.

Der echte Nutzen ist nicht der Code

Nach 30 Tagen bemerkte ich etwas Unerwartetes. Der Code war besser, aber meine eigenen Design-Fähigkeiten verbesserten sich schneller. Erklärungen zu schreiben zwingt dich, deine Annahmen explizit zu benennen. Wenn du das tust, erkennst du oft, dass dein Ansatz falsch ist, bevor du eine einzige Zeile Code schreibst.

Das LLM tippt immer noch. Aber du machst das Denken, was immer der Engpass war.

Wenn du das selbst ausprobieren möchtest, beginne mit deinem nächsten Bug Fix. Bevor du ein LLM um Hilfe bittest, schreibe zwei Absätze, die erklären, was deiner Meinung nach passiert und warum. Du könntest es lösen, bevor du mit dem Schreiben fertig bist.