ConstructiCat Logo
CodeBust.
Browse section ▾

Abstraction superficielle.

Un assistant IA enveloppe du code dans des fonctions, classes ou interfaces supplémentaires qui ajoutent une couche d'indirection sans masquer la moindre complexité ni permettre la réutilisation — des abstractions qui ne font que rediriger vers un unique site d'appel.

##Signs and Symptoms

Un relecteur reconnaît une Abstraction superficielle lorsqu'une nouvelle fonction/classe/interface ajoute un nom et une couche mais aucun levier : son corps reformule sa signature, elle transmet directement vers un unique appel sous-jacent, elle est invoquée exactement une fois, ou elle ne cache aucune décision, invariant ou variation. Le code paraît structuré en couches et « de qualité entreprise », mais vous devez lire à travers chaque couche pour comprendre quoi que ce soit — l'abstraction n'encapsule rien.

Signes révélateurs :

  • Fonctions/méthodes de passage dont le corps est un unique appel délégué sans comportement ajouté.
  • Classes enveloppes (FooServiceFooRepositorydb) où chaque couche se contente d'appeler la suivante.
  • Interfaces à une seule implémentation (IClock avec uniquement SystemClock) introduites de façon spéculative « pour la testabilité » sans deuxième implémentation ni faux objet.
  • Fonctions utils/helpers utilisées à exactement un seul endroit.
  • Aliasing/renommage redondant qui transmet une valeur inchangée.

C'est le smell classique Homme du milieu / Classe paresseuse / Généralité spéculative, et le revers du constat de « sur-spécification » d'OX Security (solutions hyper-spécifiques à usage unique au lieu de composants généralisables).

// L'IA a ajouté trois « couches » qui transmettent chacune à la suivante.
function getUserName(id: string) {
  return fetchUserName(id);            // n'ajoute rien
}
function fetchUserName(id: string) {
  return userRepository.getName(id);   // n'ajoute rien
}
class UserRepository {
  getName(id: string) {
    return db.query("SELECT name FROM users WHERE id = ?", [id]);
  }
}
// getUserName est le seul site d'appel, utilisé une seule fois.
// Deux des trois couches n'ajoutent aucun comportement — pur Homme du milieu.

##Reasons for the Problem

Pourquoi les modèles le produisent

  • Imiter la forme d'une bonne architecture. Les corpus d'entraînement sont saturés de tutoriels et d'échafaudages d'entreprise — couches service/repository/factory, injection de dépendances, interfaces à une seule implémentation. Le modèle reproduit par mimétisme le cérémonial de l'abstraction sans la substance, parce qu'un utilitaire nommé est localement plausible et « fait professionnel ». La génération token par token optimise la ressemblance avec du code bien structuré, et non le fait qu'une couche mérite sa place.
  • Aucun contexte du dépôt. En générant localement, le modèle ne peut pas voir que l'utilitaire n'est appelé qu'une fois, ou qu'une abstraction canonique existe déjà ailleurs — il invente donc de nouvelles couches superficielles au lieu de les réutiliser. C'est exactement la tendance mesurée par GitClear : les lignes refactorisées (« déplacées ») sont passées d'environ 24 % (2020) à moins de 10 % (2024) tandis que les lignes copiées-collées augmentaient et que les clones de code croissaient d'environ 4x. Les modèles ajoutent de la structure mais ne la consolident jamais.
  • Application littérale du manuel + sur-spécification. OX Security a constaté que l'IA sur-spécifie dans 80 à 90 % du code généré, produisant des « solutions étroites qui ne peuvent pas être réutilisées », et évite les refactorisations dans 80 à 90 % des cas (« s'arrête à assez bon »). Les enveloppes superficielles sont le résidu visible.
  • Littéralisme du prompt et flagornerie. Quand on lui demande d'« ajouter une couche service » ou de « rendre le code propre/modulaire », le modèle ajoute littéralement des couches même lorsque YAGNI s'applique — la tendance à la sur-ingénierie / au « god agent » que les praticiens décrivent dans la discussion code-smells-for-AI-agents.
  • Obsolescence due à la date limite d'entraînement. Il enveloppera une API dépréciée dans un mince adaptateur plutôt que d'en migrer.

Pourquoi c'est nuisible

  • Maintenabilité et charge de revue. Chaque modification doit traverser des couches mortes ; les lecteurs lisent à travers des Hommes du milieu. Le constat « Armée de juniors » d'OX : les relecteurs ne peuvent pas suivre le rythme d'un code qui paraît structuré mais n'a aucune véritable couture.
  • Correction et sécurité. arXiv 2509.20491 et SonarSource notent tous deux que l'aliasing et les enveloppes « masquent les sites d'appel décisifs », dégradant l'analyse du flux de données/de contrôle et l'analyse de teinte (taint analysis) — une enveloppe peut silencieusement avaler une erreur ou cacher un puits (sink) à un scanner.
  • Accumulation de dette technique. Les interfaces spéculatives à une seule implémentation se figent ; et parce que chaque abstraction superficielle est à usage unique, la variation suivante est copiée-collée plutôt que paramétrée — alimentant directement la croissance des clones mesurée par GitClear. Vous payez l'indirection sans jamais récolter la réutilisation qu'elle promettait.

##Treatment

Tactiques de revue et de prompting

  • Appliquez la Règle de trois : « N'ajoutez une fonction/classe/interface que si elle est utilisée à ≥2 endroits ou qu'elle cache une véritable décision/invariant. Intégrez en ligne (inline) les utilitaires à usage unique (YAGNI). »
  • Forcez la réutilisation : « Avant d'ajouter un utilitaire/service/interface, cherchez-en un existant dans le dépôt et réutilisez-le. » Orientez le modèle vers le module canonique.
  • Faites-lui justifier chaque couche : « Énumérez chaque nouvelle fonction/classe et indiquez quelle variation ou quel invariant elle cache ; supprimez toute couche qui se contente de transmettre. »
  • Exigez qu'il exécute le linter (no-useless-constructor, no-useless-rename) et un scan de duplication (jscpd) et qu'il corrige ce qu'ils signalent.

Refactorisations (nommez-les) : Intégrer une fonction, Intégrer une classe, Supprimer l'homme du milieu pour les passages directs ; Réduire la hiérarchie / abandonner l'interface pour les interfaces à une seule implémentation. Quand la duplication est le véritable moteur, faites Extraire une fonction une seule fois dans un utilitaire véritablement partagé et paramétré — pas une enveloppe par site d'appel.

Avant — Homme du milieu :

function getUserName(id: string) { return fetchUserName(id); }
function fetchUserName(id: string) { return userRepository.getName(id); }

Après — Intégrer une fonction / Supprimer l'homme du milieu (un seul site d'appel, aucun comportement ajouté) :

const name = userRepository.getName(id);

Avant — Interface spéculative à une seule implémentation :

interface IClock { now(): number; }
class SystemClock implements IClock { now() { return Date.now(); } }

Après — Réduire la hiérarchie (utilisez le type concret ; réintroduisez l'interface uniquement quand une seconde implémentation, par ex. un faux objet de test, existe réellement) :

class SystemClock { now() { return Date.now(); } }

##Detected by