La propreté du code influence-t-elle les agents de codage ? Une étude contrôlée par paires minimales
(arxiv.org)- Les évaluations des agents de codage autonomes se sont surtout intéressées au taux de réussite des tâches sur des bases de code fixes, mais cette étude isole et mesure si la propreté du code lui-même modifie les coûts d’exploration et de modification
- Elle constitue 6 dépôts en paires minimales, identiques en architecture, dépendances et comportement externe, mais ne différant que par les violations de règles SonarQube et la complexité cognitive, ainsi que 33 tâches fondées sur des tests cachés
- Avec Claude Code et Claude Sonnet 4.6, chaque tâche est exécutée 10 fois de chaque côté de chaque paire de dépôts, pour un total de 660 expériences ; l’agent ne sait pas de quel côté du code il se trouve
- La propreté du code ne change pas le taux de réussite, mais dans le code plus propre les métriques équivalentes en tokens diminuent de 7 à 8 % et les revisites de fichiers baissent de 34 %
- Au-delà du modèle ou du prompt, l’état de la base de code reste une variable pratique qui détermine le coût de calcul et l’efficacité d’exploration des agents
Question de recherche et cadrage du problème
- Les agents de codage autonomes se diffusent rapidement
- Dans une enquête menée en 2026 sur 128 018 projets GitHub, moins d’un an après la sortie des premiers agents pratiques, des traces d’activité d’agents ont été trouvées dans 22 à 29 % des projets
- Le coût d’exécution des agents n’est pas négligeable non plus
- Sur SWE-bench Verified, une tâche unique consomme en moyenne environ 4 millions de tokens avec des LLM de pointe
- Dans l’usage total, les tokens d’entrée représentent la grande majorité
- Les évaluations existantes se sont concentrées sur le fait de résoudre ou non la tâche dans des benchmarks comme SWE-bench, et des travaux récents commencent à mesurer aussi l’utilisation des ressources en parallèle du taux de réussite
- La comparaison classique consistait à fixer la base de code puis à changer l’agent ou l’échafaudage
- Cette étude fait l’inverse : elle fixe l’agent et les tâches, et ne fait varier que la propreté de la base de code
Propreté du code et construction de paires minimales
- La propreté du code est traitée comme un ensemble de propriétés liées à la maintenabilité du code
- Lisibilité
- Faible complexité cognitive
- Helpers bien séparés
- Noms explicites
- Peu de code mort, de logique dupliquée et de couplage accidentel
- L’étude ne fige pas la propreté du code dans une définition formelle stricte, et utilise comme proxy souple le nombre de violations de règles d’analyse statique SonarQube
- L’outil utilisé est SonarQube Cloud Enterprise Edition
- Le jeu de règles est le “default quality gate”
- Les dépôts en paires minimales sont construits de façon à ne différer que par leur propreté interne, tout en respectant les conditions suivantes
- Même langage et même framework
- Mêmes dépendances
- Mêmes tests ou couverture de tests équivalente
- Même comportement externe
- L’équivalence comportementale est définie comme le fait de produire les mêmes sorties et transitions d’état observables de l’extérieur pour les mêmes entrées
- En pratique, la validation consiste à passer la même suite de tests avec la même couverture, ou à passer des paires de tests ajustées pour des changements non observables, comme lors d’un refactoring
Pipeline de génération des paires minimales : Slopify et Vibeclean
- Les paires minimales sont créées dans les deux directions
- Slopify, qui rend plus désordonné un dépôt propre
- Vibeclean, qui nettoie un dépôt désordonné
-
Slopify
- Transforme une base de code propre en une version qui semble avoir grandi sans code review ni linting
- Ne vise pas un code volontairement cassé, mais une version d’histoire alternative telle qu’elle aurait pu exister sans analyse statique
- Trois étapes sont chacune réalisées par un nouvel agent
- Build : construit le dépôt, fait passer les tests, puis fige les commandes dans
build instructions.md - Explore : parcourt le dépôt et rédige un
summary.mdpour chaque répertoire à nettoyer - Transform : introduit des violations de règles SonarQube dans les répertoires désignés et relance les tests après chaque passe afin de rejeter les modifications cassées
- Build : construit le dépôt, fait passer les tests, puis fige les commandes dans
- Augmente la complexité cognitive par l’inlining de helpers, la duplication de logique par chemin, l’ajout de code mort et la fusion de certains modules en un seul fichier
-
Vibeclean
- Nettoie une base de code naturellement riche en violations de règles tout en préservant son comportement externe
- La liste de tâches de l’agent correspond à la liste des problèmes détectés par l’analyseur, chaque problème étant lié à une plage de code
- Le périmètre de modification est limité aux problèmes signalés par l’analyseur ; une refonte complète n’est pas l’objectif
- Fonctionne en deux étapes
- Build : vérifie les commandes de build et de test, puis les fige dans
build instructions.md - Clean : nettoie mécaniquement les violations des règles de l’analyseur module par module, puis vérifie l’équivalence comportementale avec les tests après le traitement de chaque module
- Build : vérifie les commandes de build et de test, puis les fige dans
- Supprime les doublons de chaînes littérales, efface le code commenté, remplace des idiomes de collections hérités et élimine les branches mortes
- Lorsque l’analyseur signale de véritables méga-structures, il transforme par exemple un
dispatch switchde plus de 200 lignes en helpers nommés, ou extrait des helpers de persistance d’une classe de 2 800 lignes - Cependant, l’extraction peut redistribuer la complexité sur davantage de méthodes plutôt que la supprimer, et certaines des plus grandes méga-structures restent en
wontfix
Dépôts de benchmark et conception des tâches
- Le benchmark est construit sur Harbor framework v0.4.0
- Au total, 6 dépôts en paires minimales sont utilisés
- 3 principalement en Java, 3 principalement en Python
- Certains dépôts incluent une petite quantité de code dans d’autres langages
- Ils se composent de 3 dépôts open source publics et de 3 bases de code SonarSource privées
- Les paires privées servent de protection contre la mémorisation, au cas où le LLM évalué aurait appris sur les dépôts publics
- Les principaux chiffres pour le côté propre et le côté désordonné de chaque dépôt sont les suivants
sonar-sca*: 94 / 2 825 problèmes, densité de problèmes 0,73 / 20,66, densité de complexité cognitive 30,6 / 56,5sonar-caas-poc*: 16 / 855 problèmes, densité de problèmes 0,61 / 27,16, densité de complexité cognitive 179,8 / 218,9sonarcloud-codedatalake*: 199 / 1 319 problèmes, densité de problèmes 4,36 / 34,39, densité de complexité cognitive 34,0 / 216,5commons-bcel: 694 / 2 711 problèmes, densité de problèmes 12,60 / 49,46, densité de complexité cognitive 102,8 / 108,3genie: 152 / 1 262 problèmes, densité de problèmes 1,28 / 10,81, densité de complexité cognitive 22,2 / 23,5ckan: 1 006 / 3 632 problèmes, densité de problèmes 7,54 / 27,50, densité de complexité cognitive 69,3 / 76,5
- La conception des tâches suit trois règles
- Passage par des hotspots : placer les tâches de sorte qu’elles traversent, des deux côtés de la paire, des zones de code où l’écart de densité de problèmes et de complexité cognitive est important
- Description observable de l’extérieur : ne fournir que les entrées, sorties et scénarios d’exemple, sans indiquer de noms de fichiers, de fonctions ni de structures internes
- Tests par surface publique : exécuter les tests cachés via les interfaces que l’application expose aux appelants, comme la CLI, les routes HTTP ou les bibliothèques/API
- La génération des tâches est partagée entre des agents et des humains
- Un agent compare les variantes propre et désordonnée pour établir une carte des différences
- Un autre agent rédige des aperçus de tâches et évalue leur testabilité
- Un humain sélectionne, édite et organise les aperçus plausibles et intéressants
- Un troisième agent crée les consignes réelles, les tests cachés par surface publique et une implémentation de référence interne
- L’implémentation de référence doit passer les tests cachés dans les deux dépôts
- Les dépôts avant modification ne doivent pas passer les tests cachés
- Les tâches qui ne satisfont toujours pas ces conditions après deux itérations sont réécrites ou supprimées par un humain
- Le nombre final de tâches est de 33, réparties en trois pistes
- 13 tâches de hotspots cognitifs : traversent une zone de forte complexité dans une seule méthode ou une seule classe
- 14 tâches multi-modules : nécessitent des modifications sur au moins deux modules
- 6 tâches de calibration : les deux côtés exécutent une tâche simple dans la même zone afin de vérifier l’existence de variations indépendantes de la propreté
Configuration expérimentale et métriques de mesure
- Toutes les expériences sont réalisées avec Claude Code et son jeu d’outils par défaut
- Les chiffres rapportés proviennent d’exécutions avec Claude Sonnet 4.6
- Claude Haiku 4.5 a également été passé rapidement sur le même ensemble de tâches, mais son taux de réussite était trop faible pour permettre de lire clairement les différences d’empreinte ; il est donc exclu des résultats principaux
- L’agent ne lit que la description de la tâche
- Il ne reçoit aucun priming supplémentaire sur la propreté du code
- Il ne sait pas de quel côté de la paire minimale il travaille
- Chaque tâche est exécutée 10 fois de chaque côté de la paire
- Le nombre total d’expériences est de 33 × 2 × 10 = 660
- Chaque exécution se déroule dans un bac à sable conteneurisé
- CPU, mémoire, stockage et temps écoulé sont limités
- L’accès aux registres de paquets publics est autorisé
- L’image de base inclut les toolchains, caches de build et services propres à chaque dépôt
- Au sein d’une paire, seul l’arbre de sources monté dans
/appdiffère
- 10 métriques sont enregistrées
- Taux de réussite : proportion de tests cachés qui passent à l’état final
- Tokens d’entrée : nombre de tokens lus par le modèle sur tous les tours, constitué principalement du contenu de fichiers et du renvoi de l’historique de conversation
- Tokens de sortie : ensemble des productions du modèle et des sous-agents, incluant prose, code, traces de raisonnement et appels d’outils
- Nombre de caractères de raisonnement : l’API Anthropic n’exposant pas séparément les tokens de raisonnement, l’étude compte les caractères en texte brut des blocs de contenu de raisonnement
- Nombre de tours de conversation : total des échanges agent-outil
- Nombre de tours avant la première édition : tours nécessaires avant la première modification de fichier
- Nombre de caractères avant la première édition : caractères de conversation sur le même intervalle
- Nombre de fichiers lus : nombre de fichiers distincts ouverts pendant l’exécution
- Revisites de fichiers : nombre de fois où un fichier déjà lu et modifié est relu
- Nombre de lignes modifiées : lignes de source modifiées par le patch final
- Les revisites de fichiers apparaissent dans des flux comme
lecture → édition → autre travail possible → relecture- L’étude les interprète comme un signal d’incertitude sur les éditions précédentes plutôt que comme une exploration large
- Les métriques d’empreinte des agents pouvant beaucoup varier même lorsque la même tâche est répétée à température fixe, un filtrage des valeurs aberrantes est appliqué
- Au sein de chaque combinaison
(tâche, côté), les exécutions qui s’écartent de plus de 50 % de la médiane des 10 répétitions sont retirées avant le calcul de la moyenne - En pratique, 9,7 % de l’ensemble des exécutions sont supprimées
- Au sein de chaque combinaison
- Les chiffres au niveau du dataset sont des micro-moyennes sur les 33 tâches
- Pour chaque métrique, les moyennes côté propre et côté désordonné par tâche sont additionnées, puis la différence relative est calculée
- Par exception, le taux de réussite est rapporté comme une différence absolue en points de pourcentage entre côté propre et côté désordonné
Résultats : un effet sur le coût d’exploration plus que sur le taux de succès
- La propreté du code ne produit pas de changement significatif du taux de réussite de l’agent
- Dans le code plus propre, les métriques équivalentes en tokens diminuent de 7 à 8 %
- Les revisites de fichiers baissent de 34 %, montrant qu’un code propre peut réduire le coût de relecture pour une même tâche
- La propreté du code peut donc être considérée comme un axe distinct, aux côtés du choix du modèle, du harness et du prompt, dans les coûts d’exécution des agents
1 commentaires
Avis sur Hacker News
Question intéressante à étudier, mais je suis très sceptique quant au protocole expérimental
Dans l’expérience, ils ont synthétisé des bases de code « dégradées » ou « nettoyées » avec Opus 4.6 pour les comparer
Pire encore, ils n’ont pas contrôlé si les tests de l’application avaient été cassés
« Le taux de réussite note l’état final de l’agent selon des tests cachés que nous avons écrits pour chaque tâche. Nous ne vérifions pas si l’agent a cassé des tests sans rapport déjà présents dans le dépôt, et même si les solutions côté propre et côté désordonné réussissent toutes deux les tests cachés, elles peuvent différer sur des tests non notés »
Si la qualité du livrable final n’est pas contrôlée, les conclusions sur la consommation de tokens me semblent presque dénuées de sens
Pour comparer équitablement, il faudrait écrire un seul ensemble de tests fonctionnant sur les deux projets de chaque paire
Cela ne veut pas dire que l’étude est bonne, mais comme la réussite des tests n’est pas forcément corrélée à l’efficacité de l’agent, je peux comprendre ce choix
Cela montre simplement une situation où le code semble fonctionnellement correct, mais coûte davantage de tokens pour terminer la tâche
La lassitude face à l’IA est devenue ennuyeuse depuis longtemps, et maintenant elle est simplement douloureuse
D’après mon expérience, l’écart de performance des agents est considérable entre une base de code remplie de code mort, de doublons, de chemins alternatifs inaccessibles, d’abstractions qui fuient et de design patterns pas mûrs, et une base où les flux de données sont clairs, avec une encapsulation et une structure propres
Sur du mauvais code, j’ai vu tous les modèles de pointe devoir passer par plusieurs cycles de revue de code, de contrôle qualité et de corrections ; sur du bon code, ils tombent juste dès la première ou la deuxième tentative
On peut demander au LLM d’exécuter des scripts qui vérifient ces points, et imposer les mêmes scripts via un hook pre-commit
Mettre en place rigoureusement ce type de configuration dans toutes les bases de code sur lesquelles je travaille a eu l’effet le plus important sur le codage agentique
J’ai écrit plus en détail sur les différents linters que j’utilise ici : https://www.balajeerc.info/Use-Deterministic-Guardrails-for-...
À mon avis, un agent ne peut que mieux s’en sortir sur une base de code propre que sur un énorme bazar
C’est pareil que lorsqu’il dispose de spécifications bien faites et d’un accès à la documentation
À un moment, les patterns horribles commencent aussi à déteindre sur moi
Il y a ici un article qui tente de répondre à la question, et les témoignages anecdotiques ne font que biaiser le lecteur, sans apporter de valeur pour conclure objectivement sur le problème
La discussion la plus utile serait que tout le monde lise l’article et critique sa méthodologie ou ses résultats
Cela dit, les gens qui se plaignent que les LLM ne sont pas si bons semblent généralement être le genre à avoir des bases de code désordonnées
Une astuce que j’ai vue bien fonctionner, en Python, consiste à demander un refactoring comme ceci
« Refactorise le code Python pour le rendre plus pythonique. Par exemple, réduis les classes et les singletons, surtout si cela améliore les performances. Le code Python doit impérativement respecter les standards d’organisation du code attendus dans les packages Python open source populaires, sans régression de performance dans les benchmarks »
Pour du code Rust, j’ai essayé cette variante
« La base de code Rust dans
/srcs’est hypertrophiée en plusieurs fichiers de plus de 1 000 lignes. Refactorise la base de code Rust pour respecter les standards d’organisation du code attendus dans les projets Rust open source populaires, sans régression de performance dans les benchmarks »Ce genre de prompt semble améliorer les performances de l’agent parce qu’il a) réorganise le code de façon logique et b) les noms de fichiers donnent des indices sémantiques sur l’emplacement du code pertinent
Dans un gros fichier de 5 000 lignes, l’agent est inefficace parce qu’il doit lire plusieurs blocs pour trouver le code pertinent
Les performances des benchmarks s’améliorent aussi généralement après le refactoring, surtout en Rust compilé ; c’est peut-être un hasard, mais je ne vais pas m’en plaindre
Par défaut, les outils de codage agentique ont tendance à hésiter à supprimer du code. Même quand on leur demande de le faire, ils font tout pour conserver l’ancien code ou ajouter de la complexité afin qu’il puisse encore être appelé
Quand on ne fait que du prototypage, c’est vraiment pénible : on finit par accumuler beaucoup de code mort, ce qui crée de la confusion plus tard lorsqu’on veut ajouter des fonctionnalités
Une fois qu’on le sait, il suffit de leur demander de supprimer le legacy
Garder la base de code propre incite l’IA à faire ce qu’il faut. S’il y a beaucoup de tests, elle en ajoute quand elle crée de nouvelles fonctionnalités ; s’il y a de la documentation, elle la met à jour sans qu’on ait à le demander
À mesure que les harnais de code s’amélioreront, ces choses seront de plus en plus intégrées, et même les personnes ayant peu d’expérience en prompting obtiendront plus facilement de bons résultats
En général, je lui demande d’abord d’examiner le code et de produire une liste de points à revoir, puis nous passons chaque point ensemble et je décide oui/non ou je propose des ajustements supplémentaires
Surtout s’il n’y a pas de tests end-to-end soigneux sur tout le système
De toute façon, j’ai déjà mis les règles de style de code dans CLAUDE.md
L’approche consistant à créer un « pipeline d’agents qui nettoie des dépôts en désordre » me paraît horrible, et suffit à elle seule à invalider toute l’étude
Il semble que la moitié des paires minimales de ce travail aient été produites de cette manière
Je n’accorderais aucune confiance à une conclusion qui suppose que des dépôts « nettoyés » par l’IA représentent réellement de bonnes bases de code
Ici, « propre » ne signifie pas qu’on a simplement demandé à l’agent d’écrire du meilleur code
Nous lui avons fourni une liste de 50 à 100 violations de règles d’analyse statique, ainsi que le nombre de lignes de code, puis nous lui avons demandé de les supprimer
Ensuite, nous avons vérifié que les violations de règles avaient bien été corrigées
Réécrire du code avec un LLM pour supprimer ce type de violations est une pratique assez acceptée
L’approche existante de Sonar, en one-shot et basée sur un LLM [1], est en production depuis plus d’un an, et leur approche agentique récente [2] accomplit aussi assez bien la même tâche
[1] https://www.sonarsource.com/solutions/ai/ai-codefix/
[2] https://www.sonarsource.com/products/sonarqube/remediation-a...
Bien sûr que cela ne peut qu’avoir un effet, à mon avis
Aucun modèle ne peut mettre en contexte une base de code réelle entière, et il doit parcourir le code comme le ferait un humain
Il cherche et lit des fichiers
Si le fichier est à l’endroit attendu et porte le nom qu’un modèle ou un humain chercherait en premier, il le trouve du premier coup ; sinon, il faut des recherches plus profondes et plusieurs tentatives
Il suffit de voir tous les chemins, d’ignorer les zones déjà traitées et de passer au chemin suivant
C’est assez proche de la manière dont travaillent les développeurs
Nous avons observé des résultats similaires dans un travail proche en cours au NJIT. Nous appelons cela la contagion contextuelle de la qualité
Ce qui est intéressant ici, ce sont les situations réelles courantes dans l’industrie : des bases de code à qualité mixte, ou des bases mêlant des patterns legacy et des patterns plus récents et « meilleurs », ce qui amène l’agent à confondre les conventions
La conception en paires minimales est en fait l’un des points forts : elle tente d’isoler la propreté d’autres facteurs comme la structure, les dépendances ou les tests, plutôt que de comparer des dépôts entre eux
Cela dit, utiliser du code « dégradé » généré par un LLM est un peu discutable, dans la mesure où ce n’est pas une méthode mécanique ni guidée par des humains
La principale critique, comme d’autres l’ont justement souligné, est le choix de ne pas vérifier toute la suite de tests. L’affirmation d’« équivalence comportementale » ne vaut que dans la mesure des tests et de leur couverture
Cette hypothèse est convaincante pour deux raisons. 1) Les LLM imitent ce qu’ils voient dans la base de code, donc l’idée « garbage in, garbage out » se tient 2) Elle correspond à ce que beaucoup d’ingénieurs ont ressenti intuitivement en utilisant ces modèles ces un ou deux dernières années
Le greenfield est presque toujours plus facile que de rejoindre une base de code chargée, et le désordre apparaît dans les intégrations complexes et la maintenance de systèmes aux objectifs legacy
Même si l’agent apprend à se débrouiller avec les stubs et le code WET qu’il laisse derrière lui, veut-on vraiment d’une base de code où les humains ne peuvent pas suivre ce qui se passe réellement ?
Personnellement, je veux donc au minimum que le code raconte par le code ce qu’il fait
Même si deux petites fonctions contiennent une logique qu’on pourrait extraire dans un helper partagé, un programmeur humain sait parfois que cette abstraction serait sale et se casserait dès qu’on voudrait modifier légèrement l’une des deux, donc il ne le fait pas
C’est intéressant de voir cela quantifié
Une structure propre semble réduire la charge cognitive, aussi bien pour les humains que pour les agents, ce qui explique pourquoi le nommage et la modularisation comptent plus qu’on ne le pense
C’est difficile à quantifier, mais c’est bien ce que tous les indicateurs de qualité du code cherchent à capturer au bout du compte
Vu sous cet angle, tant que les métriques de qualité utilisées sont raisonnables, la conclusion n’a rien de très surprenant
Si une métrique de qualité du code est bonne dans le contexte des agents de codage, c’est exactement le résultat auquel on peut s’attendre
Beaucoup de tokens sont dépensés dans l’exploration du code : trouver le code, suivre les points d’appel et constituer suffisamment de contexte pour accomplir la tâche
Si l’on donne à l’agent un accès à une forme de LSP et, dans le cas d’un monorepo, des indications hiérarchiques via des fichiers comme AGENTS.md, on peut réduire fortement la consommation de tokens liée à l’exploration
Mais une base de code dispersée nécessite au final une forme d’exploration pour résoudre chaque tâche
Et cette exploration ne se résume pas à une simple consommation de tokens. À chaque étape se répètent des allers-retours : latence du LLM, préremplissage, décodage, sortie, parsing par l’agent, appel d’outil, réponse de l’outil, puis retour vers le LLM
Une partie peut être parallélisée, mais en pratique la plupart reste séquentielle, ce qui ralentit fortement le travail
Pour utiliser efficacement les agents, localité et structure sont essentielles. La fenêtre de contexte est toujours limitée, et l’attention à l’intérieur de cette fenêtre n’est pas constante
D’après mon expérience, tout ce qui affecte les ingénieurs affecte aussi les agents
De bonnes abstractions, des méthodes de taille raisonnable, de bons noms, une structure de principe à l’intérieur des services et entre eux, des tests unitaires, etc.
Historiquement, tout cela faisait partie du travail des ingénieurs et servait à faciliter la contribution d’autres personnes au code
Désormais, cela facilite aussi la contribution au code par d’autres agents