Tous les articles

Quand lire du code écrit par l'IA n'est pas le relire

Écouter l'article (expérimental) · 10:31

00:00 / 10:31

Un champ dense de lignes de code défilant vers l'obscurité, avec un unique point de contrôle lumineux où quelques lignes sont soulevées dans la lumière et lues, tandis que le reste s'écoule sans être lu

Un développeur que je vois nettement, parce qu'une version de lui est partout en ce moment, a une façon de travailler qui semble responsable. Il laisse le modèle écrire le code, et il lit le diff avant de l'accepter. Il fait une relecture. Sauf que les diffs s'allongent, la lecture s'accélère, et un après-midi il s'aperçoit qu'il a accepté du code qu'il serait désormais incapable de modifier s'il le fallait. Les projets que la machine a construits pour l'essentiel sont ceux dans lesquels il peine à se repérer. Un projet qu'il a écrit à la main il y a trois ans lui fait encore l'effet de sa propre maison. Quelque part en chemin, lire a cessé d'être relire, et rien ne l'a signalé.

Parfois, lire, c'est tout le travail

Voici ce que la version radicale de cet avertissement rate. Pour une grande part de ce que vous acceptez dans une journée, lire le diff est tout le travail, et le lancer vous-même relèverait de la mise en scène. Un renommage que le compilateur a déjà prouvé. Une montée de version de dépendance. Une valeur de configuration, un changement de texte, un commentaire. Du boilerplate que vous avez écrit cent fois et que vous pouvez vérifier à sa forme. Un bloc de tests générés, où la lecture est de toute façon la passe la plus utile, car c'est la lecture qui attrape le test qui n'affirme rien et passe quand même au vert.

La lecture attentive n'est pas un instrument faible. Bien avant tout cela, l'inspection de code trouvait plus de défauts par heure que les tests, et c'est toujours vrai. L'important n'est donc pas de « toujours lancer le code ». L'important, c'est que la lecture et l'exécution sont aveugles à des bugs différents, et la plupart des débats que les gens ont sont en réalité un débat sur l'aveuglement dans lequel ils vivent.

Le vrai test, c'est de savoir si vous pouvez le prédire

La question n'a jamais été la lecture contre l'exécution. Elle est de savoir si vous pouvez prédire ce que fait le code sans le lancer.

Pour une catégorie de changement, vous le pouvez, et la lecture suffit. Pour une autre, la justesse n'est pas dans le texte que vous avez sous les yeux. Elle vit entre les lignes, dans l'entrelacement de deux threads que le diff ne peut vous montrer qu'un à la fois. Elle vit sous la charge, dans la requête qui va bien sur dix lignes et se révèle fatale sur dix millions. Elle vit à la frontière, dans l'off-by-one et le chemin d'erreur que le cas nominal traverse sans même le voir. Elle vit à travers le réseau, dans le fait de savoir si la nouvelle pièce est seulement reliée à ce qui l'appelle. Elle vit dans l'espace négatif, dans le contrôle d'autorisation qui n'est pas là et l'exception avalée en silence, c'est-à-dire dans une ligne qui n'apparaît jamais dans le diff parce qu'elle n'a jamais été écrite. Aucune lecture ne vous mènera à un bug qui n'existe qu'à l'exécution.

Avec l'IA, « ça a l'air correct » compte pour moins

L'IA aiguise ce problème d'une manière précise. Un relecteur approuve du code à partir de signaux qui tiennent lieu de justesse. Le code paraît idiomatique. Il a l'air sûr de lui. Il ressemble à du code qui a déjà fonctionné. Un modèle de langage est une machine réglée pour produire exactement ces signaux-là. Il est très doué pour avoir l'air correct.

Ainsi, le plus vieux raccourci de la relecture, « ça a l'air bon », n'a jamais été une preuve aussi faible que la chose est effectivement bonne. Le vernis n'est pas rassurant. Il est le risque, car il vise précisément la partie de vous qui s'apprêtait à laisser passer le changement. Des travaux montrent que des personnes dotées d'un assistant performant écrivaient du code moins sûr tout en étant plus convaincues qu'il l'était. La confiance évolue à l'inverse du danger, et du code généré bien propre est ce qu'on peut remettre de plus désarmant à un relecteur.

La machine aussi peut le lancer

Lancer le code n'est plus seulement quelque chose que vous faites à la main. L'agent peut le lancer pour vous et vous rendre les logs, une capture d'écran, un paragraphe expliquant ce qu'il a changé et pourquoi il est sûr de lui. C'est réel, et pour bien des changements c'est toute la relecture : les tests sont passés au vert sous vos yeux, la capture d'écran montre le bouton là où il doit être, et vous avez fini plus tôt que vous ne l'auriez fait seul.

Mais regardez ce qui vient de se passer. Cette preuve a été produite par le même genre de système que celui qui a écrit le code, visant la même cible : avoir l'air correct. Un log au vert que vous n'avez pas vu tourner est une affirmation, pas un fait. Vous pouvez même mettre l'agent en surveillance sur les logs, la base de données, les appels sortants, et lui demander si tout concorde, et c'est là un vrai levier. C'est aussi là que j'ai surpris un modèle à ne pas vraiment regarder, répondant par ce qu'il s'attendait à trouver. Confier l'exécution à l'agent ne supprime donc pas la relecture. Cela la déplace d'un cran, du diff vers la preuve. Quand il vous dit que les logs montrent X, ouvrez les logs et trouvez-y X. Traitez « ça a marché, voici la preuve » comme une affirmation à confirmer, de la même façon que vous vérifieriez un chiffre avant de laisser le paragraphe d'un modèle le garder. C'est le contrôle même qu'un chercheur néglige lorsqu'une citation inventée se retrouve dans un article publié.

Ce qui mène à quelque chose de presque ironique. Pour quantité de petits changements, le plus rapide et le plus fiable que vous puissiez faire, c'est de le lancer vous-même. Lire un mur de logs et un résumé plein d'assurance peut vous coûter plus cher que d'ouvrir la chose et de regarder de vos propres yeux. Le lancer n'est pas l'option lente et consciencieuse vers laquelle vous vous rabattez quand la lecture échoue. La moitié du temps, c'est l'option rapide.

L'écueil que la plupart rencontrent, c'est le volume

L'écueil que la plupart rencontrent vraiment n'a rien de subtil. C'est le volume.

Au-delà de quelques centaines de lignes, toute relecture humaine se dégrade en survol, et un agent vous donnera quelques centaines de lignes dès que vous le demandez. Le travail de pure relecture est la forme la plus fatigante que prend le métier, parce que vous reconstruisez l'intention de quelqu'un d'autre, dans un style qui n'est pas le vôtre, à un rythme que vous n'avez pas fixé. Alors il s'effondre, en silence, en simple acceptation. Personne ne décide d'arrêter de relire. Vous vous mettez à voir le code au lieu de le comprendre, et vu de l'intérieur ces deux choses semblent identiques. La contrainte qui compte n'est pas la quantité que le modèle peut écrire. C'est la quantité que vous pouvez réellement tenir en tête. Laissez-le écrire au-delà, et vous ne relisez plus, quoi que l'écran prétende que vous faites.

Adaptez la profondeur à l'ampleur de l'impact

Cela transforme la discipline en quelque chose de plus utile que « lisez plus attentivement ». Ajustez la profondeur de la relecture à l'ampleur de l'impact, et au caractère réversible du changement.

Un petit changement circonscrit et facile à annuler mérite une lecture rapide et un merge. Une migration, une interface publique, une suppression, tout ce qui touche à l'argent ou aux accès, appelle la relecture lente : le lancer, le tracer, chercher à le casser, que vous le fassiez vous-même ou que vous demandiez à l'agent de montrer son travail avant de vérifier ce qu'il a réellement fait, peu importe qui ou quoi l'a écrit. La lecture suffit précisément quand les manières dont le changement peut échouer sont celles que vos autres garde-fous attrapent déjà : le compilateur, les types, les tests qui tournent en CI que vous les lanciez à la main ou non. Elle cesse de suffire dès l'instant où la défaillance qui vous ferait vraiment mal est de celles que seule l'exécution révélerait. Consacrer la même attention à chaque diff n'est pas de la rigueur. C'est un refus de décider où se trouve le risque.

Ce que vous cédez

Il y a un coût plus discret sous le coût technique. Accepter du code que vous n'avez pas compris vous fait glisser, un clic après l'autre, de l'auteur de votre produit à son consommateur. Vous gardez le crédit et vous gardez la responsabilité, le pager sonne toujours pour vous, mais vous avez cédé la compréhension sur laquelle les deux reposent. Des gens racontent avoir construit une application en un après-midi et s'être sentis étrangement à plat, puis avoir passé deux semaines à la faire vraiment fonctionner et s'être de nouveau sentis eux-mêmes. Ce n'est pas de la nostalgie. C'est la différence entre posséder la chose qui porte votre nom et détenir une copie que vous ne pouvez pas ouvrir.

Je construis des outils qui misent à fond sur la génération. L'un transforme une phrase en plan et ouvre ses propres pull requests. Un autre gouverne des flottes entières d'agents. Je ne suis pas là pour dire à qui que ce soit d'écrire moins de code par IA. Mais chacun de ces outils suppose, tout au bout, une personne qui relit à la profondeur que mérite le changement, et un garde-fou que l'on ne fait qu'entrapercevoir n'est pas un garde-fou. La machine peut produire le diff. Elle ne peut pas produire le moment où vous l'avez compris.

Alors relisez à vue autant que vous voulez, pour les changements où la vue est tout ce qu'il y a. Gardez seulement une ligne nette entre le diff que vous avez compris et le diff que vous avez seulement vu. Le premier est le vôtre. Le second le devient à l'instant où il casse, et il se moquera bien d'avoir eu l'air correct.

Les questions auxquelles cet article répond

  • Lire le diff suffit-il pour relire du code généré par IA ?

    Pour une grande part de ce que vous acceptez dans une journée, oui, et le lancer relèverait de la mise en scène : un renommage que le compilateur a déjà prouvé, une montée de version de dépendance, une valeur de configuration, un changement de texte, du boilerplate que vous vérifiez à sa forme. Les tests générés sont le cas le plus net, car c'est la lecture qui attrape le test qui n'affirme rien et passe quand même au vert. Cela cesse de suffire quand la justesse n'est pas dans le texte sous vos yeux : l'entrelacement de deux threads, la requête qui va bien sur dix lignes et se révèle fatale sur dix millions, le chemin d'erreur que le cas nominal traverse sans le voir, ou le contrôle d'autorisation qui n'apparaît jamais dans le diff parce qu'il n'a jamais été écrit.

  • L'agent a lancé les tests et me donne des logs au vert et une capture d'écran, pourquoi aller vérifier plus loin ?

    Parce que cette preuve a été produite par le même genre de système que celui qui a écrit le code, visant la même cible, avoir l'air correct. Un log au vert que vous n'avez pas vu tourner est une affirmation, pas un fait. Confier l'exécution à l'agent ne supprime pas la relecture, cela la déplace d'un cran, du diff vers la preuve : quand il vous dit que les logs montrent quelque chose, ouvrez les logs et trouvez-le. J'ai surpris un modèle à ne pas vraiment regarder et à répondre par ce qu'il s'attendait à trouver, et c'est le contrôle même qu'un chercheur néglige lorsqu'une citation inventée se retrouve dans un article publié.

  • Quelle quantité de code écrit par IA puis-je vraiment relire avant que ce ne soit plus une relecture ?

    Moins que ce qu'un agent vous donnera, puisqu'il produit quelques centaines de lignes dès que vous le demandez et qu'au-delà toute relecture humaine se dégrade en survol. Le travail de pure relecture est la forme la plus fatigante que prend le métier, parce que vous reconstruisez l'intention de quelqu'un d'autre, dans un style qui n'est pas le vôtre, à un rythme que vous n'avez pas fixé. Personne ne décide d'arrêter de relire. Vous vous mettez à voir le code au lieu de le comprendre, et vu de l'intérieur ces deux choses semblent identiques.

  • N'est-il pas plus sûr de relire chaque changement à fond ?

    Non. Consacrer la même attention à chaque diff n'est pas de la rigueur, c'est un refus de décider où se trouve le risque. Ajustez la profondeur à l'ampleur de l'impact et au caractère réversible du changement : un petit changement circonscrit et facile à annuler mérite une lecture rapide et un merge, tandis qu'une migration, une interface publique, une suppression ou tout ce qui touche à l'argent ou aux accès appelle la relecture lente, où vous le lancez, le tracez et cherchez à le casser. Le lancer vous-même n'est pas non plus l'option consciencieuse de dernier recours, car ouvrir la chose et regarder est souvent plus rapide que lire un mur de logs et un résumé plein d'assurance.