Evitação de Refatoração.
Assistentes de IA empilham código novo e quase duplicado ao lado do que já existe em vez de reestruturá-lo, então a duplicação cresce e a refatoração — o retrabalho que mantém uma base de código saudável — desaparece silenciosamente.
##Signs and Symptoms
Um revisor vê um diff que é quase inteiramente de adições. O modelo resolveu o prompt acrescentando uma nova função, ramo ou arquivo em vez de editar a abstração que deveria ter absorvido a mudança. Sinais reveladores:
- Copiar-colar-com-ajustes: uma nova função é 90% idêntica a uma existente, diferindo por um literal, um nome de campo ou um
ifa mais. - Quase-duplicatas paralelas:
formatUserCsv,formatAdminCsv,formatGuestCsvtodas escrevendo à mão o mesmo loop em vez de uma única função parametrizada. - Proliferação de ramos em vez de extração: um método longo ganha mais um
else ifem vez de o modelo extrair uma estratégia/tabela de consulta. - Reinvenção: um
deepClone/debounce/parser de datas escrito à mão quando o repositório já importa lodash, date-fns, etc. (a OX Security chama isso de "Vanilla Style".) - Superespecificação: um helper hiperespecífico de uso único onde já existe um genérico a dois arquivos de distância.
- "Bugs déjà-vu": a mesma correção precisa ser aplicada em três cópias porque a duplicata nunca foi consolidada (DRY).
// Existente no repositório:
function priceWithTax(items: Item[]) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * 1.2; // 20% de IVA
}
// O que o modelo adiciona para o novo caso "com desconto" —
// uma segunda cópia inteira em vez de um parâmetro:
function priceWithTaxDiscounted(items: Item[], discount: number) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0); // duplicado
return subtotal * (1 - discount) * 1.2; // lógica de IVA duplicada
}
O smell é estrutural, então é mais visível ao longo da mudança inteira, não em um único trecho: muito verde, pouco código movido e um scanner de duplicação acendendo nas novas linhas.
##Reasons for the Problem
Por que os modelos produzem isso
- Viés aditivo do próximo token. Um LLM completa o prompt à sua frente. Emitir um novo bloco autocontido é a continuação localmente mais provável e de menor risco; editar uma abstração distante exige manter o módulo inteiro no contexto de trabalho e prever uma edição globalmente consistente, o que é mais difícil e não é o que o prompt imediato recompensa.
- Contexto raso do repositório. Assistentes de codificação raramente carregam a base de código inteira. Se o modelo não consegue ver o helper, a biblioteca ou a classe base existente, ele não consegue reutilizá-los — então reconstrói. O relatório da OX Security descreve isso como o efeito "Exército de Juniores": muito código localmente funcional, nenhuma memória arquitetural.
- Refatorar é arriscado e não é recompensado. Reestruturar mexe em código que o modelo não foi solicitado a alterar e pode quebrar chamadores e testes. Um assistente bajulador, do tipo "só faça funcionar", minimiza o raio de impacto não mexendo no código que funciona — exatamente o comportamento que a OX mediu como "Evitação de Refatorações" em 80–90% do código gerado por IA, e "Superespecificação" (uso único em vez de reutilizável) em outros 80–90%.
- Defasagem dos dados de treinamento. O modelo pode não saber que o repositório adotou um módulo de utilidades ou atualizou uma biblioteca após seu corte, então escreve à mão o que já existe.
- Gerar é barato, apagar é assustador. Produzir 40 novas linhas não custa nada ao modelo; convencê-lo a apagar e consolidar 40 linhas existentes contraria seu instinto de preservação.
Por que isso é prejudicial
- A duplicação se acumula. A análise de 2025 da GitClear sobre 211 milhões de linhas alteradas constatou que as linhas copiadas/coladas subiram de ~8,3% (2021) para 12,3% (2024) — o primeiro ano em que a duplicação superou o código "movido" (refatorado) — enquanto as linhas refatoradas caíram de ~25% para menos de 10%, uma queda de cerca de 60%. Blocos de 5+ linhas duplicadas saltaram ~8x em 2024.
- Manutenibilidade e correção. Cada clone é um lugar onde uma correção futura pode ser esquecida — "Bugs déjà-vu," em que o mesmo defeito se repete e precisa ser corrigido N vezes (OX: 70–80% do código de IA viola os princípios de reutilização dessa forma).
- Acúmulo de dívida técnica. A refatoração é o retrabalho que mantém a entropia baixa; suprimi-la significa que a dívida é criada mas nunca quitada. A base de código cresce mais rápido do que melhora.
- Carga de revisão. Os revisores agora precisam comparar a olho blocos quase idênticos para confirmar que são intencionalmente iguais, a mudança de carga cognitiva que a discussão "code smells for AI agents" do Stack Overflow descreve — o trabalho migra de escrever para revisar e consolidar.
##Treatment
Táticas de prompting / revisão
- Aponte o modelo para o que já existe. "Antes de adicionar código, procure no repositório por um helper/util/classe base existente e reutilize-o; se nenhum servir, generalize o mais próximo." Cole o módulo relevante no contexto para que ele realmente veja a abstração.
- Restrinja o formato do diff. "Prefira editar funções existentes a adicionar novas. Se dois caminhos de código compartilham lógica, extraia uma função compartilhada (Extrair Função) em vez de duplicar."
- Faça-o executar as ferramentas. Exija que o assistente rode o scanner de duplicação (jscpd / PMD CPD) e o linter, e resolva quaisquer novos achados de blocos duplicados antes de declarar concluído — isso transforma um smell invisível em um portão que falha.
- Peça a refatoração explicitamente como uma segunda etapa. Geração e consolidação são tarefas diferentes; faça "faça funcionar," e depois um "agora aplique DRY e remova a duplicação" separado, o que os modelos lidam muito melhor quando solicitado diretamente.
- Adicione um portão de qualidade na CI para que a duplicação não aumente despercebida (limite de linhas duplicadas do Sonar, ou
jscpd --threshold).
A refatoração de fato — nomeie os movimentos clássicos: Extrair Função, Parametrizar Função e Subir / Substituir Condicional por Polimorfismo ou uma tabela de consulta para eliminar o Código Duplicado.
// Antes: duas funções quase idênticas (refatoração evitada)
function priceWithTax(items: Item[]) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * 1.2;
}
function priceWithTaxDiscounted(items: Item[], discount: number) {
const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal * (1 - discount) * 1.2;
}
// Depois: Extrair Função + Parametrizar Função
const VAT = 1.2;
const subtotalOf = (items: Item[]) =>
items.reduce((s, i) => s + i.price * i.qty, 0);
function priceWithTax(items: Item[], discount = 0) {
return subtotalOf(items) * (1 - discount) * VAT;
}
Uma única fonte de verdade para o subtotal e o IVA: uma mudança futura na regra de imposto agora é uma edição de uma linha em vez de uma caça em N lugares.
##Detected by
- jscpd duplication above --threshold / --min-tokens — Detecção de copiar/colar
- PMD Copy/Paste Detector (minimum-tokens duplication) — CPD
- SonarQube duplicated_lines_density / quality-gate duplicated-blocks condition — Duplicações
- ESLint (eslint-plugin-sonarjs) sonarjs/no-identical-functions — no-identical-functions
- ESLint (eslint-plugin-sonarjs) sonarjs/no-duplicate-string — no-duplicate-string