ConstructiCat Logo
CodeBust.
Browse section ▾

Excesso de Defensividade.

Assistentes de IA se protegem contra falhas que não podem acontecer, embrulhando código já seguro em verificações de null redundantes, cláusulas de guarda mortas e blocos try/except pega-tudo que adicionam complexidade sem adicionar segurança.

##Signs and Symptoms

Um revisor identifica Excesso de Defensividade quando uma função gasta mais linhas se protegendo contra estados impossíveis do que fazendo trabalho de fato. Sinais reveladores:

  • Guardas para condições que o sistema de tipos já garante — verificações de null/undefined em um parâmetro tipado não anulável, typeof x === "string" em um string.
  • Guardas duplicadas ou subsumidasif (!user) return imediatamente seguido por if (user === null || user === undefined) return.
  • Pega-tudo try/catch em torno de código que não pode lançar, muitas vezes engolindo o erro ou apenas relançando-o ("só por precaução").
  • Lógica para casos extremos "fantasmas" — ramos que tratam entradas que nenhum chamador pode produzir. A OX Security descobriu que a IA rotineiramente adiciona "lógica para casos extremos imaginários".
  • Blocos defensivos copiados e colados em vez de extraídos, de modo que a mesma guarda aparece em cinco lugares.
// User é um tipo NÃO anulável: { profile: { name: string } }
function getDisplayName(user: User): string {
  if (!user) return "Unknown";                         // morto: user é não anulável
  if (user === null || user === undefined) return "?"; // duplicado, também morto
  try {
    if (user.profile && typeof user.profile.name === "string") { // o tipo já garante isto
      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 bloco não pode lançar
    return "Unknown";
  }
}

Nove linhas de proteção embrulham uma linha de intenção. A contradição notada pelos revisores é que o mesmo modelo muitas vezes omite a única verificação que importa (por exemplo, validar entrada externa não confiável) enquanto protege excessivamente chamadas internas — defensividade espalhada por vibe, não por modelo de ameaças.

##Reasons for the Problem

Por que os modelos o produzem

  • Aversão a risco do próximo token / "prestatividade" do RLHF. Os modelos são ajustados para parecer minuciosos e evitar estar "errados". Emitir uma guarda extra ou um try/catch é de baixo risco para o objetivo de próximo token e lê como consciencioso, então é superamostrado. Um revisor em escala observou que cadeias repetidas de if (array && array.length > 0) são "um sinal de que o modelo não está totalmente confiante no fluxo do código" — a guarda é uma proteção contra a própria incerteza do modelo.
  • Sem contexto de dataflow de todo o repositório. O modelo não consegue ver que um chamador já validou o argumento, ou que um tipo TypeScript torna um ramo inalcançável. O arXiv 2509.20491 (Code Smells Específicos de IA) mostra que os LLMs "têm dificuldade com code smells que dependem de fluxo ou são sensíveis a valor" — quando não conseguem raciocinar sobre o fluxo, recorrem a proteger tudo localmente.
  • Cargo-cult de prompt de segurança. Pesquisas sobre prompting de código seguro descobriram que, quando solicitados a "torná-lo seguro", os LLMs "adicionam blocos try-catch como medida de segurança isolada, sem outros aprimoramentos de segurança... tipicamente quando não conseguem identificar vulnerabilidades específicas". A defensividade substitui a compreensão.
  • Viés dos dados de treinamento em direção a código verboso, estilo tutorial que demonstra cada verificação por pedagogia, somado a uma recusa em refatorar: a OX Security encontrou evitação de refatorações em 80–90% do código de IA e sobre-especificação em 80–90% — o modelo adiciona, ele raramente remove.

Por que isso prejudica

  • Manutenibilidade & carga de revisão. Uma taxonomia de ineficiências de LLM-Python (arXiv 2503.06327) cataloga validação redundante de entrada, tratamento de erros excessivamente defensivo e condições de guarda desnecessárias, concluindo que esses "padrões defensivos adicionam complexidade sem benefício proporcional de segurança". Os revisores precisam ler cada ramo morto para confirmar que ele está morto.
  • Correção, não apenas entulho. Blocos pega-tudo que engolem ou tratam erros genericamente escondem falhas reais; um try/catch que retorna um valor padrão transforma um bug em comportamento errado silencioso. As guardas também criam ramos mortos que os testes cobrem diligentemente, inflando a cobertura com testes sem sentido (outra constatação da OX).
  • Acúmulo de dívida técnica. A análise de 2025 da GitClear descobriu que a participação da refatoração nas linhas alteradas caiu de 25% (2021) para menos de 10% (2024) enquanto as linhas copiadas/coladas subiram para 12,3% e os blocos duplicados cresceram ~8x. O boilerplate defensivo é exatamente o tipo de código que é clonado em vez de extraído, e blocos clonados correlacionam-se com 15–50% mais defeitos.
  • Maior complexidade cognitiva por função torna a lógica real mais difícil de encontrar, atrasando cada mudança futura.

##Treatment

Táticas de revisão & prompting

  • Torne o contrato explícito para que as guardas se tornem comprovadamente desnecessárias. Diga ao modelo: "user é não anulável e já validado pelo chamador — não o reverifique." Apoie-se nos tipos: com strictNullChecks ativado, @typescript-eslint/no-unnecessary-condition vai sinalizar guardas mortas para você.
  • Faça prompt por defesa proporcional, não defesa generalizada. Peça: "Só trate erros para os quais você consiga descrever um gatilho concreto. Valide entrada não confiável/externa na fronteira; confie em chamadas internas." Isso separa a validação de entrada real (mantenha-a) dos casos extremos fantasmas (apague-os).
  • Exija que o modelo rode o linter/verificador de tipos e remova o que ele sinalizar antes de retornar o código — feche o loop da forma que os praticantes de agentes recomendam (linters, verificadores de tipos, testes como sinais de feedback automatizados).
  • Peça que ele apague, não apenas adicione: "Refatore para o código mínimo que satisfaz a especificação; remova ramos inalcançáveis e blocos catch que não podem disparar." Isso combate o viés documentado de "evitação de refatorações".

A refatoração

Nomeie os movimentos clássicos: Remover Código Morto, Consolidar Expressão Condicional, Substituir Condicional Aninhado por Cláusulas de Guarda e (para entrada que de fato precisa de verificação) Introduzir Asserção / validar uma vez na fronteira em vez de repetidamente. Onde a mesma guarda foi copiada e colada, Extrair Função.

// DEPOIS — o tipo garante não-null; valide uma vez, sem catch fantasma
function getDisplayName(user: User): string {
  const name = user.profile.name.trim();
  return name || "Unknown";
}

Se um valor genuinamente é não confiável, valide-o uma vez na borda e deixe o resto do código confiar no tipo agora restringido:

// fronteira: faça parse/validação da entrada não confiável uma única vez
const user = UserSchema.parse(rawInput); // lança em dados ruins, aqui, de propósito
// ...tudo a jusante recebe um `User` validado e não precisa de re-proteção

Regra prática para revisores: cada guarda e cada catch precisa responder "qual chamador ou entrada concreta dispara isto?" Se a resposta for "nada — só por precaução", apague-o.

##Detected by