Remova código copiado e colado com segurança usando utilitários compartilhados
Remova código copiado e colado com um plano de refatoração por etapas que deduplica funções geradas por IA, preserva comportamento idêntico e verifica cada mudança.

O que “copy-paste code” quebra com o tempo
Copy-paste code é quando a mesma lógica aparece em vários lugares com pequenas edições: uma variável renomeada, um valor padrão diferente, uma mensagem de erro levemente alterada. No começo parece mais rápido que criar uma função reutilizável. Meses depois, torna-se uma armadilha.
Em projetos reais costuma parecer com o mesmo helper de requisição repetido em três arquivos, cinco versões de “formatar uma data” ou duas checagens de autenticação quase idênticas que discordam sobre o que significa “logado”. O código funciona até que uma daquelas cópias seja corrigida e as outras não.
Apps gerados por IA tendem a duplicar lógica porque o modelo resolve o problema que está à sua frente cada vez que o solicita. Peça uma nova página e ele pode recriar helpers em vez de reutilizar os existentes. Ferramentas que geram código em rajadas (página por página, recurso por recurso) tornam a repetição ainda mais provável. Você fica com uma base de código cheia de quase-clones que parecem consistentes, mas não são.
A duplicação aumenta bugs e desacelera mudanças de uma forma simples: cada alteração vira uma caça ao tesouro multi-arquivo. Perde uma cópia e você entrega comportamento inconsistente. As diferenças também são fáceis de ignorar em revisão porque costumam ser pequenas.
Quando as pessoas dizem “comportamento idêntico”, geralmente querem dizer “os usuários não devem notar nenhuma mudança”. Isso inclui a saída óbvia, mas também detalhes que as pessoas esquecem:
- Mesmas entradas produzem as mesmas saídas, incluindo casos de borda.
- Mesmos erros acontecem nas mesmas situações, com as mesmas mensagens ou códigos.
- Mesmos efeitos colaterais ocorrem (logging, cache, retries, gravações no banco).
- Comportamento sensível a timing permanece dentro dos limites esperados.
Se você remover copy-paste code com segurança, o objetivo real não é “código mais limpo”. É “sem surpresas em produção”.
Escolha um bom primeiro alvo de deduplicação
Comece por uma área pequena que seja fácil de observar e difícil de interpretar mal. Bons alvos iniciais são padrões de helper como validação de entrada, formatação de datas ou valores monetários, checagens de auth repetidas ou o mesmo wrapper de chamada de API usado em várias telas.
Escolha algo em que o código faça basicamente um trabalho e você consiga descrevê-lo em uma frase. Se você precisa de três parágrafos para explicar o que faz, não é um primeiro alvo.
Antes de tocar em qualquer coisa, defina os limites. Anote o que entra (inputs), o que sai (outputs) e o que mais é afetado (efeitos colaterais como logging, cache, leitura de variáveis de ambiente ou mutação de um objeto compartilhado). Funções geradas por IA frequentemente escondem efeitos colaterais em lugares pequenos, como valores padrão “úteis” ou silencioso swallowing de erros.
Um candidato sólido geralmente:
- Aparece em 3+ lugares com linhas em grande parte iguais
- Tem entradas e saídas claras (poucas leituras globais)
- Falha de maneira visível (erro, código de status, mensagem)
- Não está no seu caminho mais quente e frágil (pagamentos, núcleo de auth, migrations)
- Tem casos de borda que você consegue listar sem chutar
Em seguida, inventarie as duplicatas. Não confie apenas nos resultados de busca. Abra cada arquivo e confirme que ele realmente faz o mesmo trabalho, e não apenas código parecido.
Por fim, decida o que não pode mudar. Seja específico: tipos de erro exatos, mensagens exatas (se usuários ou testes dependem delas), como valores vazios são tratados e qualquer comportamento “estranho” de caso de borda. É aqui que surpresas se escondem: uma cópia remove espaços em branco, outra não, e de repente o login falha para um pequeno grupo de usuários.
Trave o comportamento atual antes de mudar qualquer coisa
Você precisa de prova do que o código faz hoje, incluindo as partes estranhas que você gostaria que não existissem. Sem essa linha de base, um “cleanup simples” pode mudar silenciosamente saídas, mensagens de erro ou casos de borda dos quais callers dependem.
Comece capturando exemplos reais de entradas e saídas. Pegue alguns trechos dos logs, tickets de suporte ou suas próprias execuções manuais. Se você não tem logs, rode o app e registre algumas requisições reais ou fluxos de usuário (o que você digitou, o que viu e o que o sistema retornou).
Anote falhas esperadas também. Muitos helpers gerados por IA diferem principalmente em como falham: um retorna um objeto vazio, outro lança, um terceiro retorna 200 com uma string de erro. Essas diferenças importam quando outras partes do app foram construídas esperando um desses comportamentos.
Crie um pequeno conjunto de cenários “dourados” que você possa reexecutar depois de cada pequena mudança. Mantenha curto e realista:
- Uma entrada comum que deve ter sucesso
- Uma entrada de limite (vazia, string longa, zero, campo ausente)
- Uma entrada ruim conhecida que deve produzir um erro específico
- Um cenário onde flags ou cabeçalhos opcionais mudam o comportamento
- Um caso sensível de performance (payload grande ou loop repetido)
Também observe dependências ocultas que podem tornar o comportamento inconsistente entre execuções: variáveis de ambiente, feature flags, tempo atual e fusos horários, IDs aleatórios, caches globais e chamadas de rede.
Exemplo: se três helpers duplicados de requisição todos adicionam um header de auth, verifique se eles diferem quando o token estiver ausente (lançam vs retornam null) e se leem o token de um global, localStorage ou variável de ambiente. Esse é o comportamento que você precisa preservar enquanto deduplica.
Mapeie as duplicatas e suas pequenas diferenças
Copy-paste code raramente bate perfeitamente. Antes de mesclar qualquer coisa, faça um mapa claro do que é compartilhado e do que mudou entre as versões.
Coloque as funções duplicadas lado a lado e compare linha a linha. Não passe os olhos por cima. Código gerado por IA frequentemente muda um pequeno detalhe (um valor padrão, o nome de um header, uma verificação nula faltando) que só aparece em produção.
A maioria das diferenças cai em algumas categorias:
- Nome e forma (nomes de parâmetros, campos retornados, mensagens de erro)
- Defaults (timeouts, retries, string vazia vs null, valores de fallback)
- Tratamento de casos de borda (campos faltantes, respostas 204, vars de ambiente indefinidas)
- Efeitos colaterais (logging, métricas, cache, escrita em storage)
- Formatação de entrada/saída (trim, encoding, parsing de data)
Depois de listar diferenças, escolha uma versão como baseline. “Baseline” não significa “melhor escrita.” Significa “a mais utilizada pelo resto do app”, frequentemente a que é usada em mais lugares ou a que bate com o que os usuários veem hoje em produção.
Então decida quais diferenças são intencionais vs acidentais. Se uma diferença está documentada, testada ou claramente exigida por um caller específico, trate-a como intencional. Se parece uma variação aleatória (timeout padrão diferente sem motivo, mapeamento de erro inconsistente, casing de header levemente diferente), assuma que é acidental até provar o contrário.
Um exemplo concreto: dois helpers de requisição ambos adicionam um header Authorization, mas apenas um também envia cookies por padrão. Essa diferença de uma linha pode mudar quem está “logado” em produção.
Projete um utilitário compartilhado que permaneça simples
O utilitário compartilhado deve parecer quase entediante. A versão um não é sobre “melhor design.” É sobre o mesmo comportamento em um só lugar.
Mantenha a superfície pequena
Comece com a menor unidade que se repete. Se cinco funções normalizam as mesmas entradas ou constroem o mesmo payload, extraia apenas essa parte. Deixe regras de validação, retries e casos especiais onde estão até provar que são realmente compartilhados.
Um utilitário pequeno é mais fácil de revisar porque há menos maneiras de mudar comportamento acidentalmente. Sinais de que você manteve pequeno:
- O nome descreve uma ação única.
- Recebe entradas explícitas e retorna um valor.
- Não sabe sobre rotas, telas ou tabelas do banco.
- Evita defaults ocultos que adivinham o que você quis dizer.
Prefira parâmetros explícitos a globais
Código gerado por IA frequentemente alcança variáveis globais, vars de ambiente ou singletons compartilhados. Isso torna a deduplicação arriscada porque cada caller pode depender de um estado oculto ligeiramente diferente.
Ao invés disso, passe o que o utilitário precisa como parâmetros. Por exemplo, passe baseUrl, headers ou timezone. Pode parecer repetitivo no começo, mas torna diferenças visíveis e mantém o código compartilhado honesto.
Mantenha efeitos colaterais no caller
Se o código duplicado faz logging, escreve no DB ou dispara analytics, mantenha esses efeitos fora do utilitário compartilhado quando possível. Mire em “dadas entradas, retorne saída.” O caller decide se vai logar, armazenar ou engolir um erro.
Exemplo: se três endpoints constroem uma mensagem de erro e também a logam, extraia apenas buildErrorMessage(details). Cada endpoint pode manter seu próprio logging para que você não mude acidentalmente volume ou timing de logs.
Passo a passo: movimentos de refatoração pequenos e verificados
Trate isso como uma série de trocas pequenas, não como um grande rewrite. Você quer o comportamento de hoje, só movido para um lugar só.
-
Escolha uma duplicata “fonte da verdade”.
-
Copie exatamente essa implementação para um novo arquivo utilitário compartilhado. Não limpe ainda. Sem renomear, sem formatar, sem “aproveitar para melhorar”.
-
Migre em saltos pequenos:
- Crie o utilitário compartilhado copiando uma função existente tal qual, e mantenha as duplicatas antigas no lugar.
- Atualize um único ponto de chamada para usar o novo utilitário.
- Rode seus cenários dourados e compare saídas, logs e efeitos colaterais.
- Repita: mova mais um call site, reexecute os mesmos checks.
- Depois que todos os call sites usarem o utilitário, delete as duplicatas antigas e remova imports não usados.
Entre cada salto, mantenha um commit limpo e revisável. Se algo quebrar, você pode reverter uma mudança pequena em vez de caçar em um diff gigante.
Se call sites têm formas de argumento ligeiramente diferentes, use um wrapper de compatibilidade temporário. Mantenha a conversão bagunçada na borda (perto do call site), não dentro do utilitário compartilhado.
Como verificar que o comportamento permaneceu idêntico
A forma mais segura de provar que você não mudou comportamento é manter ambos os caminhos por um curto tempo e compará-los com as mesmas entradas.
Salve de 10 a 20 entradas reais que você possa reproduzir (fixtures de logs são ideais). Rode-as pela função antiga e pelo novo utilitário, na mesma ordem, e compare resultados incluindo forma e tipos, não só valores.
Não pare no happy path. Muitas quebras aparecem apenas quando algo falha. Compare:
- Mensagens de erro e tipos de erro (incluindo redação se a UI as mostra)
- Códigos de status para respostas de API (200 vs 204 vs 404 importa)
- Tratamento de vazio, null e ausência ("" vs null vs undefined)
- Ordenação de itens (especialmente se você ordena ou mapeia chaves)
- Efeitos colaterais como logging, retries ou cache
Se diferenças são difíceis de achar, adicione um toggle de debug temporário que rode ambos os caminhos e imprima um diff compacto quando discordarem. Quando terminar a migração, remova o toggle.
Finalmente, observe performance em caminhos comuns. Meça algumas chamadas típicas antes e depois e confirme que você não adicionou consultas DB extras, parsing JSON repetido ou chamadas de rede desnecessárias.
Armadilhas comuns que mudam comportamento sem notar
A maioria das refatorações falha por um motivo entediante: você mudou duas coisas ao mesmo tempo. Mantenha “mesma saída para a mesma entrada” como meta, não “código mais bonito”.
Essas armadilhas aparecem muito:
- Mudar defaults sem perceber (
timeout = 0vstimeout = 30, ouundefinedtratado diferente denull) - Perder coerção de tipo (string "0" virando número
0, campo ausente virando string vazia) - “Limpar” o tratamento de erro de forma que falhas sejam escondidas (substituir um throw por um warning logado)
- Construir um utilitário que faz tudo (ele cresce flags, casos especiais e estado oculto)
- Migrar 9 call sites e esquecer o 10º (frequentemente um job background, tela admin ou endpoint raramente usado)
Exemplo: dois helpers de requisição podem ambos retryar em 500, mas apenas um retrya em 429 por rate limit. Se você os mesclar sem preservar essa exceção, um fluxo de checkout pode ficar mais lento ou começar a falhar em picos de tráfego.
Checklist rápido antes de mesclar a refatoração
Antes de mesclar, você quer remover duplicação sem mudar o que o app faz.
Checagens de design do utilitário
- O novo helper faz uma tarefa clara. Se faz duas, divida enquanto ainda é pequeno.
- Entradas e saídas são previsíveis. Evite defaults mágicos que dependem de estado global.
- O nome da função e parâmetros batem com os termos que as pessoas já usam.
- Não lê variáveis de ambiente, arquivos, contexto de requisição ou sessão de usuário internamente. Passe valores como parâmetros.
- Erros são tratados do mesmo jeito que antes (tipo, formato da mensagem, código de status se relevante).
Se estiver em dúvida se o comportamento mudou, compare com o que o código antigo produzia, não com o que você gostaria que produzisse.
Checagens de prontidão para merge
- Todo call site foi migrado. Não restam arquivos meio-movidos usando uma cópia antiga.
- Cópias antigas foram removidas (ou marcadas claramente para remoção imediata) para que não possam divergir.
- Cenários dourados passam, incluindo pelo menos um caminho infeliz (input ruim, campo ausente, timeout).
- Logs, métricas e mensagens de erro não ficaram mais barulhentos nem mais silenciosos de um jeito que atrapalhe o debug.
- Um rápido scan de diff não mostra segredos ou tokens movidos acidentalmente para o utilitário compartilhado.
Cenário de exemplo: deduplicando helpers de requisição gerados por IA
Você herda um app onde uma ferramenta de IA produziu três helpers parecidos: getJson(), postJson() e requestWithRetry(). Cada um “funciona”, mas cada endpoint chama um diferente, e bugs aparecem só em produção.
Quando você os alinha, percebe diferenças que importam: headers (Bearer vs API key, casing), timeouts (5 seconds vs 5000 ms), regras de retry (só rede vs também 503) e mapeamento de erro (retornar { ok: false } vs lançar vs envolver em message).
Ao invés de impor uma versão “melhor”, crie um builder de requisição compartilhado com inputs explícitos, como makeRequest({ method, url, headers, timeoutMs, retryPolicy, mapError }). A chave é que os defaults devem bater com o comportamento atual do primeiro endpoint que você migrar, não com o que você desejaria que fosse.
Migre um endpoint primeiro, de preferência um simples que ainda lide com auth e tratamento de erro. Mantenha o helper antigo no lugar para todo o resto.
Seus cenários dourados pegam mudanças sutis rapidamente. Exemplo: um endpoint que retornava 204 No Content costumava retornar null, mas o novo utilitário tenta chamar json() e lança. O cenário falha imediatamente e você corrige tratando 204 explicitamente.
Próximos passos se a base de código estiver muito emaranhada
Às vezes não dá para deduplicar com segurança porque as duplicatas escondem problemas mais profundos. Se cada função “parecida” tem efeitos colaterais diferentes, tratamento de erro diferente ou correções silenciosas de dados, um utilitário compartilhado pode quebrar fluxos reais de usuários.
Fazer uma pausa ajuda, mas não precisa ser um rewrite. O objetivo é um reset curto que torne refatorações pequenas possíveis de novo.
Sinais de que você deve reforçar segurança enquanto refatora
Se você está tocando código compartilhado, trate isto como checagens não-negociáveis:
- Autenticação inconsistente (algumas rotas checam auth, outras esquecem)
- Segredos expostos (API keys em código, logs ou bundles cliente)
- Entrada de usuário chegando a SQL ou queries sem validação rigorosa (risco de injeção)
- Lógica de “admin” dependendo de flag do front-end ou de checagem fraca de papel
- Mensagens de erro vazando detalhes internos (stack traces, nomes de tabela)
Consertar duplicatas sem tratar isso pode espalhar um padrão arriscado para o novo utilitário.
Mantenha o ímpeto sem reescrever o app todo
Mirar numa camada fina de estabilização primeiro: um pequeno conjunto de helpers compartilhados com regras estritas, mais testes ou snapshots ao redor dos fluxos mais usados. Depois remova duplicatas por cluster (todos os request helpers, depois todas as checagens de auth). Se um módulo está muito bagunçado, isole-o atrás de uma interface simples e postergue a limpeza interna até o resto do app ficar estável.
Se você herdou um protótipo gerado por IA de ferramentas como Lovable, Bolt, v0, Cursor ou Replit, uma auditoria externa pode poupar dias de trabalho. FixMyMess (fixmymess.ai) começa com uma auditoria de código gratuita para mapear duplicatas, diferenças de comportamento ocultas e problemas de segurança, depois ajuda a transformar o protótipo em software pronto para produção sem mudar o que os usuários já dependem.
Perguntas Frequentes
O que exatamente conta como “copy-paste code”?
Copy-paste é a mesma lógica duplicada em vários arquivos com pequenas diferenças fáceis de perder. Normalmente funciona no começo, mas com o tempo correções aparecem em uma cópia e não nas outras, então o comportamento diverge e surgem bugs.
Por que apps gerados por IA acabam com tantas duplicatas?
Porque o modelo tende a resolver o que está na frente dele agora, ele frequentemente recria helpers em vez de reutilizar os existentes. Quando recursos são gerados página a página, você acaba com near-clones que parecem consistentes mas tratam casos de borda, padrões e erros de maneira diferente.
Qual é a melhor primeira coisa para deduplicar?
Comece com algo pequeno, visível e fácil de descrever em uma frase—validação de entrada, formatação, um helper de requisição HTTP ou uma checagem de autenticação repetida. Evite caminhos críticos no começo para aprender o padrão de refatoração com baixo risco.
O que devo documentar antes de mexer em código duplicado?
Anote entradas, saídas e efeitos colaterais de cada duplicata antes de mudar qualquer coisa. Inclua detalhes que as pessoas esquecem, como mensagens de erro exatas, códigos de status, logging, cache e como valores vazios são tratados.
O que são “cenários dourados” e quantos preciso?
São um conjunto curto de cenários reais que você consegue reexecutar após cada pequena mudança para provar que o comportamento não mudou. Mantenha prático: um sucesso comum, um caso limite, uma falha conhecida com erro específico e um caso que muda por flags, cabeçalhos ou ambiente.
Como escolher a duplicata “baseline” para fundir no utilitário compartilhado?
Escolha a versão em que o resto do app mais depende, não a que parece mais bonita. Se um helper é usado em mais lugares ou corresponde ao que os usuários veem em produção, trate-o como baseline a ser preservada primeiro.
Qual é a forma mais segura de migrar call sites para o novo utilitário?
Em passos minúsculos: copie a função baseline para um arquivo compartilhado sem limpar nada, migre um ponto de chamada e reexecute seus cenários dourados. Commits pequenos e reversíveis são a defesa principal contra mudanças acidentais de comportamento.
Por que refatorações costumam quebrar o tratamento de erro mesmo quando a saída “parece igual”?
Porque refatorações frequentemente mudam o comportamento em falhas sem ninguém notar, especialmente entre lançar erros e retornar objetos, e no tratamento de respostas 204/vazias. O novo utilitário precisa preservar tipos de erro, mensagens e tratamento de vazio que callers já dependem.
Onde devem ficar logging, retries e outros efeitos colaterais após a deduplicação?
Mantenha efeitos colaterais como logging, métricas, gravações e analytics no caller sempre que possível. Assim o utilitário compartilhado fica previsível e você evita surpresas como dobrar o volume de logs ou mudar quando retries ocorrem.
Quando devo parar de refatorar e chamar uma auditoria externa ou ajuda?
Se as duplicatas escondem problemas de segurança—checagens de auth inconsistentes, segredos expostos ou validação fraca—deduplicar pode espalhar o risco para um único utilitário. Nesse caso, pare e trate a segurança primeiro ou peça uma auditoria externa.