ConstructiCat Logo
CodeBust.
Browse section ▾

Duplicação em Vez de Extração.

Assistentes de IA tendem a colar uma cópia nova e levemente ajustada de uma lógica existente em vez de reutilizar ou extrair uma função compartilhada, inflando o código duplicado enquanto a refatoração desaparece silenciosamente.

##Signs and Symptoms

Um revisor reconhece este smell quando um diff de autoria da IA adiciona um bloco que já existe em algum lugar do repositório, levemente renomeado, em vez de chamar a função que já está lá. A forma reveladora é dois ou mais corpos quase idênticos que diferem apenas em um substantivo (user/order), um endpoint ou uma constante — o tipo de cópia que um humano teria fatorado.

Sinais comuns:

  • Novos handlers/serviços que repetem literalmente o mesmo boilerplate de validação, retry, mapeamento de erros ou fetch.
  • Strings mágicas e literais de configuração redigitados em cada novo bloco em vez de referenciados de um único lugar.
  • Cada turno do agente reimplementa lógica que o turno anterior já escreveu (o agente não tem memória de que um helper existe).
  • git log mostra commits consecutivos da IA adicionando linhas, mas quase nenhuma linha "movida"/renomeada — adições sem consolidação.
// A IA adicionou isto para a nova rota — mas getUser já existe 30 linhas acima
async function getOrder(id: string) {
  const res = await fetch(`${API}/orders/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

async function getUser(id: string) {                 // <-- corpo 95% idêntico
  const res = await fetch(`${API}/users/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

Este é o clássico smell de Código Duplicado, mas produzido sistematicamente em vez de por acidente.

##Reasons for the Problem

Por que os modelos a produzem

  • Localidade do próximo token. Um LLM completa o trecho atual a partir do texto mais similar em sua janela de contexto. Reproduzir um bloco próximo e conhecidamente bom é a continuação de maior probabilidade; "pare, vá definir um helper e então chame-o" é um desvio mais longo e de menor probabilidade que abrange vários arquivos.
  • Sem modelo do repositório inteiro. O assistente raramente sabe que um helper adequado já existe fora de seu contexto. Sem recuperação, ele não pode reutilizar o que não vê, então regenera. Os praticantes descrevem agentes que "recriam lógica similar do zero para cada nova tarefa", e Eno Reyes (Factory) enquadra a própria qualidade do código como o principal preditor de se a IA ajuda ou prejudica uma base de código.
  • Amnésia por turno + escopo do prompt. Cada requisição é tratada como uma unidade autocontida ("faça isto funcionar"), então o agente faz overfitting de uma solução estreita e de uso único em vez de generalizar. A OX Security chama isso de Sobre-especificação (vista em 80–90% do código de IA) e Evitação de Refatorações (80–90%): o modelo emite código funcional para o prompt imediato, mas nunca consolida.
  • Bajulação / menor atrito. Os modelos otimizam por uma saída que satisfaz visivelmente o pedido sem tocar em arquivos não relacionados. Duplicar é "seguro" e local; refatorar arrisca quebrar algo que o modelo não consegue ver, então ele evita.
  • O ferramental torna isso barato. Como a GitClear observa, o tab-para-aceitar torna inserir um bloco novo quase gratuito, removendo o atrito que costumava empurrar os desenvolvedores para a reutilização.

Por que isso prejudica

  • Erosão de qualidade mensurada. A análise de 2025 da GitClear (211M de linhas alteradas, 2020–2024) descobriu que as linhas copiadas/coladas subiram de 8,3% para 12,3% enquanto as linhas "movidas" (refatoradas) caíram de ~24% para 9,5% — 2024 foi o primeiro ano em que as linhas copiadas/coladas excederam as linhas movidas, e blocos de 5+ linhas duplicadas saltaram ~8×.
  • Dívida de correção — "Bugs Déjà-Vu". A OX Security encontrou isso em 70–80% do código de IA: um bug corrigido em um clone sobrevive silenciosamente em todos os outros, então defeitos idênticos se repetem e cada um precisa de uma correção redundante. A duplicação transforma um bug em N bugs.
  • Manutenibilidade & carga de revisão. Cada mudança de comportamento precisa ser encontrada e editada em muitos lugares; os diffs incham com blocos quase idênticos que os revisores precisam comparar linha a linha para confirmar que são realmente idênticos (e não sutil e perigosamente diferentes).
  • Superfície de segurança. Um padrão vulnerável (verificação de auth ausente, entrada não escapada) se propaga para cada cópia, e uma correção de endurecimento posterior pode deixar clones de fora — a duplicação multiplica a superfície de patch.

##Treatment

Táticas de revisão & prompting

  • Diga ao modelo onde olhar primeiro: "Antes de escrever código novo, procure no repositório por um helper existente que faça isto e reutilize-o; não duplique." Cole o módulo relevante para que ele esteja no contexto.
  • Exija consolidação explicitamente: "Se dois blocos diferem apenas por parâmetros, extraia uma única função parametrizada (Extrair Função) e chame-a de ambos."
  • Faça-o rodar as ferramentas: "Rode o linter / jscpd / a verificação de duplicação e resolva qualquer clone que ele relatar antes de retornar o diff." Os agentes se autocorrigem bem quando recebem um sinal determinístico.
  • Na revisão, trate um bloco com aparência de colado como um convite para fazer grep: procure por uma linha distintiva; se ela já existir, devolva para extração.

A refatoração — Extrair Função + parametrizar

// depois: uma única fonte de verdade, chamada de ambos
async function getResource<T>(path: string): Promise<T> {
  const res = await fetch(`${API}/${path}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data as T;
}

const getOrder = (id: string) => getResource<Order>(`orders/${id}`);
const getUser  = (id: string) => getResource<User>(`users/${id}`);

Agora uma correção no tratamento de erros ou no parsing da resposta aterrissa uma única vez. Onde as duplicatas diferem por uma etapa em vez de um valor, prefira Formar Template Method ou passe a etapa variável como um callback. Os movimentos canônicos aqui são Extrair Função, Extrair Variável/Constante (para literais redigitados) e Subir Método quando os clones vivem em classes irmãs — todas curas padrão para Código Duplicado.

Salvaguarda, não apenas limpeza: conecte um detector de clones ao CI com um limiar baixo para que a próxima duplicata gerada por IA falhe o build em vez de ser mesclada. Combine-o com uma regra da casa de uma linha em CLAUDE.md/AGENTS.md ("reutilize helpers existentes; nunca cole uma quase-duplicata") para que a restrição acompanhe o contexto do modelo a cada turno.

##Detected by