Relire du code écrit par une IA : ce que je regarde en premier
Ayoub
Tech Lead

En bref
Comment relire efficacement du code généré par une IA ?
En inversant l'ordre habituel de la revue. Le code généré est presque toujours correct en surface — syntaxe propre, nommage cohérent, tests qui passent — et ses défauts se logent ailleurs : gestion d'erreur optimiste, réinvention de ce qui existe déjà dans le projet, et cas limites traités par omission. Je commence donc par ce que le code ne fait pas, avant de lire ce qu'il fait.
Le code généré par une IA a une propriété désagréable : il a l’air bon. Le nommage est cohérent, la structure est nette, les commentaires sont présents, les tests passent. Une revue menée à l’habitude — lire de haut en bas, chercher ce qui cloche — le laisse passer.
Ses défauts sont réels, et ils sont ailleurs. Voici l’ordre dans lequel je les cherche, du plus fréquent au plus rare.
1. Ce qui existait déjà dans le projet
Défaut numéro un, et de loin.
Un modèle produit une solution autonome et correcte. Il ne sait pas que le projet a déjà un client HTTP configuré, une fonction de formatage de dates, un utilitaire de validation, une convention pour les erreurs métier. Il en réécrit une version — bonne, mais parallèle.
Sur une revue, ça ne saute pas aux yeux : le code ajouté est propre. C’est six mois plus tard que la facture arrive, quand on corrige un bug de formatage à un endroit et pas aux trois autres.
Ce que je fais : avant de lire la logique, je cherche dans le diff les fonctions utilitaires introduites, et pour chacune je vérifie qu’un équivalent n’existe pas. C’est mécanique, ça prend deux minutes, et c’est ce qui rapporte le plus.
2. La gestion d’erreur optimiste
Deuxième défaut structurel.
Le code généré traite le chemin nominal remarquablement bien. Sur les chemins d’échec, il produit
souvent quelque chose de plausible et creux : un try/catch qui journalise et continue, une
valeur par défaut qui masque le problème, une erreur remontée sans contexte exploitable.
Les questions que je pose au diff :
- que se passe-t-il si cet appel réseau expire — pas s’il échoue, s’il n’aboutit pas ?
- cette valeur par défaut est-elle un choix métier, ou un bouche-trou ?
- si cette exception remonte à l’utilisateur, l’aide-t-elle à comprendre quoi faire ?
Ce n’est pas propre à l’IA — un développeur pressé produit les mêmes raccourcis. La différence est le volume : le code arrive plus vite, donc ces raccourcis arrivent plus vite, et la revue est le seul point où on peut encore les attraper.
3. Les cas limites traités par omission
Le code généré ne se trompe pas souvent sur les cas limites : il les ignore. C’est plus difficile à repérer, parce qu’une absence ne se voit pas dans un diff.
Ceux que je vérifie systématiquement, parce qu’ils reviennent :
- la collection vide, distinguée de la collection absente ;
- la valeur nulle distinguée du zéro et de la chaîne vide ;
- la concurrence — deux exécutions simultanées de la même opération ;
- l’idempotence, quand l’opération peut être rejouée.
Sur ce dernier point, l’expérience est nette : un traitement généré est presque toujours écrit comme s’il ne s’exécutait qu’une fois. Dès qu’il y a une file, un webhook ou une reprise sur erreur, cette hypothèse est fausse.
Photo : domaine public (CC0).
4. Les tests qui vérifient l’implémentation
Un piège plus subtil, et le seul qui soit vraiment propre au code généré.
Quand on demande à un modèle d’écrire les tests d’une fonction qu’il vient d’écrire, il produit des tests qui décrivent ce que le code fait, pas ce qu’il devrait faire. Si la fonction contient un défaut de raisonnement, le test le consacre. La couverture est excellente, la protection est nulle.
Deux parades, simples :
- écrire les cas de test avant, ou au moins les dériver de la spécification et non du code ;
- lire les assertions en se demandant « est-ce que celle-ci échouerait si la règle métier était mal comprise ? ». Si la réponse est non, le test ne sert qu’à la couverture.
Photo : domaine public (CC0).
5. Les dépendances ajoutées sans nécessité
Défaut plus discret, et coûteux dans la durée.
Un modèle propose volontiers d’installer une bibliothèque pour une tâche que le langage ou le projet sait déjà faire : formater une date, valider une adresse, générer un identifiant. Le code qui en résulte est court et lisible, ce qui le rend difficile à refuser en revue.
Chaque dépendance ajoutée est pourtant une surface à maintenir : mises à jour, failles, licence, compatibilité. Sur une bibliothèque utilisée pour trois lignes, le calcul est rarement favorable.
Ma règle de revue : toute dépendance nouvelle doit être justifiée dans la description de la demande. Pas interdite — justifiée. Cela suffit à écarter la moitié des cas, parce que l’auteur se pose la question au moment d’écrire la justification.
La règle qui rend tout ça tenable
Une seule, et elle est plus importante que la liste : le développeur qui soumet le code en est responsable, qu’il l’ait écrit ou généré.
Cette règle paraît évidente. Sans elle, une zone grise s’installe très vite — « c’est l’IA qui l’a écrit » devient une explication recevable en revue, puis en incident. Elle ne l’est pas.
Concrètement, cela veut dire que soumettre du code qu’on n’a pas relu soi-même n’est pas acceptable, même si les tests passent. La revue par un pair vient après cette relecture, pas à la place.
6. Ce qui a été supprimé sans le dire
Le défaut le plus difficile à repérer, parce qu’il ne se voit pas dans ce qui est ajouté.
Quand on demande de retravailler une fonction, la réponse la réécrit entièrement — et perd au passage un cas particulier, un commentaire qui expliquait une décision, ou une garde ajoutée après un incident. Le code est plus propre, plus court, et il a régressé.
Ma règle : relire le diff, jamais le fichier. Un fichier réécrit se lit comme un nouveau fichier et paraît bon ; le diff montre les six lignes disparues. Et quand la modification proposée touche plus de lignes que le sujet ne le justifie, je redemande une version minimale plutôt que d’arbitrer moi-même ce qui a été perdu.
Ce que je fais avant même de relire
Deux vérifications mécaniques, qui économisent une relecture sur trois.
Les tests existants passent-ils encore ? S’ils échouent, la relecture n’a pas lieu d’être. Et s’ils ont été modifiés en même temps que le code, c’est le premier point à examiner.
La modification compile-t-elle sans avertissement nouveau ? Un import inutilisé, une variable
morte, un type élargi en any : ce sont des traces de code produit sans être exécuté, et elles
signalent où regarder de près.
Ce que ça change au rythme
Une conséquence qu’on n’anticipe pas : la production de code s’accélère nettement, la revue non. Le goulot d’étranglement se déplace.
Une équipe qui adopte la génération sans ajuster sa capacité de revue accumule une file de demandes en attente, puis relâche la vigilance pour la résorber — et perd exactement le bénéfice qu’elle cherchait. Chez nous, cela s’est traduit par des demandes plus petites et plus fréquentes : un diff de deux cents lignes se relit sérieusement, un diff de mille lignes se survole, quelle que soit la bonne volonté du relecteur.