Logo ConstructiCat
CodeBust.
Parcourir la section ▾

Défense excessive.

Les assistants IA se prémunissent contre des défaillances qui ne peuvent pas se produire, enveloppant du code déjà sûr dans des vérifications de null redondantes, des clauses de garde mortes et des blocs try/except attrape-tout qui ajoutent de la complexité sans ajouter de sécurité.

##Signes et symptômes

Un relecteur repère une défense excessive quand une fonction passe plus de lignes à se prémunir contre des états impossibles qu'à faire le travail réel. Signes révélateurs :

  • Des gardes pour des conditions que le système de types garantit déjà — vérifications de null/undefined sur un paramètre typé non nullable, typeof x === "string" sur un string.
  • Des gardes en double ou subsumées — if (!user) return immédiatement suivi de if (user === null || user === undefined) return.
  • Un try/catch attrape-tout autour de code qui ne peut pas lever d'exception, avalant souvent l'erreur ou la relançant simplement (« au cas où »).
  • De la logique pour des cas limites « fantômes » — des branches qui gèrent des entrées qu'aucun appelant ne peut produire. Le pattern Phantom Bugs d'OX Security — une IA qui « sur-conçoit pour des cas limites improbables » — apparaissait dans 20–30 % du code analysé (rapport OX).
  • Des blocs défensifs copiés-collés plutôt qu'extraits, de sorte que la même garde apparaît à cinq endroits.
// User est un type NON nullable : { profile: { name: string } }
function getDisplayName(user: User): string {
  if (!user) return "Unknown";                         // mort : user est non nullable
  if (user === null || user === undefined) return "?"; // doublon, mort aussi
  try {
    if (user.profile && typeof user.profile.name === "string") { // le type le garantit déjà
      const name = user.profile.name;
      if (name.length > 0) {
        return name.trim() !== "" ? name.trim() : "Unknown";
      }
    }
    return "Unknown";
  } catch (e) {
    console.error("could not get name", e); // ce bloc ne peut pas lever d'exception
    return "Unknown";
  }
}

Neuf lignes de précautions enveloppent une ligne d'intention. La contradiction notée par les relecteurs est que le même modèle omet souvent la seule vérification qui compte (par ex. valider une entrée externe non fiable) tout en sur-gardant les appels internes — une défensivité dispersée au feeling, pas selon un modèle de menace.

##Causes du problème

Pourquoi les modèles le produisent

  • Aversion au risque du token suivant / « serviabilité » du RLHF. Les modèles sont réglés pour paraître rigoureux et éviter d'« avoir tort ». Émettre une garde supplémentaire ou un try/catch est peu risqué pour l'objectif du token suivant et se lit comme consciencieux, donc c'est sur-échantillonné. Des chaînes répétées de if (array && array.length > 0) ressemblent à une couverture contre la propre incertitude du modèle sur le flux du code.
  • Aucun contexte de flux de données à l'échelle du dépôt. Le modèle ne peut pas voir qu'un appelant a déjà validé l'argument, ou qu'un type TypeScript rend une branche inatteignable. Quand il ne peut pas raisonner sur le flux de données au-delà du fichier courant, il garde tout localement par défaut.
  • Culte du cargo du prompt de sécurité. Quand on lui demande de « le rendre sécurisé » sans menace concrète à traiter, un modèle peut se contenter d'envelopper le code dans un try/catch, comme si c'était en soi une mesure de sécurité. La défensivité se substitue à la compréhension.
  • Biais des données d'entraînement vers un code verbeux, de style tutoriel qui démontre chaque vérification à des fins pédagogiques, plus un refus de refactoriser : OX Security a trouvé un évitement des refactorisations dans 80–90 % du code IA et une sur-spécification dans 80–90 % — le modèle ajoute, il enlève rarement.

Pourquoi c'est nuisible

  • Maintenabilité & charge de revue. Une taxonomie des inefficacités du code Python généré par LLM inclut le Unnecessary Conditional Block (bloc conditionnel inutile) et les étapes redondantes ou inutiles (arXiv 2503.06327) — exactement ce que sont les gardes mortes. Les relecteurs doivent lire chaque branche morte pour confirmer qu'elle est morte.
  • Exactitude, pas seulement encombrement. Les blocs attrape-tout qui avalent ou gèrent génériquement les erreurs masquent de vraies défaillances ; un try/catch qui retourne une valeur par défaut transforme un bug en comportement erroné silencieux. Les gardes créent aussi des branches mortes que les tests couvrent consciencieusement, gonflant la couverture de tests sans signification (autre constat d'OX).
  • Aggravation de la dette technique. 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). Le boilerplate défensif est exactement le genre de code qu'on clone au lieu de l'extraire : chaque copie doit alors être lue, testée et, tôt ou tard, corrigée séparément.
  • Une complexité cognitive plus élevée par fonction rend la vraie logique plus difficile à trouver, ralentissant chaque changement futur.

##Traitement

Tactiques de revue & de prompting

  • Rendez le contrat explicite pour que les gardes deviennent prouvablement inutiles. Dites au modèle : « user est non nullable et déjà validé par l'appelant — ne le re-vérifie pas. » Appuyez-vous sur les types : avec strictNullChecks activé, @typescript-eslint/no-unnecessary-condition signalera les gardes mortes à votre place.
  • Demandez une défense proportionnée, pas une défense systématique. Demandez : « Ne gère que les erreurs pour lesquelles tu peux décrire un déclencheur concret. Valide les entrées non fiables/externes à la frontière ; fais confiance aux appels internes. » Cela sépare la vraie validation d'entrée (à conserver) des cas limites fantômes (à supprimer).
  • Exigez que le modèle exécute le linter/vérificateur de types et supprime ce qu'il signale avant de rendre le code — bouclez la boucle comme le recommandent les praticiens d'agents (linters, vérificateurs de types, tests comme signaux de rétroaction automatisés).
  • Demandez-lui de supprimer, pas seulement d'ajouter : « Refactorise pour le code minimal qui satisfait la spec ; supprime les branches inatteignables et les blocs catch qui ne peuvent pas se déclencher. » Cela contre le biais documenté d'« évitement des refactorisations ».

Le refactoring

Nommez les manœuvres classiques : supprimez le code mort, Regrouper une expression conditionnelle, Remplacer les conditions imbriquées par des clauses de garde et (pour une entrée qui a besoin d'être vérifiée) Introduire une assertion — ou validez une seule fois à la frontière — au lieu de répéter la vérification. Là où la même garde a été copiée-collée, Extraire une méthode.

// APRÈS — le type garantit non-null ; valider une fois, pas de catch fantôme
function getDisplayName(user: User): string {
  const name = user.profile.name.trim();
  return name || "Unknown";
}

Si une valeur est réellement non fiable, validez-la une fois à la frontière et laissez le reste du code faire confiance au type désormais affiné :

// frontière : parser/valider l'entrée non fiable une seule fois
const user = UserSchema.parse(rawInput); // lève une exception sur des données invalides, ici, à dessein
// ...tout en aval reçoit un `User` validé et n'a pas besoin d'être re-gardé

Règle empirique pour les relecteurs : chaque garde et chaque catch doit répondre à « quel appelant ou quelle entrée concrète déclenche ceci ? » Si la réponse est « rien — juste au cas où », supprimez-le.

##Détecté par

  • typescript-eslint @typescript-eslint/no-unnecessary-condition — no-unnecessary-condition
  • ESLint no-useless-catch — no-useless-catch
  • SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-2589 — Les expressions booléennes ne doivent pas être superflues : conditions toujours vraies ou fausses (no-gratuitous-expressions)
  • SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-3776 — Complexité cognitive (cognitive-complexity) : les vérifications défensives imbriquées l’augmentent ; détecteur indirect