ConstructiCat Logo
CodeBust.
Browse section ▾

Réinvention de la roue.

Le modèle écrit à la main du code sur mesure pour quelque chose qu'une bibliothèque standard, une dépendance existante ou un helper déjà présent dans le dépôt fait déjà — ajoutant une logique dupliquée et moins testée au lieu d'appeler ce qui existe déjà.

##Signs and Symptoms

Un relecteur repère une réinvention de la roue lorsqu'un diff introduit un morceau non trivial de logique « repartie de zéro » pour résoudre un problème déjà résolu — par le runtime du langage, par une dépendance déjà présente dans package.json, ou par un utilitaire qui existe déjà ailleurs dans le dépôt. Indices courants :

  • Un deepClone, debounce, groupBy, chunk, retry, slugify, deepMerge ou uuid écrit à la main alors que le runtime ou une bibliothèque installée le fournit.
  • Une regex d'e-mail/URL/UUID sur mesure au lieu d'un validateur qui est déjà une dépendance.
  • Des calculs de dates, du parsing de query-string ou une logique de pagination sur mesure qui réimplémentent Intl/URLSearchParams/une fonctionnalité d'ORM.
  • Deux ou trois helpers privés quasi identiques répartis dans des fichiers (la même roue retaillée par fonctionnalité) — la duplication qui apparaît dans les métriques de copier-coller.
  • La version réinventée est subtilement fausse : elle gère le chemin heureux mais manque les cas limites que l'original éprouvé couvre.
// Écrit par IA : un deep clone sur mesure réinventé en ligne
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out; // perd silencieusement Date, Map, Set, RegExp ; boucle sur les cycles
}
// ...alors même que le runtime fournit structuredClone(), ET
// que le dépôt exporte déjà cloneDeep() depuis src/utils/object.ts

La vérification la plus rapide : cherchez la capacité dans le dépôt et dans node_modules avant d'accepter le nouveau code. Si structuredClone, lodash, date-fns ou un helper local la couvre déjà, la nouvelle fonction est une roue réinventée.

##Reasons for the Problem

Pourquoi les modèles le produisent

  • Aucun contexte du dépôt par défaut. Le modèle ne voit souvent pas votre src/utils, vos dépendances installées ni vos conventions maison, alors il recourt à la complétion statistiquement la plus probable : une implémentation autonome en ligne. Il réinvente la roue parce qu'il n'a jamais vu la vôtre.
  • Localité du token suivant. Les LLM optimisent une suite localement plausible, pas une minimalité globale. Écrire function groupBy(...) est une séquence à forte probabilité ; s'arrêter pour découvrir que lodash.groupBy est déjà importé trois fichiers plus loin n'est pas ce que fait la prédiction de token.
  • Les incitations d'entraînement récompensent l'autonomie. Une énorme part des données d'entraînement est constituée de tutoriels, de réponses Stack Overflow et d'extraits qui montrent délibérément l'implémentation complète. Le modèle a appris que « bien répondre » signifie émettre du code complet et autonome — exactement le mauvais instinct au sein d'une base de code mature.
  • Obsolescence due à la date de coupure d'entraînement. Le modèle peut ignorer qu'une capacité a été promue dans la bibliothèque standard (par ex. structuredClone, Array.prototype.at, Object.groupBy) ou que votre dépôt a adopté un helper après sa coupure, alors il polyfille quelque chose qui existe déjà.
  • Complaisance / moindre résistance. Quand on lui demande d'« ajouter X », le modèle ajoute X de la manière la plus directe plutôt que de rétorquer « on a déjà ça ». Il propose rarement de lui-même « tu n'as pas besoin d'écrire ça ».
  • Sur-spécification. L'analyse par OX Security de plus de 300 dépôts a constaté que l'IA tend vers un codage « style vanille » qui reconstruit des fonctionnalités courantes au lieu d'utiliser des bibliothèques éprouvées, avec des solutions à portée étroite et non réutilisables dans environ 80 à 90 % des cas — chaque nouvelle variation reçoit du code frais au lieu d'être réutilisée.

Pourquoi c'est nuisible

  • Maintenabilité et dette technique. C'est le visage mesurable du déclin de la qualité du code IA. L'analyse par GitClear de 211 millions de lignes a constaté que le code copié-collé est passé de 8,3 % (2020) à 12,3 % (2024), que les blocs de 5 lignes dupliquées et plus ont été multipliés par ~8 en 2024, et que les lignes « déplacées » (refactorisées) sont tombées de 24,1 % à 9,5 % — 2024 a été la première année où le copier-coller a dépassé le refactoring. Les roues réinventées sont la porte d'entrée de cette duplication.
  • Correction. La version sur mesure saute les cas limites que l'implémentation mature a gagnés au fil d'années de rapports de bugs (fuseaux horaires, Unicode, cycles, échappement). Elle a l'air juste et échoue dans la longue traîne.
  • Sécurité. Réécrire la cryptographie, l'authentification, l'assainissement ou la validation au lieu d'une bibliothèque éprouvée est la façon dont le code écrit par IA « viole les bonnes pratiques d'ingénierie » à grande échelle (l'effet « Armée de juniors » d'OX) — des schémas vulnérables atteignent la production plus vite que la revue ne peut les attraper.
  • Charge de revue. Les relecteurs doivent désormais lire, comprendre et tester 40 lignes de logique sur mesure au lieu de reconnaître un seul appel de bibliothèque éprouvée — multiplié sur chaque PR.

##Treatment

Tactiques de revue et de prompting

  • Donnez au modèle le contexte qui lui manque. Avant de générer, pointez-le vers vos utilitaires et dépendances : "Réutilise les helpers de src/utils/* et les bibliothèques déjà présentes dans package.json ; n'en ajoute pas de nouvelles sans demander." Collez les dépendances pertinentes de package.json et l'index de vos utils.
  • Faites de « chercher d'abord » une règle. Donnez la consigne : "Avant d'écrire un helper, vérifie si la bibliothèque standard, une dépendance existante ou un utilitaire du dépôt le fait déjà ; si oui, appelle-le." Les outils agentiques devraient d'abord grep le dépôt.
  • Remettez en question chaque nouveau helper privé en revue. Pour chaque utilitaire écrit à la main, demandez : le runtime fait-il cela ? une dépendance le fait-elle ? l'avons-nous déjà ? Si oui, c'est une réinvention de la roue — remplacez-la.
  • Exécutez la chaîne d'outils. Un détecteur de duplication (jscpd / PMD CPD / SonarQube) en CI signale automatiquement la facette copier-coller ; câblez-le dans la barrière pour que les roues retaillées fassent échouer le build.

Le refactoring — c'est le classique Code dupliqué et Réinventer la roue ; le correctif est Substituer un algorithme (remplacer le corps sur mesure par l'appel à la bibliothèque/au standard) et, là où plusieurs copies existent, Extraire une fonction / remonter vers un seul helper partagé.

// AVANT — réinventé, partiellement correct, dupliqué par fonctionnalité
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out;
}
const copy = deepClone(state);
// APRÈS — appeler ce qui existe déjà (cas limites corrects, zéro nouveau code)
const copy = structuredClone(state);
// ou, si le standard du dépôt est le helper local :
import { cloneDeep } from "@/utils/object";
const copy = cloneDeep(state);

Si la roue a été réinventée dans plusieurs fichiers, supprimez toutes les copies et routez chaque appelant via la source unique de vérité. Résultat net : moins de lignes, moins de bugs, et les métriques de duplication repartent dans le bon sens.

##Detected by

  • jscpd duplicationDétection de copier-coller (min-tokens / seuil)
  • PMD cpdCPD (Copy/Paste Detector)
  • SonarQube common-duplicationsBlocs dupliqués / duplicated_lines_density