Pular para o conteúdo
Categoria: Fundamentos & Boas Práticas15 min de leitura

O que é refatoração e quando aplicar

Por Lucas Andrade ·

Aprenda a melhorar a estrutura interna do código sem mudar seu comportamento, com técnicas seguras, exemplos práticos, trade-offs e os sinais que pedem refatoração.

O que é refatoração e quando aplicar

Refatorar é uma das habilidades que mais separam um desenvolvedor iniciante de um experiente. Não se trata de reescrever tudo nem de "limpar o código quando der tempo": é uma disciplina precisa, com técnicas próprias e regras de segurança. Neste artigo você vai entender o que é refatoração de verdade, quando aplicá-la, como reconhecer os sinais de que o código precisa melhorar e como fazer isso sem quebrar nada.

O que é refatoração

Martin Fowler, autor do livro de referência sobre o tema, define refatoração de forma cirúrgica: é o processo de alterar a estrutura interna de um software sem mudar seu comportamento externo observável (Fowler, 2018). Em outras palavras, o programa continua fazendo exatamente a mesma coisa do ponto de vista de quem o usa; o que muda é a organização interna do código.

Essa definição tem duas consequências importantes:

  • Refatorar não adiciona funcionalidade. Se você está implementando um recurso novo, isso não é refatoração — é desenvolvimento. As duas atividades devem ser separadas, idealmente em commits distintos.
  • Refatorar não conserta bugs. Corrigir um comportamento errado muda o comportamento observável; por definição, não é refatoração.

Manter essa fronteira nítida é o que torna a refatoração segura. Quando você sabe que o comportamento não deveria mudar, qualquer mudança de comportamento detectada por um teste é imediatamente um sinal de erro.

Vale notar uma sutileza na palavra "refatoração". No uso casual, qualquer faxina de código é chamada de "refatorar". Fowler, porém, usa o termo num sentido estrito: cada refatoração é uma transformação pequena e nomeada que preserva comportamento. A diferença é prática. Quando você diz "vou refatorar este módulo" e passa três dias mexendo em tudo ao mesmo tempo, você não está refatorando no sentido disciplinado — está reescrevendo, e reescrevendo é uma atividade de risco muito maior. A refatoração de verdade avança em micro-passos reversíveis.

Refatoração não é o mesmo que reescrita

Reescrita joga fora o código existente e começa do zero. Refatoração transforma o código existente de dentro para fora, mantendo-o funcional a cada passo. A reescrita é tentadora porque dá a ilusão de um recomeço limpo, mas costuma subestimar o conhecimento embutido no código antigo — todos aqueles if estranhos que tratam casos de borda reais descobertos ao longo de anos. Refatorar preserva esse conhecimento enquanto melhora a forma.

Por que refatorar

Todo código tende a degradar com o tempo. Cada ajuste apressado, cada exceção tratada de última hora, cada "só mais um if" deixa o sistema um pouco mais bagunçado. A esse acúmulo damos o nome de dívida técnica, e a refatoração é a forma de pagá-la.

Os principais ganhos de refatorar regularmente:

  • Legibilidade: código mais claro é mais barato de entender, e desenvolvedores passam mais tempo lendo código do que escrevendo.
  • Manutenibilidade: estrutura limpa facilita mudanças futuras.
  • Detecção de bugs: ao reorganizar, você frequentemente expõe lógica confusa que escondia defeitos.
  • Velocidade sustentável: um código bem cuidado mantém o time rápido ao longo do tempo, em vez de afundar em complexidade.

Refatoração é, no fim, um investimento na sua própria velocidade futura — e na de quem vai herdar o código. É uma das práticas centrais do O que é Clean Code? Guia completo de código limpo, que trata a melhoria contínua como parte do ofício, não como luxo.

Há uma metáfora financeira útil aqui. A dívida técnica, como a dívida bancária, cobra juros: cada vez que você toca num trecho confuso, paga um pouco mais caro do que pagaria se ele estivesse limpo. Pequenas refatorações contínuas funcionam como pagar a fatura todo mês; adiar acumula juros até o ponto em que o time inteiro fica lento e cada mudança vira um drama. O objetivo não é zerar a dívida — algum nível de imperfeição é normal e saudável — mas mantê-la sob controle.

Code smells: os sinais de que algo precisa mudar

Fowler popularizou o termo code smell — "mau cheiro de código": indícios na superfície que sugerem um problema mais profundo. Smell não é bug; é um sinal de alerta que merece investigação. Os mais comuns:

  • Código duplicado: o mesmo conhecimento em vários lugares. É o gatilho clássico para aplicar o O que é DRY? Não se repita e elimine duplicação de código.
  • Método longo: funções gigantes que fazem coisas demais e são difíceis de ler.
  • Classe grande (God Class): uma classe que concentra responsabilidades demais.
  • Lista de parâmetros longa: muitos argumentos sugerem que falta um objeto para agrupá-los.
  • Comentários explicativos demais: às vezes um comentário existe para desculpar um trecho confuso que deveria ser reescrito.
  • Feature envy: um método que usa mais dados de outra classe do que da própria — provavelmente está no lugar errado.
  • Switch/if encadeados: cadeias longas de condicionais que pedem polimorfismo.
  • Obsessão por primitivos: representar conceitos do domínio (dinheiro, CPF, datas) com tipos crus como string e float, em vez de objetos próprios.
  • Cirurgia com rifle (shotgun surgery): uma única mudança de requisito obriga a tocar em muitos arquivos espalhados — sinal de que uma responsabilidade está fragmentada.

Reconhecer smells é metade do trabalho. A outra metade é aplicar a técnica certa para removê-los. Um detalhe importante: um smell é um convite à investigação, não uma ordem automática de refatorar. Há contextos em que um método longo é, de fato, a forma mais clara de expressar uma sequência linear de passos. O julgamento continua sendo seu.

Técnicas de refatoração mais comuns

O catálogo de Fowler tem dezenas de refatorações nomeadas. Algumas são tão frequentes que vale memorizar:

Extrair função (Extract Function)

A refatoração mais usada de todas. Você pega um trecho coeso de código e o transforma em uma função com nome descritivo.

Antes:

function imprimirResumo(pedido) {
  let total = 0;
  for (const item of pedido.itens) {
    total += item.preco * item.quantidade;
  }
  // aplica imposto
  total = total * 1.1;

  console.log(`Total: ${total}`);
}

Depois:

function calcularSubtotal(itens) {
  return itens.reduce((soma, item) => soma + item.preco * item.quantidade, 0);
}

function aplicarImposto(valor) {
  return valor * 1.1;
}

function imprimirResumo(pedido) {
  const total = aplicarImposto(calcularSubtotal(pedido.itens));
  console.log(`Total: ${total}`);
}

O comportamento é idêntico, mas agora cada operação tem nome e pode ser testada e reusada isoladamente.

Renomear (Rename)

Trocar um nome ruim por um claro é refatoração de pleno direito. d vira diasRestantes; proc() vira processarPagamento(). Nomes bons são documentação que não envelhece.

Substituir condicional por polimorfismo

Quando um switch decide comportamento por tipo, frequentemente é melhor substituí-lo por subclasses ou estratégias. Veja a transformação:

// Antes: condicional decide comportamento por tipo
function calcularFrete(envio) {
  switch (envio.tipo) {
    case "expresso": return envio.peso * 5 + 20;
    case "padrao":   return envio.peso * 3;
    case "retirada": return 0;
  }
}
// Depois: cada tipo encapsula seu próprio cálculo
class FreteExpresso { calcular(peso) { return peso * 5 + 20; } }
class FretePadrao   { calcular(peso) { return peso * 3; } }
class FreteRetirada { calcular()     { return 0; } }

Essa refatoração é a ponte natural para vários Design Patterns essenciais que todo dev deveria conhecer, como Strategy e State, que costumam emergir desse movimento.

Introduzir parâmetro objeto

Vários parâmetros que sempre andam juntos (dia, mes, ano) podem virar um único objeto (Data), encurtando assinaturas e dando coesão. O mesmo vale para latitude/longitude virando Coordenada, ou valor/moeda virando Dinheiro.

Substituir número mágico por constante

Um valor literal solto no meio do código, como 1.1 ou 86400, esconde sua intenção. Extrair para uma constante nomeada (ALIQUOTA_IMPOSTO, SEGUNDOS_POR_DIA) torna o código autoexplicativo e centraliza a mudança caso o valor precise variar.

Decompor condicional

Uma expressão booleana complexa dentro de um if fica mais legível quando extraída para uma função com nome:

// Antes
if (data.getMonth() >= 11 || data.getMonth() <= 1) { aplicarTarifaInverno(); }

// Depois
if (ehAltaTemporada(data)) { aplicarTarifaInverno(); }

A regra de ouro: refatore com testes

Aqui está a parte mais importante. Como você garante que "não mudou o comportamento"? Com testes. A refatoração segura depende de uma rede de proteção que avisa imediatamente se algo quebrou.

O ciclo é sempre o mesmo:

  1. Garanta cobertura de testes para o trecho que vai mudar.
  2. Faça uma pequena alteração.
  3. Rode os testes. Verdes? Siga. Vermelhos? Desfaça e tente um passo menor.
  4. Repita em passos minúsculos.

Sem essa rede, "refatorar" vira "reescrever e torcer". Por isso a refatoração é inseparável de uma boa suíte — vale a leitura do guia de Testes automatizados: o guia definitivo para começar antes de encarar mudanças estruturais maiores. Robert C. Martin coloca a coisa de forma contundente: sem testes, você não tem coragem para limpar o código, e sem limpeza o código apodrece (Martin, 2008).

E quando não há testes?

Esse é o cenário mais comum em código legado, e existe uma resposta para ele. Michael Feathers chama de código legado justamente todo código sem testes, e propõe uma técnica de duas fases: primeiro você identifica os "pontos de costura" (seams) — lugares onde é possível inserir um teste sem grandes mudanças — escreve testes de caracterização que apenas documentam o comportamento atual (mesmo que esteja errado), e só então refatora com segurança (Feathers, 2004). Testes de caracterização não julgam se o comportamento é correto; eles registram o que o sistema faz hoje, para que você perceba qualquer alteração.

Refatoração e controle de versão

Um aliado silencioso da refatoração é o commit pequeno e frequente. Como cada passo preserva o comportamento e os testes passam, é seguro commitar a cada micro-melhoria. Isso dá dois benefícios: se um passo der errado, você desfaz apenas ele com um simples reset, sem perder horas de trabalho; e o histórico fica legível, separando claramente "refatoração" de "nova feature". Uma boa prática é nunca misturar, no mesmo commit, mudança de comportamento e mudança de estrutura — quem revisar agradece.

Quando refatorar (e quando não)

Refatoração não é um evento isolado no calendário; ela acontece em fluxo com o trabalho normal. Fowler propõe alguns gatilhos práticos:

  • Regra do escoteiro: deixe o código um pouco melhor do que o encontrou, a cada passagem.
  • Refatore antes de adicionar uma feature: se a estrutura atual dificulta a mudança, primeiro reorganize para que a feature entre fácil, depois adicione. Fowler chama isso de "primeiro torne a mudança fácil, depois faça a mudança fácil".
  • Refatore ao corrigir um bug: entender o defeito muitas vezes já revela a melhoria estrutural.
  • Refatore durante o code review: quando alguém aponta confusão, é o momento de esclarecer.

E quando não refatorar?

  • Quando é mais barato reescrever. Há código tão degradado que reescrever do zero, com requisitos claros, sai melhor.
  • Perto de um prazo crítico, se a mudança não for essencial — refatoração introduz risco e tempo.
  • Sem testes e sem tempo de criá-los para código sensível: o risco de quebrar silenciosamente é alto demais.
  • Código que está prestes a ser descartado. Refatorar um módulo que será removido na próxima sprint é desperdício puro.

Trade-offs: o lado difícil da decisão

Refatorar tem custos reais que vale enxergar com honestidade. O principal é o custo de oportunidade: cada hora gasta reorganizando código é uma hora que não foi gasta entregando valor visível ao usuário. Isso cria tensão com prazos e com gestores que não veem a melhoria interna. A resposta madura não é refatorar tudo nem nunca refatorar, mas sim integrar a melhoria ao trabalho de feature, em pequenas doses, de modo que ela quase não apareça como item separado de cronograma.

Outro trade-off é o risco de introduzir bugs. Ironicamente, a atividade que deveria reduzir defeitos pode criá-los se feita sem testes ou em passos grandes demais. Daí a insistência nos micro-passos: cada um é tão pequeno que o erro, quando ocorre, é fácil de localizar.

Por fim, há o risco da over-engineering: refatorar em direção a abstrações elaboradas que o sistema ainda não precisa. Aplicar polimorfismo onde um simples if bastaria torna o código mais difícil, não mais fácil. A regra prática é refatorar para resolver dor real e presente, não para antecipar flexibilidade hipotética.

Um exemplo completo passo a passo

Vale ver uma refatoração se desenrolando do começo ao fim, no ritmo certo de micro-passos. Suponha esta função, que mistura cálculo, formatação e regra de negócio:

function gerarFatura(cliente) {
  let resultado = `Fatura de ${cliente.nome}\n`;
  let total = 0;
  for (const item of cliente.consumos) {
    let valor = 0;
    if (item.tipo === "horario") {
      valor = item.minutos * 0.5;
    } else if (item.tipo === "dados") {
      valor = item.megabytes * 0.1;
    }
    total += valor;
    resultado += `  ${item.tipo}: R$ ${valor}\n`;
  }
  resultado += `Total: R$ ${total}\n`;
  return resultado;
}

Passo 1 — extrair o cálculo do item. O trecho do if/else calcula o valor de um item; vamos isolá-lo. Rode os testes depois.

function valorDoItem(item) {
  if (item.tipo === "horario") return item.minutos * 0.5;
  if (item.tipo === "dados")   return item.megabytes * 0.1;
  return 0;
}

Passo 2 — usar a nova função no loop. A função gerarFatura agora chama valorDoItem(item) em vez do bloco condicional. Testes verdes? Seguimos.

Passo 3 — separar cálculo de formatação. Note que o loop faz duas coisas: soma o total e monta o texto. Extraímos o cálculo do total:

function calcularTotal(consumos) {
  return consumos.reduce((soma, item) => soma + valorDoItem(item), 0);
}

Ao final, gerarFatura fica enxuta e cada peça é testável isoladamente. Repare que nunca houve um momento em que o código estivesse quebrado: cada passo preservou o comportamento, e os testes confirmaram isso. Esse é o ritmo da refatoração disciplinada — pequeno, verde, commit, repete.

Refatoração e complexidade

Uma forma objetiva de saber se a refatoração valeu a pena é medir a complexidade do código. Métricas como a Complexidade ciclomática: como medir e reduzir a complexidade do código contam os caminhos de decisão de uma função: quanto mais ifs, loops e ramificações, mais difícil de testar e entender. Extrair funções e substituir condicionais por polimorfismo tende a reduzir esse número — dando a você um indicador mensurável do progresso.

Essa conexão também ajuda a priorizar: as funções com maior complexidade são geralmente as melhores candidatas a refatoração, porque concentram risco e dificuldade. E manter cada unidade com uma só responsabilidade conversa diretamente com os O que é SOLID? Os 5 princípios do design orientado a objetos, especialmente o da Responsabilidade Única.

Erros comuns ao refatorar

Mesmo desenvolvedores experientes tropeçam em armadilhas previsíveis:

  • Misturar refatoração com nova feature. Quando algo quebra, você não sabe se foi a reorganização ou a funcionalidade nova. Separe sempre.
  • Passos grandes demais. Mudar dez coisas e só então rodar os testes destrói a capacidade de localizar o erro. O valor está no passo pequeno.
  • Refatorar sem objetivo. "Limpar por limpar" pode introduzir risco sem benefício claro. Toda refatoração deveria ter um motivo: facilitar uma feature, reduzir complexidade medida, eliminar duplicação real.
  • Confiar na IA cegamente. Assistentes de código sugerem refatorações úteis, mas podem alterar comportamento sutilmente. A rede de testes vale ainda mais quando a mudança não foi escrita por você — confira sempre que os testes continuam verdes.
  • Ignorar o teste que ficou vermelho. Um teste que quebra durante a refatoração é informação, não obstáculo. Comente-o e siga em frente é o caminho mais rápido para um bug em produção.

Perguntas frequentes

Refatoração precisa estar no backlog como tarefa separada? Nem sempre. A maior parte da refatoração saudável acontece embutida no trabalho de feature, em micro-doses (a regra do escoteiro). Refatorações grandes e estruturais, que levam dias, sim, merecem ser planejadas e comunicadas como item próprio, com justificativa clara de retorno.

Dá para refatorar sem testes automatizados? Tecnicamente sim, mas com risco muito maior. Em código legado sem testes, o caminho recomendado é primeiro escrever testes de caracterização para o trecho afetado e só então refatorar. Refatorar "no escuro" só se justifica em mudanças triviais e localizadas, como renomear uma variável local.

Qual a diferença entre refatoração e otimização de performance? Otimização busca mudar uma propriedade observável — velocidade ou uso de memória. Refatoração, por definição, preserva o comportamento observável, incluindo performance aproximada. As duas atividades podem se ajudar (código limpo é mais fácil de otimizar), mas têm objetivos distintos.

Com que frequência devo refatorar? Continuamente, em pequenas doses, sempre que tocar num trecho de código. A refatoração contínua é muito mais barata e segura do que grandes mutirões periódicos de limpeza.

Conclusão

Refatorar é melhorar a estrutura interna do código preservando seu comportamento — nem mais, nem menos. É uma disciplina, não uma faxina ocasional: acontece em passos pequenos, protegida por testes, integrada ao fluxo de desenvolvimento. Aprender a reconhecer code smells, dominar algumas refatorações fundamentais como extrair função e renomear, e nunca mexer no que não está coberto por testes são os pilares dessa prática.

O retorno é silencioso, mas decisivo: um código que permanece fácil de entender e barato de mudar ao longo dos anos. Times que refatoram continuamente pagam a dívida técnica em parcelas pequenas; times que adiam acabam pagando de uma vez, com juros, em forma de reescritas dolorosas. Comece pela regra do escoteiro, garanta seus testes e melhore o código um pouco a cada vez que passar por ele.

Referências

  • Martin Fowler (2018). Refactoring: Improving the Design of Existing Code (2nd ed.). Addison-Wesley.
  • Robert C. Martin (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
  • Michael Feathers (2004). Working Effectively with Legacy Code. Prentice Hall.
  • Kent Beck (2002). Test-Driven Development: By Example. Addison-Wesley.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly