Roda Reinventada.
O modelo escreve à mão código sob medida para algo que uma biblioteca padrão, uma dependência existente ou um helper já presente no repositório já faz — adicionando lógica duplicada e menos testada em vez de chamar o que já existe.
##Signs and Symptoms
Um revisor identifica a Roda Reinventada quando um diff introduz um trecho não trivial de lógica "do zero" para resolver um problema que já está resolvido — pelo runtime da linguagem, por uma dependência já presente em package.json ou por um utilitário que já existe em outro lugar do repositório. Sinais comuns:
- Um
deepClone,debounce,groupBy,chunk,retry,slugify,deepMergeouuuidescrito à mão quando o runtime ou uma biblioteca instalada já o fornece. - Uma regex sob medida de e-mail/URL/UUID em vez de um validador que já é uma dependência.
- Cálculos de data personalizados, parsing de query-string ou lógica de paginação que reimplementam
Intl/URLSearchParams/um recurso de ORM. - Dois ou três helpers privados quase idênticos espalhados por arquivos (a mesma roda re-esculpida por funcionalidade) — a duplicação que aparece nas métricas de copiar/colar.
- A versão reinventada está sutilmente errada: ela trata o caminho feliz, mas deixa passar casos extremos que o original testado em batalha cobre.
// Escrito por IA: um deep clone sob medida reinventado inline
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(deepClone);
const out = {};
for (const k in obj) out[k] = deepClone(obj[k]);
return out; // descarta silenciosamente Date, Map, Set, RegExp; entra em loop em ciclos
}
// ...mesmo que o runtime já forneça structuredClone(), E
// o repositório já exporta cloneDeep() de src/utils/object.ts
A verificação mais rápida: procure no repositório e em node_modules pela capacidade antes de aceitar o novo código. Se structuredClone, lodash, date-fns ou um helper local já o cobre, a nova função é uma roda reinventada.
##Reasons for the Problem
Por que os modelos produzem isso
- Sem contexto do repositório por padrão. O modelo muitas vezes não consegue ver seu
src/utils, suas dependências instaladas ou as convenções da casa, então recorre à conclusão estatisticamente mais provável: uma implementação inline autocontida. Ele reconstrói a roda porque nunca viu a sua. - Localidade do próximo token. LLMs otimizam para uma continuação localmente plausível, não para a minimalidade global. Escrever
function groupBy(...)é uma sequência de alta probabilidade; parar para descobrir quelodash.groupByjá está importado a três arquivos de distância não é algo que a previsão de tokens faça. - Os incentivos de treinamento recompensam a autossuficiência. Uma enorme parcela dos dados de treinamento são tutoriais, respostas do Stack Overflow e trechos que deliberadamente mostram a implementação completa. O modelo aprendeu que "responder bem" significa emitir código completo e autônomo — exatamente o instinto errado dentro de uma base de código madura.
- Defasagem do corte de treinamento. O modelo pode não saber que uma capacidade foi promovida à biblioteca padrão (por exemplo,
structuredClone,Array.prototype.at,Object.groupBy) ou que seu repositório adotou um helper após seu corte, então ele faz polyfill de algo que já existe. - Bajulação / menor resistência. Solicitado a "adicionar X," o modelo adiciona X da maneira mais direta em vez de contestar com "nós já temos isso." Ele raramente sugere espontaneamente "você não precisa escrever isso."
- Superespecificação. A análise da OX Security de mais de 300 repositórios constatou que a IA tende a um estilo de código "vanilla" que reconstrói funcionalidades comuns em vez de usar bibliotecas comprovadas, com soluções de escopo restrito e não reutilizáveis em cerca de 80–90% dos casos — cada nova variação ganha código novo em vez de reutilização.
Por que isso é prejudicial
- Manutenibilidade e dívida técnica. Esta é a face mensurável do declínio na qualidade do código de IA. A análise da GitClear sobre 211 milhões de linhas constatou que o código copiado/colado subiu de 8,3% (2020) para 12,3% (2024), os blocos de 5+ linhas duplicadas cresceram ~8x em 2024, e as linhas "movidas" (refatoradas) caíram de 24,1% para 9,5% — 2024 foi o primeiro ano em que o copiar/colar superou a refatoração. As rodas reinventadas são por onde essa duplicação entra.
- Correção. A versão sob medida pula os casos extremos que a implementação madura conquistou ao longo de anos de relatórios de bugs (fusos horários, Unicode, ciclos, escape). Parece certa e falha na cauda longa.
- Segurança. Reescrever criptografia, autenticação, sanitização ou validação em vez de usar uma biblioteca confiável é como o código escrito por IA "viola as melhores práticas de engenharia" em escala (o efeito "Exército de Juniores" da OX) — padrões vulneráveis chegam à produção mais rápido do que a revisão consegue detectá-los.
- Carga de revisão. Os revisores agora precisam ler, raciocinar sobre e testar 40 linhas de lógica personalizada em vez de reconhecer uma única chamada de biblioteca confiável — multiplicado em cada PR.
##Treatment
Táticas de revisão e de prompting
- Dê ao modelo o contexto que lhe falta. Antes de gerar, aponte-o para seus utilitários e dependências: "Reutilize helpers de
src/utils/*e bibliotecas já presentes empackage.json; não adicione novas sem perguntar." Cole as dependências relevantes dopackage.jsone o índice dos seus utils. - Faça do "buscar primeiro" uma regra. Instrua: "Antes de escrever qualquer helper, verifique se a biblioteca padrão, uma dependência existente ou um utilitário do repositório já faz isso; em caso afirmativo, chame-o." Ferramentas agênticas devem fazer grep no repositório primeiro.
- Questione cada novo helper privado na revisão. Para cada utilitário feito à mão pergunte: o runtime faz isso? uma dependência faz isso? nós já temos isso? Se sim, é Roda Reinventada — substitua-a.
- Execute a toolchain. Um scanner de duplicação (jscpd / PMD CPD / SonarQube) na CI sinaliza a faceta de copiar/colar automaticamente; conecte-o ao portão para que rodas re-esculpidas façam o build falhar.
A refatoração — isto é o clássico Código Duplicado e Reinventar a Roda; a correção é Substituir Algoritmo (trocar o corpo sob medida pela chamada da biblioteca/padrão) e, onde existem várias cópias, Extrair Função / subir para um único helper compartilhado.
// ANTES — reinventado, parcialmente correto, duplicado por funcionalidade
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(deepClone);
const out = {};
for (const k in obj) out[k] = deepClone(obj[k]);
return out;
}
const copy = deepClone(state);
// DEPOIS — chame o que já existe (casos extremos corretos, zero código novo)
const copy = structuredClone(state);
// ou, se o padrão do repositório for o helper local:
import { cloneDeep } from "@/utils/object";
const copy = cloneDeep(state);
Se a roda foi reinventada em vários arquivos, apague todas as cópias e direcione cada chamador para a única fonte de verdade. Resultado líquido: menos linhas, menos bugs e as métricas de duplicação voltam na direção certa.
##Detected by
- jscpd duplication — Detecção de copiar/colar (min-tokens / limite)
- PMD cpd — CPD (Copy/Paste Detector)
- SonarQube common-duplications — Blocos duplicados / duplicated_lines_density