ConstructiCat Logo
CodeBust.
Browse section ▾

Évitement du refactoring.

Les assistants IA empilent du code nouveau et quasi dupliqué à côté de l'existant au lieu de le restructurer, si bien que la duplication grimpe et que le refactoring — le travail de remise en forme qui garde une base de code saine — disparaît discrètement.

##Signs and Symptoms

Un relecteur voit un diff composé presque entièrement d'ajouts. Le modèle a résolu le prompt en accolant une nouvelle fonction, branche ou fichier plutôt qu'en éditant l'abstraction qui aurait dû absorber le changement. Signes révélateurs :

  • Copier-coller avec retouches : une nouvelle fonction est identique à 90 % à une fonction existante, différant par un littéral, un nom de champ ou un if supplémentaire.
  • Quasi-doublons parallèles : formatUserCsv, formatAdminCsv, formatGuestCsv réécrivant tous à la main la même boucle au lieu d'une seule fonction paramétrée.
  • Prolifération de branches plutôt qu'extraction : une longue méthode gagne un nouveau else if au lieu que le modèle en extraie une stratégie ou une table de correspondance.
  • Réinvention : un deepClone/debounce/parseur de date écrit à la main alors que le dépôt importe déjà lodash, date-fns, etc. (OX Security appelle cela le « style vanille ».)
  • Sur-spécification : un helper hyper-spécifique à usage unique là où un générique existe déjà deux fichiers plus loin.
  • « Bugs déjà-vu » : le même correctif doit être appliqué dans trois copies parce que le doublon n'a jamais été factorisé (DRY).
// Existant dans le dépôt :
function priceWithTax(items: Item[]) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * 1.2; // TVA 20 %
}

// Ce que le modèle ajoute pour le nouveau cas « remisé » —
// une seconde copie entière au lieu d'un paramètre :
function priceWithTaxDiscounted(items: Item[], discount: number) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0); // dupliqué
  return subtotal * (1 - discount) * 1.2;                          // logique TVA dupliquée
}

L'odeur est structurelle, elle est donc surtout visible sur le changement entier, pas sur un seul bloc : beaucoup de vert, peu de code déplacé, et un détecteur de duplication qui s'allume sur les nouvelles lignes.

##Reasons for the Problem

Pourquoi les modèles le produisent

  • Biais additif du token suivant. Un LLM complète le prompt qui se trouve devant lui. Émettre un nouveau bloc autonome est la suite localement la plus probable et la moins risquée ; éditer une abstraction distante exige de tenir tout le module en mémoire de travail et de prédire une modification globalement cohérente, ce qui est plus difficile et n'est pas ce que le prompt immédiat récompense.
  • Contexte du dépôt ténu. Les assistants de codage chargent rarement la base de code entière. Si le modèle ne voit pas le helper, la bibliothèque ou la classe de base existants, il ne peut pas les réutiliser — alors il reconstruit. Le rapport d'OX Security présente cela comme l'effet « Armée de juniors » : beaucoup de code localement fonctionnel, aucune mémoire architecturale.
  • Le refactoring est risqué et non récompensé. Restructurer touche du code que le modèle n'a pas été chargé de modifier et peut casser des appelants et des tests. Un assistant complaisant, « fais juste que ça marche », minimise le rayon d'impact en ne touchant pas au code qui fonctionne — exactement le comportement qu'OX a mesuré comme « Évitement des refactorings » dans 80 à 90 % du code généré par IA, et « Sur-spécification » (à usage unique plutôt que réutilisable) dans 80 à 90 % de plus.
  • Obsolescence des données d'entraînement. Le modèle peut ignorer que le dépôt a adopté un module utilitaire ou mis à jour une bibliothèque après sa date de coupure, alors il réécrit à la main ce qui existe déjà.
  • Générer est bon marché, supprimer fait peur. Produire 40 nouvelles lignes ne coûte rien au modèle ; le convaincre de supprimer et de consolider 40 lignes existantes combat son instinct de préservation.

Pourquoi c'est nuisible

  • La duplication s'aggrave. L'analyse 2025 par GitClear de 211 millions de lignes modifiées a constaté que les lignes copiées-collées sont passées de ~8,3 % (2021) à 12,3 % (2024) — la première année où la duplication a dépassé le code « déplacé » (refactorisé) — tandis que les lignes refactorisées chutaient d'environ 25 % à moins de 10 %, soit une baisse d'environ 60 %. Les blocs de 5 lignes dupliquées et plus ont été multipliés par ~8 en 2024.
  • Maintenabilité et correction. Chaque clone est un endroit où un futur correctif peut être oublié — « Bugs déjà-vu », où le même défaut réapparaît et doit être corrigé N fois (OX : 70 à 80 % du code IA viole les principes de réutilisation de cette façon).
  • Accumulation de dette technique. Le refactoring est la remise en forme qui maintient l'entropie basse ; le supprimer signifie que la dette est créée mais jamais remboursée. La base de code grossit plus vite qu'elle ne s'améliore.
  • Charge de revue. Les relecteurs doivent désormais comparer à l'œil des blocs quasi identiques pour confirmer qu'ils sont intentionnellement identiques, le déplacement de charge cognitive que décrit la discussion « code smells for AI agents » de Stack Overflow — le travail passe de l'écriture à la revue et à la consolidation.

##Treatment

Tactiques de prompting / revue

  • Pointez le modèle vers ce qui existe. "Avant d'ajouter du code, cherche dans le dépôt un helper/util/classe de base existant et réutilise-le ; si rien ne convient, généralise le plus proche." Collez le module pertinent dans le contexte pour qu'il puisse réellement voir l'abstraction.
  • Contraignez la forme du diff. "Préfère éditer les fonctions existantes plutôt qu'en ajouter de nouvelles. Si deux chemins de code partagent une logique, extrais une fonction partagée (Extraire une fonction) plutôt que de dupliquer."
  • Faites-lui exécuter les outils. Exigez de l'assistant qu'il lance le détecteur de duplication (jscpd / PMD CPD) et le linter, et qu'il résolve toute nouvelle détection de bloc dupliqué avant de se déclarer terminé — cela transforme une odeur invisible en barrière qui échoue.
  • Demandez le refactoring explicitement comme deuxième étape. Génération et consolidation sont des tâches différentes ; faites « fais que ça marche », puis un « maintenant applique DRY et supprime la duplication » séparé, que les modèles gèrent bien mieux quand on le demande directement.
  • Ajoutez une barrière qualité en CI pour que la duplication ne puisse pas augmenter cran par cran sans être remarquée (seuil de lignes dupliquées Sonar, ou jscpd --threshold).

Le refactoring proprement dit — nommez les manœuvres classiques : Extraire une fonction, Paramétrer une fonction et Remonter / Remplacer une conditionnelle par du polymorphisme ou une table de correspondance pour tuer le Code dupliqué.

// Avant : deux fonctions quasi identiques (refactoring évité)
function priceWithTax(items: Item[]) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * 1.2;
}
function priceWithTaxDiscounted(items: Item[], discount: number) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return subtotal * (1 - discount) * 1.2;
}

// Après : Extraire une fonction + Paramétrer une fonction
const VAT = 1.2;
const subtotalOf = (items: Item[]) =>
  items.reduce((s, i) => s + i.price * i.qty, 0);

function priceWithTax(items: Item[], discount = 0) {
  return subtotalOf(items) * (1 - discount) * VAT;
}

Une seule source de vérité pour le sous-total et la TVA : un futur changement de la règle de taxe devient une modification d'une ligne au lieu d'une chasse en N endroits.

##Detected by