Je vais me remettre à écrire du code à la main
(blog.k10s.dev)- k10s était un TUI Kubernetes orienté GPU, créé rapidement avec du vibe-coding avec Claude, mais après l’ajout d’une vue de flotte, plusieurs états d’écran se sont cassés
model.goa grossi jusqu’à devenir un uniqueModelde 1690 lignes avec unUpdate()de 500 lignes, absorbant à lui seul l’UI, le client, le cache, la navigation et l’état des vues- L’IA a permis d’ajouter des fonctionnalités rapidement, mais a aussi fait enfler un god object et un gestionnaire global des touches, où chaque nouvelle vue ajoutait une branche de plus au handler existant
- Des données basées sur la position via
[]stringet des mutations directes dans destea.Cmdexécutés en arrière-plan pouvaient provoquer des erreurs de colonnes et une vraie data race - Le nouveau k10s sera réécrit en Rust, avec avant le premier prompt des interfaces, types de messages, règles d’ownership et limites de scope figés dans CLAUDE.md
Pourquoi j’en suis venu à réécrire k10s
- k10s a commencé comme un tableau de bord Kubernetes orienté GPU, un outil TUI conçu pour que les opérateurs de clusters NVIDIA puissent voir immédiatement l’utilisation GPU, les métriques DCGM, les nœuds inactifs, ou encore un coût de
$32/hr - Écrit en Go avec Bubble Tea, il a été construit en environ 7 mois, 234 commits et près de 30 week-ends de sessions de vibe-coding avec Claude
- Au début, des fonctions de base inspirées de k9s — pods, nodes, deployments, services, palette de commandes, live updates basés sur watch, raccourcis Vim — fonctionnaient en à peine 3 week-ends
- La fonctionnalité clé, la vue flotte GPU, affichait pour chaque nœud l’allocation GPU, l’utilisation, les métriques basées sur DCGM, la température, la consommation, la mémoire et des états colorés ; Claude a généré d’un coup la struct
FleetView, les filtres d’onglets GPU/CPU/All et même le rendu des barres d’allocation - Après l’ajout de la vue flotte, en revenant à la vue pods via
:rs pods, la table se vidait, les live updates s’arrêtaient, la vue nodes affichait des données obsolètes du filtre de flotte, et le nombre d’onglets de flotte devenait incorrect - En traquant le problème, il a fallu lire pour la première fois l’intégralité du
model.gogénéré par Claude, soit 1690 lignes, où une seule structModelcontenait à la fois les widgets UI, le client Kubernetes, l’état des logs/describe/fleet, l’historique de navigation, le cache et la gestion de la souris - La méthode
Update()faisait environ 500 lignes, sous la forme d’un dispatchermsg.(type)avec 110 branches switch/case - L’IA peut créer des fonctionnalités rapidement, mais si on lui délègue sans contrainte, l’architecture s’effondre, et la sensation de vitesse ressemble à une réussite jusqu’au moment où tout s’écroule d’un coup
Cinq principes tirés des décombres
-
Principe 1 : l’IA crée des fonctionnalités, pas une architecture
- Claude savait bien produire des fonctionnalités isolées comme la vue flotte, le streaming des logs ou le support de la souris, mais chacune était implémentée dans une logique de « faire marcher ça maintenant », sans prendre en compte sa relation avec les autres fonctions partageant le même état
- Le handler de
resourcesLoadedMsgcontenait des conditions commemsg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil, mêlant une logique spécifique à la vue flotte dans un chemin générique de chargement de ressources - À chaque nouvelle vue nécessitant un comportement custom, une branche était ajoutée au même handler, et il fallait vider manuellement plusieurs champs pour éviter que les données de la vue précédente fuient dans la suivante
- Dans
model.go, des nettoyages manuels commem.logLines = nil,m.allResources = nil,m.resources = nilétaient dispersés à 9 endroits ; en oublier un seul suffisait à laisser des données fantômes de la vue précédente - L’alternative consiste à écrire soi-même avant le code des interfaces, types de messages et règles d’ownership explicites, puis à les inscrire comme invariants d’architecture dans
CLAUDE.md - Exemple de règle : chaque vue implémente un trait/interface
View, une vue n’accède pas à l’état d’une autre, les données asynchrones n’entrent que via des variantesAppMsg, et la structAppne gère que la navigation et le dispatch des messages
-
Principe 2 : le god object est la sortie par défaut de l’IA
- L’IA a naturellement penché vers une structure où une seule struct porte tout, parce que c’est la façon la plus simple de satisfaire le prompt immédiat avec un minimum de cérémonial
- La gestion des touches n’était pas séparée par vue : la touche
sservait à l’autoscroll dans la vue logs, au shell dans la vue pods, et au shell du conteneur dans la vue containers - La demande « ajouter le support shell aux pods » a été réalisée en injectant une branche près du gestionnaire global des touches existant
- La touche
Enteraussi se divisait dans un dispatch plat entre la vue contexts, la vue namespaces, la vue logs et la logique générique de drill-down, via des comparaisons de chaînes surm.currentGVR.Resource - Dans le seul fichier
model.go,m.currentGVR.Resource ==était utilisé plus de 20 fois comme discriminateur de type, et chaque nouvelle vue obligeait à toucher plusieurs handlers - L’alternative est d’interdire l’ajout de champs d’état spécifiques à une vue dans
App/Model, de faire de chaque vue une struct séparée, et de mettre les raccourcis dans la keymap de la vue active, règle à graver dansCLAUDE.md - Il faut des garde-fous du type : « ajouter une vue doit vouloir dire ajouter un fichier ; si cela exige de modifier une vue existante, on s’arrête et on demande », afin d’empêcher l’IA d’ajouter des branches comme chemin le plus court
-
Principe 3 : l’illusion de vitesse élargit le scope
- k10s était au départ un outil pour un public très ciblé, les opérateurs de clusters d’entraînement GPU, mais le vibe-coding a donné l’impression que des fonctions comme pods, deployments, services, palette de commandes, support souris, contexts et namespaces étaient « gratuites »
- Le résultat a été un glissement : au lieu d’un outil centré GPU, k10s s’est élargi vers un TUI généraliste pour tous les utilisateurs Kubernetes, en pratique vers une recréation de k9s
- Une
keyMapplate mélangeait dans une même structure des bindings spécifiques à des vues très différentes :Fullscreen,Autoscroll,ToggleTime,WrapText,CopyLogs,ToggleLineNums,Describe,YamlView,Edit,Shell,FilterLogs,FleetTabNext,FleetTabPrev AutoscrolletShellutilisaient tous deuxs, et comme le dispatch vérifiait la ressource courante, cela « marchait », mais rendait la compréhension locale des raccourcis impossible- La vitesse de production donnait l’impression de « shipper », mais chaque fonctionnalité ajoutait en réalité une branche de plus dans le god object
- L’alternative est d’inscrire explicitement dans
CLAUDE.mdles limites de scope : k10s doit rester un outil pour opérateurs de clusters GPU, limité aux vues fleet, node-detail, gpu-detail et workload, sans vues génériques de ressources ni duplication de fonctionnalités de k9s - L’IA peut fournir un budget de lignes de code quasi infini, mais le budget de complexité, lui, reste fini ; il faut donc refuser le scope à l’avance
-
Principe 4 : les données basées sur la position sont une bombe à retardement
- k10s aplatissait directement les ressources reçues de l’API Kubernetes sous la forme
type OrderedResourceFields []string - La fonction de tri de la vue flotte traitait
ra[3]comme Alloc,ra[2]comme Compute, etra[0]comme Name ; l’identité des colonnes ne reposait que sur des commentaires et l’ordre des colonnes dansresource.views.json - Ajouter une colonne entre Instance et Compute dans
resource.views.jsonpouvait casser silencieusement le tri, le rendu conditionnel ou la cible de drill-down qui s’appuyaient surra[2]etra[3] - Le compilateur ne peut pas connaître la signification d’un
[]string, et la config JSON ne pouvait pas exprimer le comportement de tri, le rendu conditionnel ou les cibles personnalisées de drill-down, si bien que le code Go durcissait des hypothèses positionnelles - L’IA a tendance à choisir
[]stringouVec<String>parce que c’est plus simple à injecter directement dans un widget de table, tandis qu’une struct typée demande plus de travail en amont et se fait écarter sur le chemin rapide - L’alternative consiste à conserver des données structurées sous forme de structs typées comme
FleetNodeouPodInfojusqu’au tout dernier moment du rendu, et à faire les tris sur des champs nommés plutôt que sur des accès positionnels commerow[3] - Une struct du genre
FleetNode { name, instance_type, compute_class, alloc }permet d’exprimer l’identité des colonnes dans le type lui-même et d’empêcher des états impossibles comme un tri sur la mauvaise colonne - « Making impossible states impossible » est une expression des communautés Elm/Rust qui signifie qu’au lieu d’ajouter des vérifications au runtime, on conçoit les types pour qu’un état invalide ne puisse pas être construit
- k10s aplatissait directement les ressources reçues de l’API Kubernetes sous la forme
-
Principe 5 : l’IA ne possède pas les transitions d’état
- Dans Bubble Tea, l’idée centrale est que l’état ne change que dans
Update(), piloté par des messages ; k10s violait cette règle - Le handler
updateTableMsgretournait une closuretea.Cmd, et cette closure modifiait des champs duModelavec des appels commem.updateColumns(m.viewWidth),m.updateTableData(),m.table.SetCursor(savedCursor) - Bubble Tea exécute les
tea.Cmddans une goroutine séparée, si bien que pendant que cette closure lisait et écrivaitm.resources,m.tableetm.viewWidth, la goroutine principale pouvait lire les mêmes champs dansView() - Il n’y avait ni lock ni mutex, et
<-m.updateTableChanne faisait qu’attendre un signal de mise à jour sans empêcherView()de lire un état à moitié écrit - Cette structure constituait une data race évidente, qui la plupart du temps semblait fonctionner, mais se manifestait parfois par un affichage corrompu
- L’alternative est qu’un worker en arrière-plan ne mute jamais directement l’état UI, mais envoie des messages typés via un channel, puis que la boucle d’événements principale applique les mutations d’état à partir de ces messages
- La règle de concurrence est que les tâches de fond ne modifient jamais directement l’état UI, envoient leurs résultats sous forme de messages typés, et que
render()/view()reste une fonction pure sans side effects, I/O ni opérations de channel
- Dans Bubble Tea, l’idée centrale est que l’état ne change que dans
Règles de protection à mettre dans CLAUDE.md et agents.md
-
Invariants d’architecture
- Chaque vue doit implémenter le trait/interface
Viewet ne doit pas accéder à l’état d’une autre vue - Toutes les données asynchrones doivent entrer via des variantes
AppMsg, et aucune tâche de fond ne doit muter directement des champs - L’ajout d’une nouvelle vue ne doit pas exiger de modifier les vues existantes
- La struct
Appdoit être un routeur fin chargé uniquement de la navigation et du dispatch des messages
- Chaque vue doit implémenter le trait/interface
-
Règles de propriété de l’état
- Aucun champ d’état spécifique à une vue ne doit être ajouté à la struct
App/Model - Chaque vue doit exister sous forme de struct séparée et déclarer ses propres raccourcis
- L’application doit dispatcher les touches vers la vue active, et tout nouveau raccourci doit être ajouté à la keymap de cette vue, pas à un handler global
- Si l’ajout d’une vue exige de modifier une vue existante, il faut s’arrêter et demander confirmation
- Aucun champ d’état spécifique à une vue ne doit être ajouté à la struct
-
Scope
- k10s doit être un outil pour opérateurs de clusters GPU, pas pour tous les utilisateurs Kubernetes
- Les vues prises en charge doivent se limiter à fleet, node-detail, gpu-detail et workload
- Il ne faut pas ajouter de vues génériques de ressources comme pods, deployments ou services
- Il ne faut pas ajouter de fonctionnalités qui dupliquent celles de k9s
- Toute demande de fonctionnalité qui n’aide pas les opérateurs de jobs d’entraînement GPU doit être refusée
-
Représentation des données
- Il ne faut pas aplatir les données structurées en
[]string,Vec<String>ou tableaux positionnels - Les données doivent rester sous forme de structs typées jusqu’à l’appel de rendu
- L’identité des colonnes doit venir des noms de champs des structs, pas des index de tableau
- Les fonctions de tri doivent opérer sur des champs typés, pas sur des accès positionnels du style
row[3] - La production des chaînes destinées à l’affichage ne doit se faire que dans les fonctions
render()/view()
- Il ne faut pas aplatir les données structurées en
-
Règles de concurrence
- Les tâches de fond comme watcher, scraper ou appel API ne doivent jamais muter directement l’état UI
- Les tâches de fond doivent envoyer leurs résultats via un channel sous forme de messages typés
- Seule la boucle d’événements principale doit appliquer les mutations d’état à partir des messages reçus
render()/view()doit rester une fonction pure sans side effects, I/O ni opérations de channel- Si un travail asynchrone doit modifier l’état, il faut définir une nouvelle variante
AppMsg
La manière de reconstruire
- k10s va être réécrit en Rust, non pas parce que Rust serait « meilleur », mais parce que c’est un langage que l’auteur a le sentiment de pouvoir piloter directement
- Dans un langage qu’on a suffisamment pratiqué, on peut sentir qu’il y a quelque chose qui cloche avant même de savoir l’expliquer verbalement, et cette intuition ne peut pas être remplacée par le vibe-coding
- Quand l’IA produit un code plausible, il faut être capable de détecter si c’est en réalité du déchet
- Dans la nouvelle version, le travail de conception — interfaces concrètes, types de messages, règles d’ownership — sera d’abord fait à la main avant d’écrire le code
- Au lieu de laisser l’IA prendre de mauvaises décisions d’architecture comme auparavant, ces décisions seront désormais fixées dans des documents avant même le premier prompt
- Les liens vers le TUI existant et le projet sont disponibles sur k10s Github et K10S.DEV
Remarque
- Bubble Tea est un framework TUI Go fondé sur The Elm Architecture, et les problèmes d’architecture de k10s viennent de son implémentation, pas de Bubble Tea
- « Making impossible states impossible » est une expression des communautés Elm/Rust qui consiste à empêcher la construction d’un état invalide par le design des types, plutôt que de le vérifier au runtime
- Comme le « tiret cadratin » dans l’écriture IA, le « god object » peut être une odeur caractéristique du code généré par l’IA, et le vibe-coding peut donner l’impression que l’implémentation ne coûte rien, au prix d’une perte de focus et d’un gonflement du projet
1 commentaires
Commentaires Hacker News
Les gens qui disent que le code généré est correct sont en général les seules personnes qui ne lisent pas ce code
Même les mesures d’atténuation proposées dans l’article ne tiendront pas longtemps. Lorsqu’on conçoit un système ou un composant, on définit des invariants comme « une vue n’accède pas à l’état d’une autre vue », mais tôt ou tard on doit ajouter une fonctionnalité qui entre en conflit avec eux
À ce moment-là, on finit en général soit par abandonner la fonctionnalité, soit par l’empiler de façon bancale et inefficace au-dessus de l’invariant, soit par modifier l’invariant lui-même. Ce choix n’est pas seulement une question de contexte, c’est une question de jugement, et les modèles actuels se trompent bien trop souvent sur ce point
Si l’on explicite les contraintes d’architecture, l’agent produit, même quand il faudrait les changer, un code tordu pour s’y conformer, complexe et impossible à maintenir. Si on ne le lit pas encore plus soigneusement que du code humain, on finit avec un « code qui se dévore lui-même » et on ne s’en rend compte que trop tard
L’essentiel est d’identifier les points où l’IA a du mal et de les lui simplifier. Par exemple, il faut un contexte extrêmement réduit, une modularisation avec des frontières nettes, des modules purs séparés des entrées/sorties, du masquage derrière des interfaces, 100 tests qui tournent en moins d’une seconde, des benchmarks, etc.
L’IA fonctionne bien quand il y a des frontières et peu de contexte. Si on ne lui donne pas cela, ses performances se dégradent, et la responsabilité incombe à la personne qui utilise l’outil
Aucune spécification ne résiste au réel, et même avec beaucoup d’étude et de conception, certains invariants de la spécification finissent par se révéler faux
Quand un humain rencontre cette situation en développant, il peut prendre du recul et reconsidérer si l’invariant est erroné et quelles seraient les conséquences de le modifier. L’IA, en revanche, a souvent tendance à bricoler une solution sous des hypothèses ou une conception fausses, sans l’intuition nécessaire pour réévaluer l’ensemble
Cela peut s’améliorer avec de bons workflows et de bonnes validations, mais ce n’est pas un domaine que des outils comme Claude Code gèrent bien par défaut, et il y a des limites
Au départ, nous avons posé des principes forts et déplacé quelques usages à la main pour nous convaincre. La migration complète était tellement vaste et coûteuse qu’elle avait été repoussée presque 10 ans, donc nous avons voulu l’accélérer avec l’IA pour réduire les coûts
L’IA s’en sortait correctement dans les 80 % de cas mécaniques et simples. Les 20 % restants nécessitaient des changements dans le framework ; la plupart étaient mineurs, comme l’ajout de champs à une API, mais un ou deux exigeaient une refonte conceptuelle
Le backend d’un certain système pouvait produire certaines données dans 99 % des cas, mais dans quelques cas importants c’était logiquement impossible et il fallait les recevoir de l’extérieur. Or une optimisation importante avait été construite sur l’hypothèse que « c’est impossible »
L’outil d’IA n’a pas détecté ce cas et a ajouté une logique de migration comme si tout allait fonctionner correctement. Grâce à notre mode de déploiement, cela n’a pas encore créé de bug en production, mais en posant les bonnes questions à l’équipe partenaire, nous avons découvert que le même besoin existait ailleurs
Au final, cela n’a pas tourné à la catastrophe parce qu’une personne était allée très loin dans l’analyse. Des outils de validation et des modèles plus intelligents rendront peut-être ce type de migration plus simple à l’avenir, mais pour l’instant le code généré peut être séduisant puis se casser, donc il faut le surveiller de près en permanence
J’avais utilisé pendant environ deux mois un motif d’architecture un peu particulier, et à chaque utilisation il me gênait légèrement ; ce n’est qu’hier soir que j’ai compris que ce n’était pas une bonne abstraction et qu’il fallait le découper autrement
Quand je laissais un LLM générer le code, cette gêne était beaucoup moins nette, donc il m’a fallu plus de temps pour repérer le problème et trouver une solution. On peut générer les parties périphériques, mais les fonctionnalités centrales, je dois encore en écrire moi-même l’essentiel
Même s’ils sont exprimés dans un langage formel précis, le LLM sous l’agent n’a pas la capacité de comprendre pourquoi cet invariant est nécessaire ni pourquoi il est important. Il pourra peut-être exister un jour un LLM capable de relier les tokens à une spécification formelle et d’en rédiger la preuve, mais le code étrange généré à partir de la partie informelle du prompt continuera d’apparaître
On ne peut pas l’empêcher simplement en ajoutant des contraintes et des prompts à une liste technique ou à une spécification. Même avec un meilleur piège, la créature s’échappe
Le problème, c’est le gonflement du code, quand on ajoute du code pour satisfaire le prompt ou la tâche. Souvent, moins de code vaut mieux, et il faut quelqu’un capable d’anticiper ce que d’autres voudront et attendront. Les générateurs sont utiles, mais il faut les utiliser avec plus de retenue, pas comme un tuyau d’incendie
Quand Copilot complétait automatiquement une ligne, on disait « oui, mais c’est quand même toi qui dois écrire toute la fonction » ; quand il a su compléter une fonction, on disait « oui, mais la logique autour de la fonction, c’est toi qui dois l’écrire » ; puis quand cette logique a été complétée, on disait « oui, mais la fonctionnalité, c’est toi qui dois l’écrire »
Maintenant qu’il complète la fonctionnalité aussi, on dit « oui, mais l’architecture, c’est encore toi qui dois l’écrire ». Je ne sais pas si ces modèles pourront résoudre l’architecture, mais c’est intéressant de voir les attentes se déplacer en permanence
Que l’IA complète une ligne, une fonction entière, une fonctionnalité ou un ticket, il faut toujours lire et comprendre le code
J’utilise l’IA en permanence et elle s’améliore, mais je relis toujours chaque ligne. Même au niveau ligne par ligne, je ne suis pas sûr qu’aujourd’hui ce soit meilleur que l’autocomplétion par tab de l’an dernier ; parfois c’est excellent, parfois c’est vraiment mauvais
Les LLM sont excellents pour le développement logiciel, mais seulement quand on ne leur laisse pas écrire l’architecture. Il vaut mieux créer soi-même les modules, structures et énumérations, et autant que possible ajouter soi-même les champs et variantes
Mettre des commentaires de documentation sur chaque structure, énumération, champ et module, puis indiquer ces modules et structures de données au LLM pour qu’il remplisse les corps de fonctions nécessaires, par exemple, me paraît être une bonne approche
Même si on répète plusieurs fois « surtout pas de blocage dans le chemin critique », le LLM finit par mettre du blocage dans le chemin critique ; et même si on dit « si tu fais X, il faut des tests de type Y », il fait X et oublie les tests
Les humains non plus ne suivent pas les consignes à 100 %, mais les LLM sont plus aléatoires. Les erreurs humaines consistent relativement moins souvent à faire exactement l’inverse de ce qu’on veut
Les LLM voient des invariants importants dans le code, puis construisent un contournement, écrivent des tests qui font passer un échec pour un succès, affirment avoir fait ce qui était demandé, puis noient tout cela dans un commit de 5 000 lignes
Je suis convaincu que les LLM sont excellents et qu’ils représentent l’avenir, et c’est pour cela que je crée un langage appelé https://GitHub.com/Cuzzo/clear. Il faut dépasser le problème des langages qui exigent un contexte global là où il ne devrait pas être nécessaire, afin de rendre la collaboration plus facile
J’ai eu des succès, mais c’est parfois tellement frustrant que je me demande si cela valait le coup d’y investir ma santé mentale
Cela ne veut pas dire que l’architecture n’est pas importante, mais que l’architecture qui convenait hier n’est pas forcément encore la bonne aujourd’hui
En utilisant des agents de code, je me suis fixé quelques règles
Premièrement, si je génère du code avec un agent, il faut que je sois absolument certain que, si j’en avais le temps, je pourrais moi-même l’écrire correctement
Deuxièmement, si ce n’est pas le cas, je ne passe pas à la suite tant que je n’ai pas complètement compris ce qui a été généré au point de pouvoir le reproduire moi-même
Troisièmement, si je viole la deuxième règle, je peux créer une dette cognitive, mais je dois l’avoir intégralement remboursée avant de déclarer le projet terminé
Plus cette dette s’accumule, plus la qualité du code généré ensuite risque de baisser, avec une impression d’intérêts composés. Sur des projets personnels, cette méthode est agréable, on apprend beaucoup, et il reste une codebase qu’on comprend confortablement
Il faut trouver un point d’équilibre qui permette de rester connecté à la codebase sans devenir le goulet d’étranglement de l’équipe
Claude était un mathématicien de niveau doctorat, pas moi, mais je savais exactement quelles propriétés devait avoir la solution recherchée et comment tester sa validité. J’ai donc gardé la solution de Claude plutôt que ma solution simple et naïve, je l’ai indiqué dans la pull request, et tout le monde a considéré que c’était le bon choix
Je me demande s’il faut prévoir une exception dans ce genre de cas. Si l’IA devient bien meilleure que moi non seulement en mathématiques avancées mais aussi en code, la vraie question intéressante sera peut-être de savoir si je cesserai complètement de coder à la main, au prix de perdre ma capacité à juger le code directement, en supposant que je puisse toujours juger les tests
C’est plus précis, parce que la dette qui s’accumule est exactement un manque de compréhension du code
Je ne vois pas pourquoi il faudrait tout à coup traiter l’IA différemment
Au final, il faut raisonner en termes de risque et de récompense. Il faut se demander ce qu’on perd si c’est faux, quelle est la probabilité de le détecter dans les tests et la revue, et quel bénéfice on en retire si cela marche. C’est pareil pour les bibliothèques et les services externes
Des règles financières complexes dans un contrat crypto impossible à mettre à jour et sans tests n’ont absolument pas le même niveau de risque qu’un viewer interne pour visualiser des données de logs
En théorie cela paraît bien, mais en réalité on prend toujours des raccourcis mentaux sans même s’en rendre compte
Quand je corrige un problème dans une codebase inconnue, si je compare ce que je retiens une semaine plus tard entre une correction faite par moi et une correction faite par un agent que je croyais avoir « complètement comprise », ce n’est pas la même chose. Quand je le fais moi-même, cela s’ajoute à ma connaissance générale et les parties importantes restent en mémoire ; quand j’essaie de m’approprier comme mienne une intervention de l’agent, j’ai l’impression de l’avoir comprise sur le moment, mais je l’oublie très vite
J’en ai donc conclu que dans ce genre de cas l’aide des LLM est, la plupart du temps, nuisible à mes objectifs, même sans tenir compte d’autres préoccupations comme le temps ou la pression business
J’ai vécu la même chose
L’arnaque se déroule ainsi. Sur une bonne codebase, l’IA peut produire beaucoup de fonctionnalités, et cela semble plus rapide, plus sûr et plus précis. C’est encore plus vrai dans les domaines qu’on connaît mal
Puis, avec le temps, la codebase grandit, le temps d’exploration augmente, le taux d’échec monte. On ne veut pas l’admettre, on pousse encore plus fort, et on ne s’arrête qu’une fois les changements devenus pratiquement impossibles
Quand on relit le code, « spaghetti » est un mot trop faible, c’est la Grande Muraille
Au final j’ai supprimé 75 000 lignes sur 140 000, et j’ai l’impression que les 3 mois d’immersion intense dans le coding par agent ont été perdus. J’ai construit des fonctionnalités inutiles, augmenté les bugs, perdu mon modèle mental du code, raté des décisions difficiles qui ne se voient que lorsqu’on est dans le code, et au final j’ai aussi échoué vis-à-vis des utilisateurs
Je ne dis pas ça par sarcasme ; je suis vraiment curieux de savoir quelles étaient les attentes initiales et d’où elles venaient
On a l’impression que les attentes sont différentes avec les LLM. Si quelqu’un donnait une description résumée de fonctionnalité à un « développeur » pris au hasard en ligne et recevait en retour un tas d’implémentations à moitié cassées, personne ne serait surpris
Pourtant, les gens attendent parfois d’une machine qui hallucine longuement des miracles qu’ils n’attendraient même pas d’un humain. Je me demande d’où vient cette confiance
Comme une grande ville est un ensemble de petites villes : il y a une carte, on zoome sur une zone locale et on travaille dans ce périmètre. On n’a pas besoin de connaître tous les détails de New York pour aller boire un café
Construire une architecture saine et maintenable est de la responsabilité de la personne qui utilise l’outil. L’IA ne l’empêche pas, et si on tient l’outil correctement, elle peut même aider
Par exemple, traiter immédiatement le code généré par l’IA comme du code legacy, lui imposer des frontières d’encapsulation fortes et des interfaces bien définies, puis l’intégrer via un flux plus manuel
Il existe tout un spectre allant du prompt ponctuel à la génération de code inline, et la bonne méthode dépend du problème et de l’endroit dans la codebase
La génération ponctuelle convient mieux à la phase de prototypage, où l’on réécrit beaucoup la spécification ; puis, une fois le prototype stabilisé, on peut descendre à une génération plus systématique au niveau module ou fichier, en conservant à ce niveau un modèle mental satisfaisant
S’il était lu mais pas compris, il aurait suffi de demander des commentaires détaillés sur chaque sortie ; et si on sait que le modèle a plus de mal à mesure que la codebase grossit, alors il faut examiner ses sorties d’autant plus strictement que la complexité augmente
Créer des îlots de code de meilleure qualité, utiliser l’IA pour aider à reconstruire l’intention du développeur et les règles métier, puis créer des seams et des tests unitaires autour du module ciblé
L’IA n’a pas forcément à servir uniquement à augmenter le débit ; elle peut aussi devenir un outil souple d’exploration et de refactoring, qui aide ensuite le coding manuel ou l’implémentation par agent
Chaque fois que je vois ce genre d’article, je compare la vitesse que les gens disent obtenir avec l’IA à celle que j’obtiens simplement en codant à la main
Par hasard, je travaille depuis 7 mois sur un projet de 3D MMO ; il est déjà jouable, les gens le trouvent amusant, les graphismes sont corrects, et je peux facilement mettre des centaines de personnes sur le serveur. L’architecture est aussi plutôt bonne, donc il est facile d’ajouter des fonctionnalités, et j’ai l’impression qu’une sortie serait possible après environ un an de développement
Pourtant, dans l’article original, après 7 mois de vibe coding, l’auteur n’avait même pas réussi à produire une TUI de base. On peut avoir l’impression d’avancer vite côté fonctionnalités, mais pour construire une UI aussi basique, c’est incroyablement lent. Il existe beaucoup de bonnes bibliothèques TUI, et si le besoin se résume à remplir un tableau avec les données requises, cela se fait à la main en quelques semaines
Avec l’IA, on a fortement l’impression d’avancer vite et en grande quantité, mais en pratique cela semble souvent bien plus lent que le code manuel. Les données de productivité semblent aussi indiquer que les utilisateurs d’IA se sentent plus rapides alors que leur production réelle est moindre
Dans le développement logiciel, le plus gros poste de consommation de temps, ce sont les réunions pour aligner les attentes des parties prenantes et la solution. De ce point de vue, l’IA n’aide presque pas ; si l’on compare les heures humaines entre la proposition et l’entrée dans la boucle de test, les résultats seront décevants
En revanche, pour résoudre des problèmes, corriger des bugs et implémenter une solution déjà validée, j’ai l’impression d’être au moins 10 fois meilleur qu’avant. Ce n’est pas qu’une question de temps brut : ma capacité à interpréter le comportement observé et à enquêter sur un problème s’est aussi améliorée
Mais certaines personnes n’arrivent pas à obtenir de l’IA des résultats exacts et utiles. Si l’on sait précisément ce qu’on veut et comment on le veut, l’IA aide énormément. Si je lui demande de faire ce que j’aurais fait moi-même, elle le fait plus vite. Mais si je ne sais pas exactement ce que je veux, l’IA nuit à la progression
Si ce que les gens montrent comme produit avec des LLM n’est pas très impressionnant, c’est souvent parce que ce sont des choses qu’on pourrait aussi faire à la main en très peu de temps
Et je n’observe pas non plus une multiplication des logiciels impressionnants, ce qui semble cohérent avec l’idée que les LLM servent aujourd’hui davantage à résoudre des problèmes simples que des problèmes importants
Et il y a un autre point dont on parle peu : la qualité du code
Une codebase issue du vibe coding est un excellent exemple du fait que les LLM ne sont pas si bons que ça pour écrire du code. Ils corrigent leurs propres erreurs puis les recréent aussitôt, et leur usage des patterns manque de cohérence
Dernièrement, Claude fait parfois des choix de style de code « intéressants » qui ne correspondent pas au style actuel de la codebase
Il faut empêcher cette répétition avec un langage de type « développeur senior »
La partie « je conçois moi-même des interfaces concrètes, des types de messages et des règles de possession avant d’écrire le code » est justement la partie difficile du développement
Une fois l’architecture là, écrire le code devient très facile. Si on ne l’écrit pas soi-même, il est plus difficile de remarquer qu’on a conçu une API acceptant
nullalors que la base de données ne l’accepte pas, ou même si elle l’accepte, qu’on a raté un autre petit problèmeCe que je ne comprends pas, c’est qu’après avoir écrit cet article, l’auteur n’ait pas compris que le problème était l’IA. Pas seulement parce qu’il lui a confié l’architecture, mais aussi parce qu’il n’a pas surveillé attentivement tout ce qu’elle faisait
L’IA n’est qu’un générateur de code glorifié, et tout ce qu’elle fait doit être vérifié. La partie difficile de l’ingénierie logicielle n’a jamais été l’écriture du code, mais tout le reste
Les développeurs qui trouvent le coding difficile adorent vraiment le coding avec l’IA, parce que ce qui était difficile auparavant est devenu facile
À l’inverse, pour ceux qui pensent que coder est facile, le coding concerne surtout les abstractions, la maintenabilité et l’extensibilité. La difficulté est de poser des fondations sensées pour permettre au logiciel de grandir, et une fois la bonne abstraction trouvée, le reste devient relativement simple
Pour ces personnes, le coding avec l’IA est un outil utile, mais pas un outil magique. L’auteur original a perçu les limites de l’IA ; il fait donc partie de la seconde catégorie, celle qui voit la partie difficile que l’IA ne sait pas faire
D’un côté, il y a des gens qui utilisent un tab autocomplete très puissant ou un chatbot dans une fenêtre voisine, tout en relisant clairement tout ; de l’autre, il y a des cas comme Steve Yegge, qui fait la promotion de nouveaux éditeurs où l’on orchestre des dizaines d’agents comme si l’on n’allait pas lire la majorité du code : https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
Le premier groupe continue de réfléchir profondément à la conception, aux interfaces et aux structures de données, et de faire une revue rigoureuse. Le second est plus inquiétant, précisément parce qu’il ne le fait pas
Je suis l’approche plan → red/green/refactor, et la planification elle-même paraît souvent assez plausible et bien étayée, car elle aspire toute la documentation et les discussions de forum
Le problème, c’est que dès qu’on commence à travailler, il existe forcément des points où la documentation et l’implémentation divergent en réalité. L’association d’outils n’a peut-être jamais été utilisée de cette manière, la documentation est peut-être obsolète, ou c’est peut-être simplement un bug
Malgré cela, si l’objectif du projet ou de la fonctionnalité est suffisamment clair et qu’on peut exécuter et tester localement, l’agent peut itérer et sortir d’une impasse architecturale. Il ira même lire les dépendances et le code des bibliothèques et proposera des corrections upstream, ce qui ressemble à ce que je ferais dans une session de débogage approfondie
Je suis donc assez satisfait d’une manière de travailler où je donne des instructions et je supervise, plutôt que d’effectuer moi-même les tâches ennuyeuses. En revanche, une bonne partie de l’équipe ne creuse pas les problèmes d’architecture à ce niveau de profondeur et a tendance à « escalader vers l’architecte », ce qui ne me paraît pas sain à long terme
La fenêtre pendant laquelle on peut tout exécuter et tout comprendre semble se refermer rapidement. Cela dit, de la même manière qu’on utilise un compilateur sans comprendre parfaitement sa traduction en code machine, ou un CPU moderne sans maîtriser entièrement la prédiction de branchement et le caching, on s’adaptera peut-être en construisant de nouveaux outils et frameworks
Du point de vue de quelqu’un qui n’a pas énormément d’expérience en code, je n’ai jamais autant appris qu’en vérifiant le résultat, en voyant ce qui est juste ou faux, puis en demandant à Claude de corriger
C’est pour cela que je ne pense pas qu’il va s’améliorer radicalement de sitôt. Quand les gens me demandent « comment tu fais pour que Claude produise quelque chose d’aussi bon ? », ma réponse est toujours : « je regardais attentivement et je trouvais les problèmes, puis je demandais à Claude de les corriger ». C’est littéralement tout, mais dès qu’on dit ça, on voit déjà leur regard se vider
C’est un peu comme Google : il a rendu la recherche d’information plus facile, mais il n’a pas supprimé le facteur humain nécessaire pour distinguer les bonnes informations des mauvaises
D’abord je réfléchis au problème, je conçois la structure et l’API, et ensuite seulement je laisse l’IA s’occuper de l’implémentation
Le titre dit « je suis revenu à écrire du code à la main », mais en réalité il fait plutôt « le travail de conception » à la main avant que le code ne soit écrit
Ensuite, il semble que ce soit toujours Claude qui génère le code
Plus grave encore, j’ai du mal à comprendre qu’après 7 mois, il pensait que son projet de vibe coding fonctionnait bien sans même regarder le code source généré, et qu’il soit allé jusqu’à acheter le nom de domaine
Si c’est un side project et qu’on vérifie progressivement en suivant les diff, ne pas regarder le code en profondeur n’est pas totalement étrange. C’est clairement une autre manière de travailler, mais ce n’est pas non plus complètement délirant
Cela donne l’impression de voir des développeurs faire un speedrun des leçons de gestion de projet et de product management
On est en train de redécouvrir que les spécifications sont utiles, et qu’écrire beaucoup de code faux n’accélère pas vraiment un projet. Les développeurs s’agacent souvent des réunions et des discussions qui gênent l’écriture de code, mais ces processus existent fréquemment justement pour éviter que tout le monde n’écrive davantage de mauvaises choses
On redécouvre aussi que la gestion du travail est utile, et maintenant, à force de dire qu’il faut tout concevoir à l’avance, on repart vers le cycle en V
La prochaine étape sera probablement de donner un nom au prototypage, puis de parler de fonctionnalités incrémentales qui gèrent à la fois les anciennes exigences et les nouvelles, avant d’en arriver finalement à l’idée qu’il faut davantage impliquer les clients
Il faudrait regarder ce que font réellement les chefs de projet et les chefs de produit. Ils pilotent un produit qui est du code, mais on n’attend pas d’eux qu’ils le lisent, et pourtant ils doivent y arriver uniquement avec du langage naturel
Ils pensent que les humains n’écrivent pas eux aussi des choses cassées ? Qu’une équipe ne peut pas partir dans la mauvaise direction et y brûler une semaine, voire plusieurs mois ? Avec le vibe coding, on peut simplement vivre toute cette expérience en 30 minutes. En tant qu’ancien chef de produit technique, c’est exactement la même sensation
En pratique, on n’a pas vraiment l’impression qu’il soit revenu à écrire du code à la main, donc l’écart entre le titre et la conclusion est déroutant