Logo ConstructiCat
CodeBust.
Parcourir la section ▾

Duplication au lieu d'extraction.

Lorsqu'ils écrivent du nouveau code, les assistants IA ont tendance à coller ou à réécrire une copie légèrement modifiée d'une logique qui existe déjà au lieu d'appeler ou d'extraire une fonction partagée, si bien que le code dupliqué grossit à chaque nouvelle fonctionnalité.

##Signes et symptômes

Un relecteur reconnaît ce smell quand un diff rédigé par IA ajoute un bloc qui existe déjà quelque part dans le dépôt, légèrement renommé, au lieu d'appeler la fonction qui est déjà là. La forme révélatrice est deux corps quasi identiques ou plus qui ne diffèrent que par un nom (user/order), un endpoint ou une constante — le genre de copie qu'un humain aurait factorisée.

Signaux courants :

  • De nouveaux handlers/services qui répètent verbatim la même validation, le même retry, le même mapping d'erreurs ou le même boilerplate de fetch.
  • Des chaînes magiques et littéraux de configuration re-saisis dans chaque nouveau bloc plutôt que référencés depuis un seul endroit.
  • Chaque tour de l'agent réimplémente une logique que le tour précédent avait déjà écrite (l'agent n'a aucune mémoire qu'un helper existe).
  • Un détecteur de clones (jscpd, PMD CPD, les métriques de duplication de Sonar) signale de nouveaux blocs dupliqués — ou un bond de duplicated_lines_density — sur la PR qui a ajouté la fonctionnalité.
// L'IA a ajouté ceci pour la nouvelle route — mais getUser existe déjà 30 lignes plus haut
async function getOrder(id: string) {
  const res = await fetch(`${API}/orders/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

async function getUser(id: string) {                 // <-- corps identique à 95 %
  const res = await fetch(`${API}/users/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

C'est le smell classique Code dupliqué, mais produit systématiquement plutôt que par accident.

Proche mais distinct : l'Évitement du refactoring consiste à ne jamais restructurer le code existant — le nouveau comportement est greffé par-dessus au lieu de remodeler la conception. Ce smell-ci est plus étroit et survient au moment de l'écriture : le modèle réécrit une logique qui a déjà sa place au lieu de l'appeler ou de l'extraire.

##Causes du problème

Pourquoi les modèles le produisent

  • Localité du token suivant. Un LLM complète l'étendue courante à partir du texte le plus similaire de sa fenêtre de contexte. Reproduire un bloc proche et connu-bon est la continuation à plus forte probabilité ; « s'arrêter, aller définir un helper, puis l'appeler » est un détour plus long, à plus faible probabilité, qui s'étend sur plusieurs fichiers.
  • Aucun modèle à l'échelle du dépôt. L'assistant sait rarement qu'un helper approprié existe déjà hors de son contexte. Sans récupération, il ne peut pas réutiliser ce qu'il ne voit pas, alors il régénère. Eno Reyes (Factory) relie le bénéfice de l'IA à la base de code elle-même — « plus la base de code était de qualité, plus l'IA accélérait l'organisation » (entretien sur le blog de Stack Overflow) — et les clones déjà présents dans le dépôt deviennent le modèle que la génération suivante recopie.
  • Amnésie par tour + portée du prompt. Chaque requête est traitée comme une unité autonome (« fais marcher ceci »), donc l'agent sur-ajuste une solution étroite à usage unique au lieu de généraliser. OX Security appelle cela sur-spécification (Over-Specification) — « des solutions hyper-spécifiques à usage unique, impossibles à adapter à d'autres usages » — et l'a relevée dans 80–90 % du code IA étudié (rapport OX).
  • Complaisance / moindre friction. Les modèles optimisent pour une sortie qui satisfait visiblement la demande sans toucher à des fichiers non liés. Copier un bloc reste confiné au fichier en cours d'édition ; appeler un helper partagé — ou l'extraire — oblige à toucher un autre module que le modèle n'a pas entièrement vu, donc la copie locale l'emporte.
  • L'outillage rend cela bon marché. Comme le souligne le résumé du rapport GitClear par DevClass, accepter une suggestion avec la touche Tab rend l'insertion d'un bloc frais quasi gratuite, supprimant la friction qui poussait autrefois les développeurs vers la réutilisation (DevClass).

Pourquoi c'est nuisible

  • Érosion mesurée de la qualité. L'analyse 2025 de GitClear, portant sur 211 millions de lignes modifiées (2020–2024), montre que les lignes associées au refactoring (lignes « déplacées ») sont passées de 25 % des lignes modifiées en 2021 à moins de 10 % en 2024, tandis que les lignes copiées-collées passaient de 8,3 % à 12,3 % sur la même période ; 2024 est la première année où les lignes copiées-collées dépassent les lignes déplacées, et les blocs d'au moins cinq lignes dupliquées ont été multipliés par huit environ au cours de 2024 (GitClear 2025).
  • Dette d'exactitude — « bugs déjà-vu ». OX Security a trouvé cela dans 70–80 % du code IA : un bug corrigé dans un clone survit silencieusement dans tous les autres, donc des défauts identiques se reproduisent et chacun nécessite une correction redondante. La duplication transforme un bug en N bugs.
  • Maintenabilité & charge de revue. Chaque changement de comportement doit être trouvé et édité à de nombreux endroits ; les diffs gonflent de blocs quasi identiques que les relecteurs doivent comparer ligne par ligne pour confirmer qu'ils sont réellement identiques (et pas subtilement, dangereusement différents).
  • Surface de sécurité. Un pattern vulnérable (vérification d'authentification manquante, entrée non échappée) se propage dans chaque copie, et un durcissement ultérieur peut manquer des clones — la duplication multiplie la surface de correctif.

##Traitement

Tactiques de revue & de prompting

  • Dites au modèle où chercher d'abord : « Avant d'écrire du nouveau code, cherche dans le dépôt un helper existant qui fait ceci et réutilise-le ; ne duplique pas. » Collez le module pertinent pour qu'il soit dans le contexte.
  • Exigez la consolidation explicitement : « Si deux blocs ne diffèrent que par des paramètres, extrais une seule fonction paramétrée et appelle-la depuis les deux. » Autrement dit, Extraire une méthode suivi de Paramétrer la méthode.
  • Faites-lui exécuter les outils : « Exécute le linter / jscpd / la vérification de duplication et résous tout clone qu'il signale avant de rendre le diff. » Les agents se corrigent bien quand on leur donne un signal déterministe.
  • En revue, traitez un bloc d'apparence collée comme une invitation à grep : cherchez une ligne distinctive ; si elle existe déjà, renvoyez-le pour extraction.

Le refactoring — Extraire une méthode + Paramétrer la méthode

// après : une seule source de vérité, appelée depuis les deux
async function getResource<T>(path: string): Promise<T> {
  const res = await fetch(`${API}/${path}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data as T;
}

const getOrder = (id: string) => getResource<Order>(`orders/${id}`);
const getUser  = (id: string) => getResource<User>(`users/${id}`);

Désormais, une correction de la gestion d'erreurs ou de l'analyse des réponses n'est appliquée qu'une seule fois. Lorsque les doublons diffèrent par une étape plutôt que par une valeur, préférez Constituer une méthode patron ou passez l'étape variable en callback. Les manœuvres canoniques ici sont Extraire une méthode, Extraire une variable ou Remplacer un nombre magique par une constante symbolique (pour les littéraux re-saisis), et Remonter une méthode lorsque les clones vivent dans des classes sœurs — autant de remèdes standard au Code dupliqué.

Garde-fou, pas seulement nettoyage : branchez un détecteur de clones dans la CI avec un seuil bas afin que le prochain doublon généré par IA fasse échouer le build au lieu d'être fusionné. Associez-le à une règle maison d'une ligne dans CLAUDE.md/AGENTS.md (« réutilise les helpers existants ; ne colle jamais un quasi-doublon ») pour que la contrainte soit présente dans le contexte du modèle à chaque tour.

##Détecté par

  • jscpd --min-tokens / --min-lines / --threshold — Duplication par copier-coller (seuils min-tokens / min-lines configurables ; prend en charge le contrôle --threshold en CI)
  • PMD CPD — CPD — détection des duplications par jetons dans plus de 30 langages
  • SonarQube / SonarSource S4144 — Les méthodes ne doivent pas avoir des implémentations identiques
  • SonarQube / SonarSource S1192 — Les littéraux de chaîne ne doivent pas être dupliqués
  • SonarQube duplicated_lines_density — Blocs dupliqués / métrique duplicated_lines_density (moteur intégré de détection des duplications ; échec du contrôle qualité au-delà du seuil)
  • eslint-plugin-sonarjs sonarjs/no-identical-functions
  • eslint-plugin-sonarjs sonarjs/no-duplicate-string