La plupart des développeurs utilisent les LLMs à l’envers. On décrit ce qu’on veut en cinq mots, on récupère 200 lignes de code, et on passe l’heure suivante à déboguer les hypothèses faites par le modèle.
J’ai passé trois mois à adopter le workflow inverse : rédiger une explication de conception complète avant de demander une seule ligne de code. Le code résultant contenait moins de bugs, mais la vraie amélioration concernait ma propre compréhension.
Qu’est-ce que le literate programming avec les LLMs ?
Donald Knuth a forgé l’expression “literate programming” en 1984. L’idée était simple : écrire une prose qui explique votre programme, puis tisser le code dans cette prose. La plupart des développeurs l’ont ignorée car maintenir deux artefacts était fastidieux. Les LLMs changent ce calcul. La prose n’est plus de la documentation. C’est le prompt.
Quand vous expliquez votre approche à un LLM avant de demander du code, vous faites quelque chose de différent du prompt engineering standard. Vous n’optimisez pas des tokens pour le modèle. Vous vous forcez à articuler les contraintes, les edge cases et les dépendances avant que le modèle n’hallucine une architecture pour vous. Le LLM devient un rubber duck qui répond, mais seulement après que vous avez fait le véritable travail de réflexion.
L’expérience : 30 jours d’explication d’abord
J’ai sélectionné 12 tâches réelles dans mon backlog, allant d’un parser CSV à une stratégie de reconnexion WebSocket. Pour chacune, j’ai utilisé deux approches :
- Le raccourci : coller la description du ticket dans le LLM et demander du code.
- L’explication : rédiger 300 à 500 mots décrivant le problème, les contraintes, la stratégie de gestion des erreurs et les test cases. Puis demander au LLM d’implémenter ce design.
J’ai évalué le résultat selon trois critères : les bugs trouvés en code review, le temps jusqu’au merge, et le nombre de follow-up prompts nécessaires pour corriger les problèmes.
Les chiffres étaient sans appel. Le code “explication d’abord” nécessitait en moyenne 1,2 follow-up prompt, contre 4,7 pour l’approche raccourci. La code review détectait 0,3 bug par soumission “explication d’abord” contre 2,1 bugs pour le code raccourci.
Le hic, c’était le temps. Rédiger l’explication ajoutait 15 à 20 minutes en début de chaque tâche. Que cela vaille le coup dépend de ce que vous construisez.
Comment structurer une explication qui fonctionne vraiment
Une bonne explication n’est pas juste un prompt plus long. C’est un design document avec une structure précise. Voici le template que j’ai affiné après la première semaine :
## Problem
[What are we solving and why?]
## Constraints
[What can't we do? What formats, protocols, or dependencies must we respect?]
## Edge Cases
[What happens when input is null, empty, malformed, or hostile?]
## Error Handling Strategy
[Fail fast or fail graceful? What do we log? What do we return?]
## Test Cases
[The specific inputs and expected outputs that prove this works.]
Voici un exemple concret. J’avais besoin d’une fonction qui valide une signature de webhook en utilisant HMAC-SHA256, mais avec une subtilité : l’en-tête de signature pouvait utiliser deux formats différents selon le service émetteur.
Problem : Valider les signatures de webhook entrantes. Certains expéditeurs utilisent X-Signature: sha256=<hex> et d’autres utilisent X-Signature: <hex> sans préfixe. Nous devons supporter les deux sans casser les intégrations existantes.
Constraints : Doit utiliser le module crypto de Node.js. Doit utiliser une comparaison en temps constant pour prévenir les timing attacks. Doit retourner un boolean clair, ne jamais lever d’exception en cas d’input malformé.
Edge Cases : L’en-tête est manquant. L’en-tête est une chaîne vide. L’en-tête a un préfixe inconnu comme sha512=. La chaîne hex a une longueur impaire ou contient des caractères non hex.
Error Handling Strategy : Logger la raison spécifique de l’échec au niveau debug, mais retourner false pour tous les cas invalides. Ne jamais divulguer de détails de signature dans les messages d’erreur.
Test Cases :
- Valid
sha256=abc123...matches computed signature. - Valid bare
abc123...matches computed signature. - Missing header returns
false. - Wrong signature returns
false. - Non-hex characters in signature return
false.
Puis j’ai ajouté : “Implement this function in TypeScript.”
Le modèle a retourné ceci :
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);
}
Cela a compilé du premier coup. Plus important encore, cela gérait chaque edge case que j’avais spécifié parce que j’avais déjà fait le travail de réflexion. Le modèle n’avait pas besoin de deviner ce que je voulais dire par “validate”.
Où cela aide et où cela fait perdre du temps
Cette approche excelle pour tout ce qui comporte de la branching logic, des dépendances externes ou des implications de sécurité. Le validateur de webhook ci-dessus en est un exemple parfait. Le modèle ne peut pas connaître votre threat model à moins que vous le lui disiez.
Mais pour du boilerplate, c’est excessif. Je n’écris pas 500 mots pour générer un composant de formulaire React ou une migration de base de données standard. Pour ceux-là, l’approche raccourci fonctionne bien car les patterns sont bien représentés dans les training data et les stakes sont faibles.
Le véritable piège est le terrain intermédiaire : des tâches qui semblent simples mais ne le sont pas. Un parser CSV semble évident jusqu’à ce que vous rencontriez des champs entre guillemets contenant des virgules, des marqueurs BOM, ou des mixed line endings. Ce sont ces tâches pour lesquelles sauter l’explication vous coûte le plus de temps par la suite.
Un workflow que vous pouvez piquer aujourd’hui
Vous n’avez pas besoin de changer tout votre processus. Essayez ceci pour une tâche cette semaine :
- Ouvrez votre chat LLM et écrivez l’explication d’abord. Ne mentionnez pas encore le code.
- Relisez votre explication. Si vous ne pouvez pas articuler les edge cases, arrêtez. Vous n’êtes pas prêt à écrire du code, et vous n’êtes certainement pas prêt à laisser un LLM l’écrire pour vous.
- Ajoutez votre demande d’implémentation au même message.
- Revoyez le résultat par rapport à vos propres test cases, pas par rapport à votre intuition.
L’étape que la plupart des gens sautent est la numéro deux. Si votre explication est vague, le LLM vous reflétera cette vagueur sous forme de bugs subtils. L’explication est un smoke test de votre propre compréhension.
Le vrai bénéfice n’est pas le code
Après 30 jours, j’ai remarqué quelque chose d’inattendu. Le code était meilleur, mais mes compétences en design se sont améliorées plus vite. Rédiger des explications vous force à nommer explicitement vos hypothèses. Quand vous faites cela, vous réalisez souvent que votre approche est erronée avant d’écrire une seule ligne de code.
Le LLM fait toujours la frappe. Mais c’est vous qui faites la réflexion, ce qui a toujours été le bottleneck.
Si vous voulez essayer cela vous-même, commencez par votre prochain bug fix. Avant de demander de l’aide à un LLM, écrivez deux paragraphes expliquant ce que vous pensez qui se passe et pourquoi. Vous pourriez résoudre le problème avant d’avoir fini d’écrire.