Técnicas de Refactoring
Como usar o catálogo
Seção intitulada “Como usar o catálogo”Cada refactoring de Fowler é descrito por: Nome (vocabulário compartilhado), Motivação (quando aplicar), Mecânica (passos ordenados) e Exemplo. A regra de ouro: aplique um passo, rode os testes, repita. Se ficar vermelho, reverta apenas o último passo. Abaixo, as técnicas mais frequentes com exemplo Java; para 2-3 delas, a mecânica detalhada.
flowchart TD
A[Code Smell detectado] --> B{Qual smell?}
B -->|Long Method| C[Extract Function]
B -->|Switch repetido| D[Replace Conditional with Polymorphism]
B -->|Primitive Obsession| E[Replace Primitive with Object]
B -->|Long Parameter List| F[Introduce Parameter Object]
B -->|Feature Envy| G[Move Method]
C & D & E & F & G --> H[Rodar testes -> verde -> commit]
Extract Function
Seção intitulada “Extract Function”Motivação: Long Method, trecho que pede um comentário, código duplicado. Smell: Long Method, Duplicated Code.
Mecânica passo a passo
Seção intitulada “Mecânica passo a passo”- Crie uma função nova, nomeada pela intenção (o quê), não pela implementação (o como).
- Copie o trecho extraído para a nova função.
- Verifique variáveis locais usadas no trecho: se lidas, viram parâmetros; se atribuídas e usadas depois, viram valor de retorno.
- Substitua o trecho original por uma chamada à nova função.
- Rode os testes.
public void imprimirExtrato(Conta conta) { System.out.println("=== Extrato ==="); System.out.println("Titular: " + conta.getTitular()); double saldo = 0; for (Movimento m : conta.getMovimentos()) { saldo += m.getValor(); } System.out.println("Saldo: " + saldo);}public void imprimirExtrato(Conta conta) { imprimirCabecalho(conta); double saldo = calcularSaldo(conta); System.out.println("Saldo: " + saldo);}
private void imprimirCabecalho(Conta conta) { System.out.println("=== Extrato ==="); System.out.println("Titular: " + conta.getTitular());}
private double calcularSaldo(Conta conta) { return conta.getMovimentos().stream() .mapToDouble(Movimento::getValor) .sum();}Inline Function
Seção intitulada “Inline Function”Motivação: o inverso de Extract — quando o corpo é tão claro quanto o nome, ou quando você tem indireção desnecessária e quer reorganizar antes de re-extrair. Substitua a chamada pelo corpo e remova a função.
int avaliacao(Motorista m) { return maisDeCincoEntregas(m) ? 2 : 1; }boolean maisDeCincoEntregas(Motorista m) { return m.getEntregas() > 5; }int avaliacao(Motorista m) { return m.getEntregas() > 5 ? 2 : 1; }Extract Variable
Seção intitulada “Extract Variable”Motivação: expressão complexa e difícil de ler. Dê nome a subexpressões.
return pedido.getQuantidade() * pedido.getPrecoUnitario() - Math.max(0, pedido.getQuantidade() - 500) * pedido.getPrecoUnitario() * 0.05 + Math.min(pedido.getQuantidade() * pedido.getPrecoUnitario() * 0.1, 100);double precoBase = pedido.getQuantidade() * pedido.getPrecoUnitario();double desconto = Math.max(0, pedido.getQuantidade() - 500) * pedido.getPrecoUnitario() * 0.05;double frete = Math.min(precoBase * 0.1, 100);return precoBase - desconto + frete;Rename (Variable / Method / Field)
Seção intitulada “Rename (Variable / Method / Field)”Motivação: o nome não revela a intenção. É o refactoring mais frequente e mais subestimado. Ferramentas de IDE tornam-no seguro em segundos.
public List<Cliente> get(boolean f) { }public List<Cliente> buscarClientesAtivos(boolean incluirInadimplentes) { }Extract Class
Seção intitulada “Extract Class”Motivação: Large Class / Divergent Change — a classe acumula responsabilidades que mudam por razões diferentes. Separe um conjunto coeso de campos e métodos numa nova classe. Cura direta de violação de SRP.
public class Pessoa { private String nome; private String telefoneDDD; private String telefoneNumero;
public String telefoneFormatado() { return "(" + telefoneDDD + ") " + telefoneNumero; }}public class Pessoa { private String nome; private Telefone telefone;
public String telefoneFormatado() { return telefone.formatado(); }}
public class Telefone { private final String ddd; private final String numero;
public Telefone(String ddd, String numero) { this.ddd = ddd; this.numero = numero; }
public String formatado() { return "(" + ddd + ") " + numero; }}Move Method
Seção intitulada “Move Method”Motivação: Feature Envy — o método usa mais dados de outra classe que da própria. Mova-o para onde os dados estão.
public class Fatura { public double percentualDesconto(Cliente cliente) { return cliente.getPontos() > 1000 ? 0.15 : 0.0; }}public class Cliente { private int pontos; public double percentualDesconto() { return pontos > 1000 ? 0.15 : 0.0; }}Replace Conditional with Polymorphism
Seção intitulada “Replace Conditional with Polymorphism”Motivação: Switch Statements / condicional por tipo repetida pelo código. Substitua o switch por uma hierarquia onde cada subtipo implementa seu ramo. Habilita OCP: novos casos entram como novas classes, sem editar código existente.
Mecânica passo a passo
Seção intitulada “Mecânica passo a passo”- Crie a hierarquia de classes (interface ou classe base + subclasses por caso).
- Se ainda não existe, use Replace Constructor with Factory ou um método fábrica para instanciar o subtipo certo.
- Mova o corpo de cada ramo do
switchpara o método sobrescrito da subclasse correspondente. - Substitua o
switchpela chamada polimórfica. - Rode os testes após mover cada ramo.
public class CalculadoraArea { public double area(Forma f) { switch (f.getTipo()) { case CIRCULO: return Math.PI * f.getRaio() * f.getRaio(); case QUADRADO: return f.getLado() * f.getLado(); case RETANGULO: return f.getBase() * f.getAltura(); default: throw new IllegalArgumentException("forma desconhecida"); } }}public interface Forma { double area();}
public class Circulo implements Forma { private final double raio; public Circulo(double raio) { this.raio = raio; } public double area() { return Math.PI * raio * raio; }}
public class Quadrado implements Forma { private final double lado; public Quadrado(double lado) { this.lado = lado; } public double area() { return lado * lado; }}
public class Retangulo implements Forma { private final double base; private final double altura; public Retangulo(double base, double altura) { this.base = base; this.altura = altura; } public double area() { return base * altura; }}Adicionar Triangulo agora é criar uma classe nova — nenhuma linha existente muda.
Replace Primitive with Object (Value Object)
Seção intitulada “Replace Primitive with Object (Value Object)”Motivação: Primitive Obsession — um primitivo carrega regras de domínio espalhadas. Encapsule-o num tipo próprio. Leva a Value Objects.
public class Cliente { private String email;
public void setEmail(String email) { if (!email.contains("@")) throw new IllegalArgumentException(); this.email = email; }}public class Email { private final String valor;
public Email(String valor) { if (valor == null || !valor.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) { throw new IllegalArgumentException("e-mail inválido: " + valor); } this.valor = valor; }
public String valor() { return valor; }
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Email)) return false; return valor.equals(((Email) o).valor); }
@Override public int hashCode() { return valor.hashCode(); }}
public class Cliente { private Email email; public void setEmail(Email email) { this.email = email; }}Introduce Parameter Object
Seção intitulada “Introduce Parameter Object”Motivação: Long Parameter List — parâmetros que sempre viajam juntos. Agrupe-os num objeto.
public List<Lancamento> buscar(LocalDate inicio, LocalDate fim, String moeda) { }public class Periodo { private final LocalDate inicio; private final LocalDate fim; public Periodo(LocalDate inicio, LocalDate fim) { this.inicio = inicio; this.fim = fim; } public boolean contem(LocalDate data) { return !data.isBefore(inicio) && !data.isAfter(fim); }}
public List<Lancamento> buscar(Periodo periodo, String moeda) { }O objeto extraído tende a atrair comportamento (contem), reduzindo Feature Envy ao redor dos campos.
Replace Temp with Query
Seção intitulada “Replace Temp with Query”Motivação: variáveis temporárias que guardam o resultado de uma expressão dificultam extrações posteriores. Transforme a temp num método de consulta.
public double preco() { double precoBase = quantidade * precoUnitario; if (precoBase > 1000) return precoBase * 0.95; return precoBase * 0.98;}public double preco() { if (precoBase() > 1000) return precoBase() * 0.95; return precoBase() * 0.98;}
private double precoBase() { return quantidade * precoUnitario;}Com precoBase() como consulta, extrair preco() para outra classe deixa de arrastar estado local.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Passos grandes — mover cinco métodos de uma vez sem rodar testes entre eles; a mecânica de Fowler é deliberadamente granular.
- Nomear pela implementação —
Extract Functiondeve nomear a intenção (calcularSaldo), não o mecanismo (loopSomando). - Polimorfismo prematuro — aplicar Replace Conditional with Polymorphism num
switchúnico e estável adiciona indireção sem retorno. - Value Object anêmico — criar
Emailsó para embrulhar a String, sem mover validação nemequals/hashCode, não cura Primitive Obsession. - Refatorar sem testes — qualquer técnica aqui pressupõe a rede de Fundamentos de Refactoring.
Relações
Seção intitulada “Relações”- Cura da tríade: Clean Code e Refactoring
- Meta a atingir: Clean Code
- Diagnóstico que dispara cada técnica: Code Smells
- Rede de segurança e mecânica de passos: Fundamentos de Refactoring
- Habilita OCP - Open-Closed Principle via polimorfismo; cura violações de SRP - Single Responsibility Principle via Extract Class
- Replace Primitive with Object leva a Value Objects · Entidades
- Princípios de fundo: SOLID · Code Smells e SOLID
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Descreva a mecânica de Extract Function e como decidir parâmetros e retorno.
Resposta
Crie uma função nomeada pela intenção, copie o trecho para ela, trate as variáveis locais (as lidas viram parâmetros; a atribuída e usada depois vira valor de retorno), substitua o trecho por uma chamada e rode os testes. Passos pequenos, testes a cada passo.
2. Por que Replace Conditional with Polymorphism habilita o OCP?
Resposta
Porque cada ramo do condicional vira o método sobrescrito de uma subclasse. Adicionar um novo caso passa a ser criar uma classe nova, sem editar o código existente — exatamente o que OCP pede: aberto para extensão, fechado para modificação. Elimina também os switches duplicados que causariam Shotgun Surgery.
3. O que caracteriza um Value Object bem-feito ao aplicar Replace Primitive with Object?
Resposta
Ele é imutável, valida na construção (falha cedo para estados inválidos), concentra o comportamento antes espalhado e implementa igualdade por valor (equals/hashCode). Só embrulhar o primitivo sem mover validação nem igualdade produz um wrapper anêmico que não cura Primitive Obsession. Ver Value Objects.
4. Quando aplicar Inline Function em vez de Extract Function?
Resposta
Quando a indireção não agrega — o corpo é tão claro quanto o nome — ou quando você quer reunir código espalhado numa única função para reorganizá-lo antes de re-extrair de forma melhor. Inline é o inverso de Extract e ambos coexistem no dia a dia.