---
title: "Exceso de defensa"
type: "ai-smell"
slug: "defensive-overkill"
url: "http://localhost:3000/es/ai-smells/defensive-overkill.md"
category: "Mantenimiento"
description: "Los asistentes de IA se protegen contra fallos que no pueden ocurrir, envolviendo código ya seguro en comprobaciones de nulos redundantes, cláusulas de guarda muertas y bloques try/except generales que añaden complejidad sin añadir seguridad."
---
# Exceso de defensa

> Los asistentes de IA se protegen contra fallos que no pueden ocurrir, envolviendo código ya seguro en comprobaciones de nulos redundantes, cláusulas de guarda muertas y bloques try/except generales que añaden complejidad sin añadir seguridad.

## Signs and Symptoms

Un revisor detecta el exceso de defensa cuando una función gasta más líneas protegiéndose contra estados imposibles que haciendo trabajo real. Señales reveladoras:

* **Guardas para condiciones que el sistema de tipos ya garantiza**: comprobaciones de null/undefined sobre un parámetro tipado no anulable, `typeof x === "string"` sobre un `string`.
* **Guardas duplicadas o subsumidas**: `if (!user) return` seguido de inmediato por `if (user === null || user === undefined) return`.
* **`try/catch` general alrededor de código que no puede lanzar**, que a menudo traga el error o simplemente lo relanza ("por si acaso").
* **Lógica para casos límite "fantasma"**: ramas que manejan entradas que ningún llamador puede producir. OX Security encontró que la IA añade rutinariamente "lógica para casos límite imaginarios".
* **Bloques defensivos copiados y pegados** en lugar de extraídos, así que la misma guarda aparece en cinco lugares.

```ts
// User es un tipo NO anulable: { profile: { name: string } }
function getDisplayName(user: User): string {
  if (!user) return "Unknown";                         // muerto: user es no anulable
  if (user === null || user === undefined) return "?"; // duplicado, también muerto
  try {
    if (user.profile && typeof user.profile.name === "string") { // el tipo ya garantiza esto
      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); // este bloque no puede lanzar
    return "Unknown";
  }
}

```

Nueve líneas de protección envuelven una línea de intención. La contradicción que señalan los revisores es que el _mismo_ modelo a menudo omite la única comprobación que importa (p. ej. validar entrada externa no confiable) mientras sobreprotege las llamadas internas: actitud defensiva repartida por intuición, no por modelo de amenazas.

## Reasons for the Problem

### Por qué los modelos lo producen

* **Aversión al riesgo del siguiente token / "utilidad" del RLHF.** Los modelos están afinados para parecer minuciosos y evitar "equivocarse". Emitir una guarda extra o un `try/catch` es de bajo riesgo para el objetivo del siguiente token y se lee como concienzudo, así que se sobremuestrea. Un revisor a gran escala señaló que las cadenas repetidas de `if (array && array.length > 0)` son "una señal de que el modelo no confía del todo en el flujo del código": la guarda es un seguro contra la propia incertidumbre del modelo.
* **Sin contexto de flujo de datos de todo el repositorio.** El modelo no puede ver que un llamador ya validó el argumento, o que un tipo de TypeScript hace una rama inalcanzable. arXiv 2509.20491 (_olores de código específicos de la IA_) muestra que los LLM "tienen dificultades con los olores de código que dependen del flujo o son sensibles al valor": cuando no pueden razonar sobre el flujo, recurren a proteger todo localmente.
* **Imitación acrítica de prompts de seguridad.** La investigación sobre prompting de código seguro encontró que, cuando se les pide "hazlo seguro", los LLM "añaden bloques try-catch como medida de seguridad aislada sin otras mejoras de seguridad... normalmente cuando no pueden identificar vulnerabilidades concretas". La actitud defensiva sustituye a la comprensión.
* **Sesgo de los datos de entrenamiento hacia código verboso de estilo tutorial** que demuestra cada comprobación por pedagogía, sumado al rechazo a refactorizar: OX Security encontró **evitación de refactorizaciones en el 80–90 %** del código de IA y **sobreespecificación en el 80–90 %**: el modelo añade, rara vez quita.

### Por qué resulta perjudicial

* **Mantenibilidad y carga de revisión.** Una taxonomía de ineficiencias de LLM en Python (arXiv 2503.06327) cataloga la _validación de entrada redundante_, el _manejo de errores excesivamente defensivo_ y las _condiciones de guarda innecesarias_, concluyendo que estos "patrones defensivos añaden complejidad sin un beneficio de seguridad proporcional". Los revisores deben leer cada rama muerta para confirmar que _está_ muerta.
* **Corrección, no solo desorden.** Los bloques generales que tragan o manejan errores de forma genérica ocultan fallos reales; un `try/catch` que devuelve un valor por defecto convierte un error en un comportamiento incorrecto silencioso. Las guardas también crean ramas muertas que las pruebas cubren obedientemente, inflando la cobertura con pruebas sin sentido (otro hallazgo de OX).
* **Acumulación de deuda técnica.** El análisis de 2025 de GitClear encontró que la proporción de líneas modificadas por refactorización cayó del **25 % (2021) a menos del 10 % (2024)** mientras que las líneas copiadas/pegadas subieron al **12,3 %** y los bloques duplicados crecieron \~8×. El código repetitivo defensivo es exactamente el tipo de código que se clona en lugar de extraerse, y los bloques clonados se correlacionan con un **15–50 % más de defectos**.
* **Mayor complejidad cognitiva** por función hace que la lógica real sea más difícil de encontrar, ralentizando cada cambio futuro.

## Treatment

### Tácticas de revisión y de prompting

* **Haz explícito el contrato para que las guardas sean demostrablemente innecesarias.** Dile al modelo: _"`user` es no anulable y ya lo validó el llamador: no lo vuelvas a comprobar."_ Apóyate en los tipos: con `strictNullChecks` activado, `@typescript-eslint/no-unnecessary-condition` señalará las guardas muertas por ti.
* **Pide defensa proporcional, no defensa indiscriminada.** Pregunta: _"Maneja solo los errores para los que puedas describir un desencadenante concreto. Valida la entrada no confiable/externa en la frontera; confía en las llamadas internas."_ Esto separa la validación de entrada real (consérvala) de los casos límite fantasma (elimínalos).
* **Exige que el modelo ejecute el linter/verificador de tipos y elimine lo que señale** antes de devolver el código: cierra el bucle como recomiendan los profesionales de agentes (linters, verificadores de tipos, pruebas como señales de retroalimentación automatizadas).
* **Pídele que elimine, no solo que añada:** _"Refactoriza para obtener el mínimo código que satisface la especificación; elimina las ramas inalcanzables y los bloques catch que no pueden dispararse."_ Esto contrarresta el documentado sesgo de "evitación de refactorizaciones".

### La refactorización

Nombra los movimientos clásicos: **Eliminar código muerto**, **Consolidar expresión condicional**, **Reemplazar condicional anidado con cláusulas de guarda** y (para la entrada que _sí_ necesita comprobación) **Introducir aserción / validar una vez en la frontera** en lugar de repetidamente. Donde la misma guarda se copió y pegó, **Extraer función**.

```ts
// DESPUÉS — el tipo garantiza no-nulo; valida una vez, sin catch fantasma
function getDisplayName(user: User): string {
  const name = user.profile.name.trim();
  return name || "Unknown";
}

```

Si un valor _es_ genuinamente no confiable, valídalo **una vez** en el borde y deja que el resto del código confíe en el tipo ahora acotado:

```ts
// frontera: parsea/valida la entrada no confiable una sola vez
const user = UserSchema.parse(rawInput); // lanza ante datos inválidos, aquí, a propósito
// ...todo lo posterior recibe un `User` validado y no necesita reprotección

```

Regla práctica para revisores: cada guarda y cada `catch` debe responder _"¿qué llamador o entrada concreta dispara esto?"_ Si la respuesta es "nada, por si acaso", elimínalo.

## 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/)
