1. Início
  2. Blog
  3. Modernização de legado
Modernização de legado

Refatoração de código legado: como mudar código antigo com segurança

Como refatorar código legado com segurança: testes de caracterização, alvos por risco, passos pequenos e reversíveis e quando partir para migração incremental.

Illustration of a scaffolded tower of code blocks with one orange block carefully extracted, representing safe legacy code refactoring
Nesta página · 6 seções
  1. O que torna o código legado difícil de refatorar?
  2. Como adicionar testes antes de mexer em qualquer coisa?
  3. Por onde começar a refatorar?
  4. Como refatorar em passos pequenos enquanto entrega funcionalidades?
  5. Quando a refatoração deve dar lugar a uma migração?
  6. Quer uma segunda opinião sobre a sua base de código?

Principais pontos

  • Refatorar significa mudar a estrutura, não o comportamento. Se o comportamento muda, é uma funcionalidade ou uma correção de bug, e deve ficar num commit separado.
  • Escreva testes de caracterização primeiro. Eles registram o que o código faz hoje, mesmo quando esse comportamento parece errado.
  • Comece onde as mudanças são frequentes e arriscadas, não onde o código é mais feio.
  • Trabalhe em passos pequenos que possam ser mergeados e revertidos isoladamente. Branches de refatoração de vida longa são onde os projetos morrem.
  • Se a própria stack é o problema (runtime sem suporte, nenhum profissional disponível para contratar, nenhum jeito de testar), pare de refatorar no lugar e parta para uma migração incremental com um chaveamento na frente de tudo.

A refatoração de código legado é segura quando você muda a estrutura do código sem mudar o que ele faz, e consegue provar isso com testes antes de mexer em qualquer coisa. A ordem é: travar o comportamento atual, escolher as partes que mais doem, mudá-las em passos pequenos e reversíveis e continuar entregando funcionalidades enquanto isso.

Dá errado quando times arrumam código que não entendem, sem rede de proteção, num branch gigante.

O que torna o código legado difícil de refatorar?

Código legado é difícil de mudar porque ninguém consegue prever o que uma mudança vai quebrar; a feiura é secundária. Nas bases de código que assumi, as mesmas coisas sempre aparecem:

  • Nenhum teste, ou testes que não testam nada. Você não consegue dizer se uma mudança preservou o comportamento.
  • Acoplamento escondido. Uma função que parece autocontida lê uma variável global, escreve numa tabela compartilhada ou depende da ordem das chamadas.
  • Comportamento que ninguém documentou. Condições de aparência estranha muitas vezes codificam um caso real de cliente ou um incidente passado. Apagá-las como código morto é um erro clássico.
  • Falta de contexto. O desenvolvedor original ou a agência foi embora, e o histórico de commits diz coisas como “fix” e “update”.
  • Fluxos de dinheiro misturados com todo o resto. A lógica de cobrança, checkout ou permissões está emaranhada no código de UI ou de dados, então uma edição pequena tem um raio de impacto grande.

O primeiro objetivo não é limpar nada, e sim tornar barato verificar uma mudança: se você não consegue rodar o projeto localmente, fazer um build reproduzível e ver o resultado de um teste em minutos, resolva isso primeiro. Quando assumo uma base de código herdada, sigo sempre a mesma ordem: acessos primeiro, depois o build, depois os fluxos de dinheiro, depois os riscos. A refatoração começa quando os dois primeiros estão sólidos.

Como adicionar testes antes de mexer em qualquer coisa?

Você adiciona testes de caracterização: testes que capturam o que o código realmente faz agora, não o que ele deveria fazer. Michael Feathers popularizou essa abordagem em Working Effectively with Legacy Code.

Comece por fora, não por dentro

Testes unitários em internals emaranhados são lentos de escrever e frágeis. Comece numa fronteira que você consegue observar:

  • Um endpoint HTTP: envie uma requisição, registre a resposta.
  • Um estado do banco de dados: execute uma operação, faça asserções sobre as linhas que mudaram.
  • Uma página renderizada ou um fluxo de ponta a ponta: cadastrar, finalizar a compra, candidatar-se, pagar.

Esses testes são grosseiros, mas pegam mudanças que os usuários conseguem ver. Eu sempre cubro primeiro os fluxos de dinheiro: checkout, cobrança, cadastro, qualquer coisa que movimente valores ou deixe pessoas sem acesso.

Registre, depois faça a asserção

O processo é simples:

  1. Chame o código com entradas realistas.
  2. Anote a saída, mesmo que pareça errada.
  3. Transforme essa saída numa asserção.
  4. Repita para os casos de borda que você encontrar em logs, tickets de suporte e relatórios de bug antigos.

Se encontrar um bug real nesse processo, anote, mantenha o teste verificando o comportamento atual e corrija depois, num commit próprio. Misturar correções de bug numa refatoração torna impossível saber o que mudou.

Use a produção como fonte de dados de teste

Amostras sanitizadas de requisições de produção mostram os casos que o código realmente trata. Remova os dados pessoais antes que eles cheguem a uma fixture.

Quebre dependências só o necessário

Quando o código é impossível de testar porque chama uma API de pagamento ou o relógio do sistema, introduza uma costura fina: passe a dependência como parâmetro ou envolva-a numa função pequena que você possa substituir nos testes. Mantenha essas mudanças de costura minúsculas e mecânicas, já que são o único tipo de edição que você faz antes de ter testes.

Por onde começar a refatorar?

Comece onde o código muda com frequência e os erros são caros. Código feio em que ninguém mexe pode continuar feio.

Quando classifico os candidatos, estes são os sinais que observo, mais ou menos nesta ordem:

  1. Frequência de mudança. O histórico do controle de versão mostra quais arquivos são editados a cada sprint; estrutura bagunçada ali cobra um imposto repetidas vezes.
  2. Concentração de bugs. Tickets de suporte e notas de incidentes apontam para os módulos que vivem quebrando.
  3. Risco. Fluxos de dinheiro e autenticação, onde falhas custam dinheiro ou confiança.
  4. Bloqueios do roadmap. Se os próximos três itens do roadmap mexem no mesmo módulo, comece por ele.

Minha regra prática: se um módulo é ao mesmo tempo alterado com frequência e assustador de alterar, ele vem primeiro.

Por onde não começar

  • Renomear e reformatar a base de código inteira. Isso gera diffs ruidosos e conflitos de merge e não resolve nada.
  • O código mais antigo, só porque é o mais antigo.
  • Código que você planeja apagar em breve. Não dê polimento no que está prestes a sumir.

A refatoração deve ser guiada por evidências sobre custo e risco, não por gosto.

Como refatorar em passos pequenos enquanto entrega funcionalidades?

Você faz cada passo de refatoração pequeno o suficiente para ser mergeado no branch principal por conta própria e intercala esses passos com o trabalho em funcionalidades, em vez de pausar o roadmap. Na minha experiência, times que param tudo por um “trimestre de refatoração” muitas vezes perdem a confiança dos stakeholders e terminam com um branch pela metade.

Use a regra do “deixe melhor” no ponto de trabalho

Quando uma funcionalidade exige mexer num módulo bagunçado, gaste um esforço limitado deixando essa área mais fácil de mudar primeiro e depois construa a funcionalidade. Kent Beck resume assim: “torne a mudança fácil, depois faça a mudança fácil.” A funcionalidade justifica a refatoração, e o negócio enxerga as duas.

Mantenha os passos reversíveis

Bons passos pequenos são assim:

  • Extraia uma função, rode os testes, faça o merge.
  • Mova uma função para um módulo melhor, rode os testes, faça o merge.
  • Introduza uma nova implementação atrás de uma flag, mande um pouco de tráfego para ela, compare os resultados, depois faça a troca e apague a antiga.

Esse último padrão é o jeito mais seguro que conheço de mudar lógica arriscada: se o caminho novo se comportar mal, normalmente dá para voltar sem um deploy.

Separe os commits por intenção

Mantenha separados os commits de refatoração e os de mudança de comportamento. Assim, quem revisa consegue ler um commit de refatoração sabendo que nada deveria mudar, e um teste falhando avisa na hora que algo mudou.

Como isso foi no meu time

Na Inovaula, construí a v2 da plataforma web e depois liderei o time, e chegamos ao fluxo de entregas mais estável que o time já tinha tido. Olhando para trás, atribuo isso menos a alguma grande limpeza e mais ao hábito descrito acima: mudanças pequenas, revisadas com cuidado, mergeadas com frequência, com a estrutura melhorando como parte do trabalho normal.

Quando a refatoração deve dar lugar a uma migração?

A refatoração melhora o código dentro de uma stack, mas não resolve uma stack que é, ela própria, o obstáculo. Considere uma migração quando:

  • O runtime, o framework ou bibliotecas importantes não recebem mais atualizações de segurança.
  • Você não consegue contratar, ou o onboarding demora demais, por causa da tecnologia.
  • A arquitetura impede qualquer teste, por exemplo lógica que vive no banco de dados ou em templates sem costuras.
  • Cada refatoração exige tanta estrutura de apoio que, na prática, você já está reconstruindo.

Mesmo assim, prefiro a migração incremental à reescrita big-bang. A regra que sigo: colocar um chaveamento na frente de tudo antes de escrever código novo. Uma camada de roteamento, um proxy ou feature flags permitem mandar uma fatia do tráfego para o sistema novo enquanto o antigo continua atendendo todo o resto. Uma reescrita normalmente custa mais e demora mais, porque o sistema antigo precisa continuar rodando em paralelo o tempo todo.

Na avanzzada, liderei a migração completa de uma plataforma que se candidata automaticamente a vagas de emprego para seus usuários. Toda a arquitetura saiu de uma stack legada para uma nova enquanto as pessoas continuavam usando todos os dias. O que tornou isso viável foram os hábitos deste guia: saber o que o sistema atual faz, colocar o chaveamento primeiro e mover uma peça de cada vez.

Quer uma segunda opinião sobre a sua base de código?

Se você está diante de uma base de código antiga e sem saber se deve refatorar ou migrar, mande um email para me@filipeeduardo.dev com uma descrição curta ou um link, e eu digo o que verificaria primeiro.

Perguntas frequentes

Qual a diferença entre refatorar e reescrever?

Refatorar muda a estrutura em passos pequenos enquanto o comportamento continua o mesmo e o sistema segue rodando. Uma reescrita substitui o código por inteiro, muitas vezes em paralelo, e normalmente demora mais e traz mais risco, porque você precisa redescobrir cada comportamento que o código antigo tratava.

Dá para refatorar código legado sem testes?

Só nas menores mudanças mecânicas, como renomear com uma ferramenta em que você confia ou adicionar uma costura. Para todo o resto, escreva testes de caracterização primeiro, começando pelos caminhos de maior risco. Refatorar às cegas significa que você vai descobrir as quebras pelos seus usuários.

Quanto tempo um time deve dedicar à refatoração?

Não existe um número universal. Trate isso como parte do trabalho em funcionalidades, mais um trabalho direcionado nos módulos que causam incidentes repetidos, e evite projetos de refatoração separados sem um resultado de negócio visível.

Devo usar ferramentas de IA para refatorar código legado?

Elas ajudam em edições mecânicas e a explicar código desconhecido, mas não conhecem as suas regras de negócio escondidas. Trate o que elas produzem como o pull request de um júnior: rode contra os seus testes de caracterização e revise cada diff antes do merge.

Como saber se a refatoração não quebrou nada?

Os testes que passavam antes continuam passando depois, mais o monitoramento de produção. Para lógica arriscada, compare os caminhos antigo e novo atrás de uma flag antes de fazer a virada. Nenhum processo elimina o risco, mas este o mantém pequeno e visível.

legacy code refactoring

Filipe Eduardo

Engenheiro de Software Sênior. Sete anos construindo produtos web e mobile de ponta a ponta e liderando os times que os entregam.

Acompanhe

Quer conversar
sobre isso?

Trabalho com problemas assim todos os dias. Me conte o seu.

Fale comigo