Logotipo de ConstructiCat
CodeBust.
Explorar la sección ▾

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.

##Signos y síntomas

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. El patrón Phantom Bugs de OX Security —una IA que "sobrediseña para casos límite improbables"— apareció en el 20–30 % del código analizado (informe de OX).
  • Bloques defensivos copiados y pegados en lugar de extraídos, así que la misma guarda aparece en cinco lugares.
// 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.

##Causas del problema

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. Las cadenas repetidas de if (array && array.length > 0) parecen un seguro contra la propia incertidumbre del modelo sobre el flujo del código.
  • 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. Cuando no puede razonar sobre el flujo de datos más allá del archivo actual, recurre a proteger todo localmente.
  • Imitación acrítica de prompts de seguridad. Cuando se le pide "hazlo seguro" sin una amenaza concreta que resolver, el modelo puede limitarse a envolver el código en try/catch como si eso fuera en sí una medida de seguridad. 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 en el código Python generado por LLM incluye el Unnecessary Conditional Block (bloque condicional innecesario) y los pasos redundantes o innecesarios (arXiv 2503.06327), que es justo lo que son las guardas muertas. 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 GitClear de 2025 sobre 211 millones de líneas modificadas (2020–2024) concluyó que las líneas asociadas a la refactorización (líneas «movidas») cayeron del 25 % de las líneas modificadas en 2021 a menos del 10 % en 2024, mientras que las líneas copiadas/pegadas subieron del 8,3 % al 12,3 % en el mismo periodo; 2024 fue el primer año en que las líneas copiadas/pegadas superaron a las movidas, y los bloques con cinco o más líneas duplicadas se multiplicaron aproximadamente por ocho durante 2024 (GitClear 2025). El código repetitivo defensivo es exactamente el tipo de código que se clona en lugar de extraerse, así que cada copia hay que leerla, probarla y, al final, corregirla por separado.
  • Mayor complejidad cognitiva por función hace que la lógica real sea más difícil de encontrar, ralentizando cada cambio futuro.

##Tratamiento

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: elimina el 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 —o valida una sola vez en la frontera— en lugar de repetir la comprobación. Donde la misma guarda se copió y pegó, Extraer método.

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

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

##Detectado por

  • typescript-eslint @typescript-eslint/no-unnecessary-condition — no-unnecessary-condition
  • ESLint no-useless-catch — no-useless-catch
  • SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-2589 — Las expresiones booleanas no deben ser superfluas: condiciones siempre verdaderas o falsas (no-gratuitous-expressions)
  • SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-3776 — Complejidad cognitiva (cognitive-complexity): las comprobaciones defensivas anidadas la aumentan; detector indirecto