---
title: "Dérive des conventions"
type: "ai-smell"
slug: "convention-drift"
url: "http://localhost:3000/fr/ai-smells/convention-drift.md"
category: "Maintenance"
description: "Code généré par IA qui ignore discrètement les conventions établies d'un dépôt — réinventant des helpers, choisissant la mauvaise bibliothèque et adoptant un nommage et une gestion d'erreurs non conformes au style maison — de sorte que la base de code dérive vers un style générique, moyen-de-l'internet."
---
# Dérive des conventions

> Code généré par IA qui ignore discrètement les conventions établies d'un dépôt — réinventant des helpers, choisissant la mauvaise bibliothèque et adoptant un nommage et une gestion d'erreurs non conformes au style maison — de sorte que la base de code dérive vers un style générique, moyen-de-l'internet.

## Signs and Symptoms

Un relecteur reconnaît la dérive des conventions quand un changement de l'IA _fonctionne_ mais n'a pas l'air d'appartenir à la base de code. Il se rabat sur le pattern moyen mondial au lieu du pattern local :

* Un nouveau helper est écrit inline alors qu'un utilitaire éprouvé existe déjà (`utils/date.ts`, `lib/apiClient`, un type `Result` partagé).
* Une bibliothèque ou une API différente du standard du dépôt apparaît (`axios` dans une app qui a standardisé sur un wrapper `fetch` ; `moment` là où le dépôt utilise `date-fns`).
* Le nommage, l'agencement des fichiers et le style de gestion des erreurs divergent — `camelCase` là où le module est en `snake_case`, un `throw new Error("...")` brut là où tout le reste renvoie une erreur typée, un `try/catch` sur mesure avec logs au lieu du logger partagé.
* Chaque nouvelle exigence reçoit son propre bloc flambant neuf et étroitement taillé plutôt que de réutiliser une abstraction (la « sur-spécification » d'OX Security, présente dans 80–90 % du code IA).
* Signature symptomatique : les blocs dupliqués se multiplient. GitClear a mesuré un bond de 8× des blocs de clones de 5+ lignes en 2024, les lignes copiées-collées dépassant pour la première fois les lignes déplacées (refactorisées).

```ts
// Style maison (déjà dans le dépôt)
import { apiClient } from "@/lib/apiClient";   // encapsule auth, retries, URL de base
import { formatDate } from "@/utils/date";      // à l'échelle de l'app, sensible à la locale

// Changement généré par IA — dérive loin des deux
import axios from "axios";                       // pas un pattern de dépendance du projet
async function getUser(id: string) {
  try {
    const res = await axios.get(`https://api.example.com/users/${id}`); // URL de base codée en dur
    return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() }; // réinvente formatDate
  } catch (e) {
    console.log("error", e);                     // pas le logger partagé ; avale l'erreur
  }
}

```

L'indice est la cohérence, pas l'exactitude : trois relecteurs trouvent chacun un choix _différent_ « faux-mais-fonctionnel », et aucun ne correspond au fichier d'à côté.

## Reasons for the Problem

**Pourquoi les modèles le produisent**

* _Régression vers la moyenne d'entraînement._ Les LLM sont entraînés sur un vaste corpus de code moyen-de-l'internet, pas sur votre dépôt. Sans instruction, ils émettent l'idiome statistiquement le plus courant (`axios`, `moment`, `console.log`), pas votre idiome maison — vos conventions sont un signal minuscule et hors distribution à côté de la moyenne mondiale.
* _Cohérence locale du token suivant plutôt que portée sur le dépôt._ Il est localement plus simple de compléter un bloc autonome (insérer un formateur de date inline) que de « savoir » que `@/utils/date` existe et d'importer un identifiant que le modèle n'a jamais vu. La réutilisation exige un contexte de dépôt que le modèle ne possède pas ; la réinvention ne requiert que le tampon courant.
* _Contexte du dépôt limité / lacunaire._ Le helper canonique, la config du linter et l'ADR qui dit « utilise le wrapper fetch » sont généralement hors du prompt. Le modèle ne peut pas suivre une convention qu'on ne lui a jamais montrée. Comme l'a formulé Eno Reyes de Factory, une grande partie d'une convention est _tacite_ — des patterns que les humains absorbent en lisant la base de code et que les agents ne voient tout simplement jamais.
* _Complaisance / focalisation littérale sur la tâche._ À qui on demande « d'ajouter `getUser` », le modèle fait exactement cela et ne dira pas spontanément « en fait nous avons déjà un client pour ça ». Il optimise pour l'accomplissement de la tâche énoncée, pas pour l'intégration au système. La « fixation sur le manuel » d'OX Security (80–90 % des échantillons) est la même force : il suit une convention générique de manuel plutôt que d'évaluer celle du projet.
* _Obsolescence liée à la date limite d'entraînement._ Si le dépôt a migré vers une bibliothèque ou un pattern plus récents après la date limite du modèle, le modèle réintroduit avec assurance la version sur laquelle il a été entraîné.

**Pourquoi c'est nuisible**

* _Maintenabilité & charge cognitive._ Chaque choix dérivé est une façon de plus de faire la même chose. Les lecteurs doivent garder en tête N variantes de « comment nous formatons les dates » ; la base de code perd sa source unique de vérité.
* _Exactitude lors des changements._ La logique dupliquée/réinventée diverge silencieusement — un bug corrigé dans le helper partagé n'est _pas_ corrigé dans la copie de l'IA. GitClear relie l'explosion des clones directement aux assistants IA qui rendent le tab-pour-insérer moins coûteux que la réutilisation, tandis que la part de la refactorisation dans les changements est tombée de 25 % (2021) à moins de 10 % (2024).
* _Sécurité._ Une validation, une authentification ou une construction de requête réinventées contournent le chemin partagé durci (URL de base codées en dur, erreurs avalées, SQL ad hoc en chaînes). OX qualifie l'effet agrégé d'« armée de juniors » : rapide, fonctionnel, sans jugement architectural.
* _Charge de revue & accumulation de dette technique._ La dérive n'est pas attrapée par les tests (le code fonctionne), elle atterrit donc en revue ou pas du tout, s'agrégeant dans le smell architectural « Fonctionnalité éparpillée / Mirage modulaire » où un comportement apparenté est fragmenté entre fichiers sans réelle cohésion.

## Treatment

**Tactiques de prompting / de workflow**

* _Montrez les conventions._ Placez les règles là où l'agent les lit — un fichier `CLAUDE.md`/`AGENTS.md`/de règles listant le client sanctionné, le logger, le type d'erreur, le nommage et « utilise X et non Y ». Les conventions que le modèle ne voit pas, il ne peut pas les suivre.
* _Pointez vers le code canonique._ « Utilise `@/lib/apiClient` et `@/utils/date` ; n'ajoute pas de nouvelles bibliothèques HTTP. Aligne-toi sur la gestion d'erreurs de `services/orders.ts`. » Donnez l'idiome du dépôt en few-shot en collant un fichier exemplaire.
* _Demandez avant qu'il n'écrive._ « Quels helpers/abstractions existants couvrent ceci ? Réutilise-les ; n'ajoute du nouveau code que si aucun ne convient. » Cela convertit la réinvention en réutilisation en amont.
* _Faites du linter la barrière._ Exigez que l'agent exécute le formateur, le linter et le vérificateur de types et corrige tous les constats avant de rendre. Le conseil de Factory est de systématiser les signaux de qualité (lint/format/type-check/test) pour que l'agent optimise vers eux au lieu de dériver.
* _Revoyez pour l'adéquation, pas seulement la fonction._ Examinez les imports à la recherche de bibliothèques non sanctionnées ; cherchez (grep) la logique qui aurait dû appeler le helper partagé ; exécutez un détecteur de copier-coller sur la PR.

**Le refactoring** — nommez les manœuvres classiques : _Supprimer la duplication_ / _Remplacer du code inline par un appel de fonction_, _Substituer un algorithme_, et _Extraire une fonction_ si la bonne abstraction n'existe pas encore.

```ts
// AVANT — dérivé, réinvente le client + la logique de date, URL codée en dur, erreur avalée
import axios from "axios";
async function getUser(id: string) {
  try {
    const res = await axios.get(`https://api.example.com/users/${id}`);
    return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() };
  } catch (e) {
    console.log("error", e);
  }
}

// APRÈS — réutilise les conventions établies
import { apiClient } from "@/lib/apiClient";
import { formatDate } from "@/utils/date";

async function getUser(id: string) {
  const user = await apiClient.get<User>(`/users/${id}`); // URL de base, auth, retries, erreurs typées gérées ici
  return { ...user, joined: formatDate(user.joined) };
}

```

Si le même bloc dérivé apparaît dans plusieurs PR, c'est le signe que la convention est indécouvrable — corrigez la cause racine en la documentant dans le fichier de règles et/ou en l'exposant via un point d'entrée unique et évident, plutôt qu'en re-révisant chaque instance.

## Detected by

- **jscpd** `duplication threshold (min-lines / min-tokens)` — Détection de copier-coller (https://github.com/kucherenko/jscpd)
- **PMD CPD** `CPD duplicated code blocks` — Détecteur de copier-coller (https://pmd.github.io/latest/pmd_userdocs_cpd.html)
- **ESLint** `no-restricted-imports` — no-restricted-imports (https://eslint.org/docs/latest/rules/no-restricted-imports)
- **typescript-eslint** `@typescript-eslint/naming-convention` — naming-convention (https://typescript-eslint.io/rules/naming-convention/)
- **SonarQube** `Source files should not have any duplicated blocks` — Blocs dupliqués (https://docs.sonarsource.com/sonarqube-server/latest/user-guide/code-metrics/metrics-definition/)
