Pular para o conteúdo

Fundamentos de Refactoring

Martin Fowler (“Refactoring: Improving the Design of Existing Code”, 2ª ed., 2018) dá duas definições complementares:

  • Substantivo: uma transformação nomeada que preserva o comportamento (ex.: Extract Function).
  • Verbo: reestruturar software aplicando uma série dessas transformações sem mudar o comportamento observável.

A palavra-chave é comportamento observável. Você reorganiza a estrutura interna — nomes, decomposição, hierarquia — enquanto as entradas produzem exatamente as mesmas saídas e efeitos externos. Se o comportamento muda, não é refactoring: é adição de feature ou correção de bug. Fowler insiste em separar esses dois chapéus — o “chapéu de refactoring” e o “chapéu de adicionar feature” — e nunca usá-los ao mesmo tempo.

Isso amarra este módulo a Test-Driven Development e a Tipos de Teste - Unit, Integration e E2E. Os testes de unidade rápidos são os que dão o feedback em segundos necessário para o loop apertado de refactoring; testes de integração e E2E protegem contornos maiores. As propriedades desejáveis desses testes estão em Princípio FIRST — sobretudo Fast, para que rodar a suíte a cada passo não seja um fardo.

flowchart TD
    A[Testes verdes] --> B[Aplicar 1 refactoring pequeno]
    B --> C[Rodar testes]
    C -->|verde| D[Commit]
    C -->|vermelho| E[Reverter passo]
    D --> A
    E --> A

O ciclo é apertado de propósito: se a suíte quebra, você desfaz um único passo pequeno, não uma hora de trabalho.

Refactoring é uma sequência de passos pequenos e reversíveis, cada um deixando o sistema funcionando. Reescrita joga fora o código existente e o reconstrói — arriscada, cara e sem garantia de paridade de comportamento. A reescrita se justifica quando o código é tão degradado que o custo de entendê-lo supera o de recriá-lo, ou quando a plataforma mudou. Mas, na dúvida, refatore: você mantém o sistema entregável a cada instante.

Refactoring contínuo x “sprint de refactoring”

Seção intitulada “Refactoring contínuo x “sprint de refactoring””

Fowler defende o refactoring oportunista e contínuo: você limpa o código à medida que passa por ele para implementar features (a Boy Scout Rule de Clean Code). O modelo oposto — reservar um “sprint de refactoring” trimestral — costuma ser sintoma de dívida acumulada e tende a virar reescrita disfarçada, sem valor de negócio entregue no intervalo. Refactoring saudável é invisível no roadmap porque está diluído em cada tarefa.

No ciclo Red-Green-Refactor de Test-Driven Development (Kent Beck), o refactoring tem um lugar canônico e agendado:

flowchart LR
    R["RED<br/>teste falha"] --> G["GREEN<br/>faz passar<br/>(código feio ok)"]
    G --> RF["REFACTOR<br/>limpa com testes verdes"]
    RF --> R

O passo Green permite código sujo, contanto que passe. O passo Refactor é onde você remove duplicação e melhora nomes — com a segurança de que a suíte acabou de ficar verde. TDD, portanto, produz a rede de testes e agenda a limpeza no mesmo fluxo. A relação mais ampla entre projeto testável e boa arquitetura está em Testabilidade e Arquitetura.

public class RelatorioFinanceiro {
public double calcular(List<Lancamento> lancamentos) {
double total = 0;
for (Lancamento l : lancamentos) {
if (l.getTipo() == Tipo.CREDITO) {
total += l.getValor() * 1.0;
}
}
for (Lancamento l : lancamentos) {
if (l.getTipo() == Tipo.DEBITO) {
total -= l.getValor() * 1.0;
}
}
return total;
}
}

Cada passo abaixo foi seguido de execução da suíte de testes, mantendo-a verde:

public class RelatorioFinanceiro {
public double calcular(List<Lancamento> lancamentos) {
return somarCreditos(lancamentos) - somarDebitos(lancamentos);
}
private double somarCreditos(List<Lancamento> lancamentos) {
return somarPorTipo(lancamentos, Tipo.CREDITO);
}
private double somarDebitos(List<Lancamento> lancamentos) {
return somarPorTipo(lancamentos, Tipo.DEBITO);
}
private double somarPorTipo(List<Lancamento> lancamentos, Tipo tipo) {
return lancamentos.stream()
.filter(l -> l.getTipo() == tipo)
.mapToDouble(Lancamento::getValor)
.sum();
}
}
  • Código que será descartado — não invista em limpar um módulo que já tem data de remoção.
  • Ausência de testes cobrindo o trecho — primeiro escreva testes de caracterização (que fixam o comportamento atual), depois refatore. Refatorar sem rede é arriscar regressão silenciosa.
  • Prazo crítico com o chapéu de feature — não misture refactoring com a entrega urgente; separe os commits.
  • Reescrita já decidida — se o módulo será reconstruído do zero por outra razão, refatorá-lo é esforço perdido.
  • Refatorar sem rodar os testes a cada passo — você perde a capacidade de localizar qual passo quebrou.
  • Passos grandes demais — misturar dez transformações num commit torna a reversão inviável.
  • Misturar chapéus — mudar comportamento e estrutura no mesmo commit esconde bugs.
  • Chamar reescrita de “refactoring” — vender um rewrite arriscado sob o rótulo seguro do refactoring.
  • “Sprint de refactoring” — postergar limpeza para uma fase isolada em vez de fazê-la continuamente.

1. Qual a definição precisa de refactoring segundo Fowler?

Resposta

Alterar a estrutura interna do código sem mudar seu comportamento observável — as mesmas entradas produzem as mesmas saídas e efeitos externos. Como substantivo é uma transformação nomeada que preserva comportamento; como verbo é aplicar uma série dessas transformações.

2. Por que a rede de testes é pré-requisito e não opcional?

Resposta

Porque a única forma de comprovar que uma mudança de estrutura preservou o comportamento é reexecutar testes auto-verificáveis que capturam esse comportamento. Sem eles, “refactoring” vira editar e torcer, com risco de regressão silenciosa. Testes de unidade rápidos são essenciais pelo loop apertado.

3. Qual a diferença entre refactoring e reescrita, e quando cada um se justifica?

Resposta

Refactoring é uma sequência de passos pequenos e reversíveis que mantém o sistema funcionando a cada instante. Reescrita descarta e reconstrói, com risco alto e sem garantia de paridade. Reescrita só se justifica quando o código está degradado demais para ser entendido ou a plataforma mudou; na dúvida, refatore.

4. Onde o refactoring se encaixa no ciclo TDD e por que é seguro ali?

Resposta

No passo Refactor do ciclo Red-Green-Refactor. O passo Green tolera código feio contanto que passe; o passo Refactor limpa esse código com a suíte recém-verde servindo de rede. TDD produz os testes e agenda a limpeza no mesmo fluxo.