
Lorsque nous programmons, nous utilisons un compilateur pour transformer une intention formalisée sous forme de code, en un artefact exécutable pour en faire un programme. L’interaction avec un LLM relève d’un mécanisme analogue, à une différence près: le compilateur est déterministe et formellement défini. C’est précisément cette propriété qui en fait un meilleur modèle conceptuel. Le prompt tient lieu de code source, la sortie en constitue la compilation. Et, comme avec tout compilateur, lorsque le résultat est incorrect, la démarche appropriée ne consiste pas à retoucher la sortie, mais à corriger la source et à recompiler.
Ce parallèle montre que l’on peut certes ajuster, corriger et relancer un échange avec un modèle, ce qui donne l’apparence d’un dialogue, mais dès que la tâche gagne en complexité, cette boucle de correction devient fragile. Chaque ajustement injecte une instruction supplémentaire dans l’historique de la conversation. Le modèle doit alors réconcilier l’intention initiale, les corrections intermédiaires et la dernière demande, sans disposer d’une hiérarchie formelle entre ces instructions. L’approche la plus fiable reste alors de revenir au prompt, de reformuler l’intention initiale avec davantage de précision, puis de recompiler.
Il est enthousiasmant pour des profils non techniques, débutants ou décideurs, de pouvoir spécifier une intention en langue naturelle plutôt que dans un langage formel. C’est la promesse de produire du logiciel sans acquérir de compétence en programmation. Mais cette accessibilité apparente masque une faiblesse structurelle.
Le problème ne réside pas dans la langue elle-même, qui dispose d’un vocabulaire riche, de nuances et de registres variés. Il réside dans l’absence de contrat formel avec la machine. Un terme peut être parfaitement précis pour un lecteur humain tout en restant ambigu pour un modèle, car rien ne garantit que l’interprétation sera stable, univoque et reproductible. Il ne s’agit pas d’un défaut de la langue, mais d’un défaut de l’interface.
La langue naturelle fonctionne de manière satisfaisante pour décrire des tâches courantes, dans la mesure où celles-ci correspondent à des motifs déjà intégrés par le modèle. Dès que la demande devient spécifique ou inédite, l’absence de spécification formelle oblige à multiplier les contraintes au sein du prompt, jusqu’à ce que celui-ci devienne aussi long et aussi fragile que le code qu’il était censé remplacer.
À cette difficulté s’ajoute le caractère non déterministe des modèles. Dans un langage de programmation bien conçu, il est possible de raisonner localement : une fonction, un module, une interface possèdent des portées clairement délimitées. Dans un prompt, une modification même mineure peut altérer l’ensemble de la sortie de façon imprévisible. Tout est couplé. Cette absence de modularité rend le prompt difficile à maintenir, à déboguer et à faire évoluer, et l’élargissement progressif des fenêtres de contexte ne résout pas le problème, il ne fait qu’augmenter l’espace dans lequel le couplage peut se propager.
L’émergence de frameworks de prompting structurés avec systèmes de rôles comme BMAD, chaînages d’étapes avec les plans, bibliothèques de skills spécialisés, illustrent cette dérive : pour compenser les limites du langage naturel, on y réintroduit progressivement de la modularité, des contrats et des interfaces. Ce faisant, on reconstruit, sans les garanties formelles, les abstractions mêmes que les langages de programmation fournissaient déjà.
Si l’IA semble si efficace pour produire du code, ce n’est pas nécessairement parce qu’elle serait supérieure à un développeur expérimenté. Ça pourrait-être simplement que nos outils imposent un coût d’expression disproportionné. Langages trop verbeux, frameworks trop rigides, environnements de build trop fragiles, en l’état l’IA ne transcende rien. Elle compense, par de la génération statistique, des frictions qui n’auraient pas dû exister.
Prenons un exemple avec Java : on souhaite lire un fichier JSON pour en extraire un champ donné. L’intention est triviale, mais sa traduction impose une suite d’opérations techniques: gestion des exceptions, instanciation d’un parseur, parcours d’une structure d’objets, conversion de types. L’IA ne paraît efficace que parce que le langage impose un coût d’expression sans rapport avec la simplicité de l’intention initiale.
Prenons un seconde exemple, on souhaite exposer une route HTTP qui filtre des données en base et les renvoie au client, cette intention se décrit simplement en une phrase. Dans un framework comme Spring Boot, cette même intention se décompose en un contrôleur, un DTO, un service, un repository. Ce que l’on a rendu verbeux et laborieux aurait dû rester simple à exprimer.
Le recours massif aux grands modèles de langage pour produire du code ne doit pas être interprété comme un simple gain de productivité. Il constitue avant tout un signal révélateur de l’empilement croissant de complexité accidentelle dans nos systèmes et nos organisations.
Nous devrions en conséquence réévaluer en profondeur nos pratiques de développement, nos standards de qualité et nos modèles organisationnels.
Ce constat ne remet pas en cause l’utilité opérationnelle des LLM dans notre quotidien pour des usages qui relèvent précisément de motifs déjà connus du modèle, là où le langage naturel suffit comme interface. La critique porte sur un périmètre différent, celui où l’on confie au prompt la responsabilité d’une spécification logicielle, c’est-à-dire là où la précision, la modularité et la reproductibilité cessent d’être optionnelles. Un exemple pour appuyer mon propos et le rendre plus tangible: Un LLM peut générer un contrôleur REST en quelques secondes. Mais décider si ce contrôleur doit exister, s’il ne viole pas une séparation de responsabilités, s’il n’introduit pas un couplage entre deux contextes métier qui devraient rester indépendants, ça relève d’un jugement architectural qu’aucun prompt ne peut formuler à la place du concepteur. Nous pourrions alors sans être initié nous poser la question, mais qu’est ce que ça impliquerait de ne pas suivre ces principes et laisser l’IA agir? Un couplage introduit aujourd’hui entre deux contextes devient, à six mois, une impossibilité de faire évoluer l’un sans faire régresser l’autre. L’équipe qui voulait livrer plus vite finit par livrer plus lentement, parce que chaque modification exige une coordination croissante entre des composants qui n’auraient jamais dû se connaître. Pour un LLM, plus le système est couplé, plus il faut de contexte pour le modifier, plus le contexte est large, moins la sortie est fiable, plus il faut corriger, plus on consomme de tokens pour corriger, et à aucun moment le couplage lui-même n’est remis en question.
De même, un modèle peut produire une suite de tests unitaires syntaxiquement correcte. Mais il nous faut distinguer un test qui vérifie un comportement métier pertinent, d’un test qui ne fait que refléter l’implémentation actuelle. Obtenir une couverture de tests élevée avec un LLM donne l’illusion de la fiabilité, mais des tests couplés à l’implémentation se brisent au moindre refactoring. La dette technique s’accumule sous couvert d’indicateurs rassurants.
Je vais compléter ce point avec un exemple parlant avec un service qui calcule le prix total d’une commande:
public class OrderService {
public double calculateTotal(List<Item> items) {
double total = 0;
for (Item item : items) {
total += item.getPrice() * item.getQuantity();
}
return total;
}
}
À posteriori je fais générer le test au LLM
@Test
void shouldIterateOverItemsAndSumPriceTimesQuantity() {
// vérifie que la méthode parcourt les items,
// appelle getPrice() et getQuantity() sur chacun,
// accumule le résultat dans une variable
}
Le test passe. La couverture est à 100%. Six mois plus tard, un développeur remplace la boucle impérative par un stream pour préparer une feature à venir, pour laquelle on doit ajouter un filtre (exclure les articles en promotion), appliquer une transformation (une TVA par catégorie). La forme déclarative (on ne gère ni l’accumulateur, ni l’itération, ni la mutation) se compose par chaînage tandis que la boucle impérative, elle, exige d’imbriquer des conditions et des variables supplémentaires, ce qui augmente la complexité cyclomatique.
public double calculateTotal(List<Item> items) {
return items.stream()
.mapToDouble(i -> i.getPrice() * i.getQuantity())
.sum();
}
Le test casse, non pas parce que le résultat est différent, il est identique, mais parce qu’il vérifiait le comment et non le quoi. Un test qui aurait simplement vérifié qu’une liste de deux articles à 10€ et 20€ renvoie 30€ aurait survécu au refactoring. À l’échelle d’un projet, cet effet se multiplie. Les tests censés être un filet de sécurité deviennent le principal obstacle à l’évolution du système. L’IA accélère précisément cette dynamique. Sans elle, un développeur qui écrit ses tests un par un a le temps de se poser la question : est-ce que je teste le comportement ou l’implémentation ? Avec un LLM, cette friction disparaît. On génère cinquante tests en quelques secondes, la couverture monte à 90%, et personne ne remarque que la majorité de ces tests sont couplés à une structure interne qui n’aurait jamais dû être figée.
L’absence de jugement architectural ne produit pas un résultat médiocre immédiatement visible, elle produit un résultat qui semble correct à court terme et dont le coût réel n’apparaît qu’à mesure que le système évolue.
Ces réflexions autour de l’IA me font dire que l’industrie du développement logiciel a progressivement perdu de vue sa raison d’être. On développe un logiciel pour résoudre un problème, pour servir un utilisateur, pour produire de la valeur (qui est d’ailleurs devenu un mot valise). Le logiciel que nous construisons résout-il effectivement le problème pour lequel il a été commandé ?
D’expérience je constate que l’effort des équipes ne porte plus sur cette finalité, mais sur la gestion de sa propre machinerie : cérémonies de coordination, empilement de couches par convention plutôt que par nécessité, ajout de dépendances, processus de validation qui protègent l’organisation mais ralentissent la livraison, outillage qui exige lui-même de la maintenance. Le développeur passe davantage de temps à satisfaire le système qu’à résoudre le problème que le système était censé adresser.
C’est certainement dans ce contexte que l’IA séduit autant, elle promet de court-circuiter cette bureaucratie technique en générant directement le résultat attendu. Mais cette promesse ne fait que contourner le symptôme sans traiter la cause.
