Logo ConstructiCat
CodeBust.
Parcourir la 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.

##Signes et symptômes

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 (FooService → FooRepository → db) 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 l'odeur classique Intermédiaire / Classe paresseuse / Généralité spéculative, et le revers du constat de sur-spécification d'OX Security ("des solutions hyper-spécifiques, à usage unique, impossibles à adapter à d'autres besoins").

// 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.

##Causes du problème

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.
  • Pas de contexte du dépôt. En générant localement, le modèle ne voit pas que le helper n'est appelé qu'une fois, ni qu'une abstraction canonique existe déjà ailleurs — il invente donc de nouvelles couches superficielles au lieu de réutiliser l'existant. L'analyse 2025 de GitClear a mesuré la même tendance : les lignes associées au refactoring ("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 % — ce que GitClear présente en titre comme une multiplication par 4 des clones de code. Les modèles ajoutent de la structure mais la consolident rarement.
  • Application à la lettre + sur-spécification. Le rapport d'OX Security portant sur plus de 300 dépôts recense la fixation sur les règles (By-The-Book Fixation) — un code qui "s'en tient rigidement aux patterns standard même quand des solutions plus innovantes seraient plus efficaces" — et la sur-spécification (Over-Specification) — "des solutions hyper-spécifiques, à usage unique, impossibles à adapter à d'autres besoins" —, chacune dans 80 à 90 % du code généré par IA, ainsi que l'évitement des refactorings (Avoidance of Refactors, également 80 à 90 %) : l'IA "excelle à générer du nouveau code mais ne revient jamais améliorer l'architecture". Les enveloppes superficielles en sont le résidu visible.
  • Littéralisme du prompt et complaisance. Quand on lui demande d'"ajouter une couche service" ou de "rendre le code propre/modulaire", le modèle ajoute des couches au pied de la lettre, même quand YAGNI s'applique — de la sur-ingénierie sur commande plutôt que par besoin.
  • 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 ; le lecteur doit passer par chaque Intermédiaire pour atteindre l'appel réel. OX compare l'IA à "une armée de développeurs juniors talentueux mais inexpérimentés" dont la production dépasse la capacité de revue — et un code qui paraît structuré mais n'a aucune vraie couture est justement ce qui passe entre les mailles.
  • Exactitude et sécurité. Chaque enveloppe et chaque alias ajoute un saut entre l'entrée et l'appel qui compte : le relecteur doit le suivre, tout comme n'importe quelle analyse de flux de données ou de propagation (taint). Une fonction de transit qui "n'ajoute rien" peut tout de même avaler une erreur ou cacher un point sensible (sink) derrière un nom anodin.
  • 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.

##Traitement

Tactiques de revue et de prompting

  • Appliquez YAGNI et la règle de trois : "N'ajoute pas de fonction/classe/interface pour la réutilisation tant que trois sites d'appel n'en ont pas réellement besoin ; en deçà, une nouvelle couche doit cacher une vraie décision ou un invariant. Intègre les helpers à usage unique."
  • 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.

Refactorings (nommez-les) : Intégrer la méthode, Intégrer la classe, Supprimer l’intermédiaire pour les fonctions de transit ; Réduire la hiérarchie ou suppression de l'interface pour les interfaces à implémentation unique. Quand la duplication est le vrai moteur, appliquez Extraire une méthode une seule fois pour obtenir un helper réellement partagé et paramétré — pas une enveloppe par site d'appel.

Avant — Intermédiaire :

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

Après — Intégrer la méthode / Supprimer l’intermédiaire (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(); } }

##Détecté par

  • ESLint no-useless-constructor — Constructeur de passage qui ne fait que déléguer à super ou qui est vide
  • ESLint no-useless-rename — Aliasing redondant d'import/export/déstructuration qui transmet un nom inchangé
  • PMD (Java) UselessOverridingMethod — Méthode de surcharge qui se contente d'appeler super avec les mêmes arguments
  • SonarSource (Sonar S4144) S4144 — Signale les abstractions dupliquées à usage unique ayant des corps identiques
  • jscpd --threshold — Détecte les utilitaires à usage unique quasi dupliqués que les abstractions superficielles ne parviennent pas à consolider