---
title: "Défense excessive"
type: "ai-smell"
slug: "defensive-overkill"
url: "http://localhost:3000/fr/ai-smells/defensive-overkill.md"
category: "Maintenance"
description: "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é."
---
# 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é.

## Signs and Symptoms

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. OX Security a constaté que l'IA ajoute couramment de la « logique pour des cas limites imaginaires ».
* **Des blocs défensifs copiés-collés** plutôt qu'extraits, de sorte que la même garde apparaît à cinq endroits.

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

## Reasons for the Problem

### 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é. Un relecteur à grande échelle a noté que des chaînes répétées de `if (array && array.length > 0)` sont « un signe que le modèle n'est pas pleinement confiant dans le flux du code » — la garde est une couverture contre la propre incertitude du modèle.
* **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. arXiv 2509.20491 (_AI-Specific Code Smells_) montre que les LLM « peinent avec les smells dépendants du flux ou sensibles à la valeur » — quand ils ne peuvent pas raisonner sur le flux, ils gardent tout localement par défaut.
* **Culte du cargo du prompt de sécurité.** Des recherches sur le prompting de code sécurisé ont constaté que, à qui on demande de « le rendre sécurisé », les LLM « ajoutent des blocs try-catch comme mesure de sécurité autonome sans autres améliorations de sécurité… généralement quand ils ne peuvent pas identifier de vulnérabilités spécifiques ». 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 LLM-Python (arXiv 2503.06327) catalogue la _validation d'entrée redondante_, la _gestion d'erreurs sur-défensive_ et les _conditions de garde inutiles_, concluant que ces « patterns défensifs ajoutent de la complexité sans bénéfice de sécurité proportionnel ». 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 a constaté que la part de la refactorisation dans les lignes modifiées est tombée de **25 % (2021) à moins de 10 % (2024)** tandis que les lignes copiées-collées montaient à **12,3 %** et que les blocs dupliqués croissaient d'environ 8×. Le boilerplate défensif est exactement le genre de code qui se fait cloner au lieu d'être extrait, et les blocs clonés corrèlent avec **15–50 % de défauts en plus**.
* **Une complexité cognitive plus élevée** par fonction rend la vraie logique plus difficile à trouver, ralentissant chaque changement futur.

## Treatment

### 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 : **Supprimer le code mort**, **Consolider une expression conditionnelle**, **Remplacer une condition imbriquée par des clauses de garde**, et (pour une entrée qui _a_ besoin d'être vérifiée) **Introduire une assertion / valider une fois à la frontière** plutôt que de manière répétée. Là où la même garde a été copiée-collée, **Extraire une fonction**.

```ts
// 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é :

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

## Detected by

- **typescript-eslint** `@typescript-eslint/no-unnecessary-condition` — no-unnecessary-condition (https://typescript-eslint.io/rules/no-unnecessary-condition/)
- **ESLint** `no-useless-catch` — no-useless-catch (https://eslint.org/docs/latest/rules/no-useless-catch)
- **SonarSource (SonarQube / eslint-plugin-sonarjs)** `RSPEC-2589 — Boolean expressions should not be gratuitous (always-true/false conditions)` — no-gratuitous-expressions (https://rules.sonarsource.com/javascript/RSPEC-2589/)
- **SonarSource (SonarQube / eslint-plugin-sonarjs)** `RSPEC-3776 — Cognitive Complexity (nested defensive guards inflate it; proxy detector)` — cognitive-complexity (https://rules.sonarsource.com/javascript/RSPEC-3776/)
