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 umstring. - Guardas duplicadas ou subsumidas —
if (!user) returnimediatamente seguido porif (user === null || user === undefined) return. - Pega-tudo
try/catchem 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 deif (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/catchque 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: comstrictNullChecksativado,@typescript-eslint/no-unnecessary-conditionvai 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
- typescript-eslint @typescript-eslint/no-unnecessary-condition — no-unnecessary-condition
- ESLint no-useless-catch — no-useless-catch
- SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-2589 — Boolean expressions should not be gratuitous (always-true/false conditions) — no-gratuitous-expressions
- SonarSource (SonarQube / eslint-plugin-sonarjs) RSPEC-3776 — Cognitive Complexity (nested defensive guards inflate it; proxy detector) — cognitive-complexity