Logotipo do ConstructiCat
CodeBust.
Explorar a seção ▾

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

Ao escrever código novo, assistentes de IA tendem a colar ou rederivar uma cópia levemente ajustada de uma lógica que já existe em vez de chamar ou extrair uma função compartilhada, de modo que o código duplicado cresce a cada nova funcionalidade.

##Sinais e sintomas

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).
  • Um detector de clones (jscpd, PMD CPD, as métricas de duplicação do Sonar) aponta novos blocos duplicados — ou um salto em duplicated_lines_density — no PR que adicionou a funcionalidade.
// 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.

Relacionado, mas diferente: a Evitação de Refatoração trata de nunca reestruturar o código que já existe — o comportamento novo é acoplado por cima em vez de remodelar o design. Este smell é mais restrito e acontece no momento da criação: o modelo rederiva uma lógica que já tem lugar próprio em vez de chamá-la ou extraí-la.

##Causas do problema

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. Eno Reyes (Factory) liga o ganho com IA à própria base de código — "quanto maior a qualidade da base de código, mais a IA acelerava a organização" (entrevista no blog do Stack Overflow) — e os clones já presentes no repositório viram o padrão que a próxima geração copia de novo.
  • 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 (Over-Specification) — "soluções hiperespecíficas e de uso único que não podem ser adaptadas para outros fins" — e a encontrou em 80–90% do código de IA analisado (relatório OX).
  • Bajulação / menor atrito. Os modelos otimizam por uma saída que satisfaz visivelmente o pedido sem tocar em arquivos não relacionados. Copiar um bloco fica restrito ao arquivo em edição; chamar um helper compartilhado — ou extraí-lo — exige mexer em outro módulo que o modelo não viu por inteiro, então a cópia local vence.
  • O ferramental torna isso barato. Como aponta o resumo da DevClass sobre o relatório da GitClear, aceitar uma sugestão com a tecla Tab torna inserir um bloco novo quase gratuito, removendo o atrito que costumava empurrar os desenvolvedores para a reutilização (DevClass).

Por que isso prejudica

  • Erosão de qualidade mensurada. A análise de 2025 da GitClear, com 211 milhões de linhas alteradas (2020–2024), constatou que as linhas associadas à refatoração (linhas "movidas") caíram de 25% das linhas alteradas em 2021 para menos de 10% em 2024, enquanto as linhas copiadas/coladas subiram de 8,3% para 12,3% no mesmo período; 2024 foi o primeiro ano em que as linhas copiadas/coladas superaram as movidas, e os blocos com cinco ou mais linhas duplicadas cresceram cerca de oito vezes ao longo de 2024 (GitClear 2025).
  • 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.

##Tratamento

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 e chame-a de ambos." Ou seja, Extrair Método seguido de Parametrizar Método.
  • 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 Método + Parametrizar Método

// 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 é aplicada uma única vez. Onde as duplicatas diferem por uma etapa em vez de um valor, prefira Formar Método Template ou passe a etapa variável como um callback. Os movimentos canônicos aqui são Extrair Método, Extrair Variável ou Substituir Número Mágico por Constante Simbólica (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.

##Detectado por

  • jscpd --min-tokens / --min-lines / --threshold — Duplicação por copiar e colar (limites min-tokens / min-lines configuráveis; permite a verificação --threshold na CI)
  • PMD CPD — CPD — detecção de duplicações baseada em tokens em mais de 30 linguagens
  • SonarQube / SonarSource S4144 — Métodos não devem ter implementações idênticas
  • SonarQube / SonarSource S1192 — Literais de string não devem ser duplicados
  • SonarQube duplicated_lines_density — Blocos duplicados / métrica duplicated_lines_density (mecanismo integrado de detecção de duplicações; reprova a verificação de qualidade acima do limite)
  • eslint-plugin-sonarjs sonarjs/no-identical-functions
  • eslint-plugin-sonarjs sonarjs/no-duplicate-string