🎯 Exercícios Práticos - Aula 03
Refatoração Segura em Java
Desenvolvimento de Sistemas II
Prof. Ricardo Pires
3º Técnico DS
📚 Objetivos dos Exercícios
Ao final desta série de exercícios, você será capaz de:
- Identificar code smells em código Java real
- Aplicar técnicas Extract Method e Decompose Conditional
- Validar refatorações através de testes automatizados
- Medir o impacto da refatoração na manutenibilidade
- Desenvolver disciplina de refatoração contínua
🎮 Exercício 1: Identificação de Code Smells
⏱️ Tempo estimado: 15 minutos
📊 Dificuldade: ⭐⭐ (Básica)
🎯 Objetivo
Identificar e classificar code smells em trechos de código Java.
📝 Situação
Você é desenvolvedor em uma empresa de e-commerce e herdou este código:
public class ProcessadorPedido {
public String processar(Pedido pedido) {
// Validações
if (pedido.getCliente() == null) {
return "Erro: Cliente não informado";
}
if (pedido.getCliente().getNome() == null || pedido.getCliente().getNome().isEmpty()) {
return "Erro: Nome do cliente não informado";
}
if (pedido.getCliente().getCpf() == null || pedido.getCliente().getCpf().length() != 11) {
return "Erro: CPF inválido";
}
if (pedido.getItens() == null || pedido.getItens().isEmpty()) {
return "Erro: Pedido sem itens";
}
// Cálculo do total
double total = 0;
for (ItemPedido item : pedido.getItens()) {
if (item.getQuantidade() <= 0) {
return "Erro: Quantidade inválida para item " + item.getProduto().getNome();
}
if (item.getProduto().getPreco() <= 0) {
return "Erro: Preço inválido para item " + item.getProduto().getNome();
}
total += item.getQuantidade() * item.getProduto().getPreco();
}
// Validação do valor mínimo
if (total < 20.0) {
return "Erro: Valor mínimo do pedido é R$ 20,00";
}
// Cálculo de desconto
double desconto = 0;
if (pedido.getCliente().isVip()) {
if (pedido.getCliente().getAnosComoCliente() > 5) {
if (total > 500) {
desconto = total * 0.15; // 15%
} else {
desconto = total * 0.10; // 10%
}
} else {
desconto = total * 0.05; // 5%
}
}
double totalFinal = total - desconto;
return "Pedido processado com sucesso. Total: R$ " +
String.format("%.2f", totalFinal) +
(desconto > 0 ? " (Desconto: R$ " + String.format("%.2f", desconto) + ")" : "");
}
}✅ Tarefas
- Identifique pelo menos 5 code smells diferentes no código acima
- Classifique cada smell encontrado (ex: método longo, condicionais aninhadas, etc.)
- Estime o número de responsabilidades que o método
processartem - Propor uma estrutura de métodos após a refatoração
📋 Template de Resposta
| # | Code Smell | Classificação | Linhas Afetadas | Impacto na Manutenibilidade |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 |
🔧 Exercício 2: Extract Method - Prática Guiada
⏱️ Tempo estimado: 30 minutos
📊 Dificuldade: ⭐⭐⭐ (Intermediária)
🎯 Objetivo
Aplicar a técnica Extract Method de forma sistemática e segura.
📝 Situação
Use o mesmo código do Exercício 1. Agora você precisa refatorá-lo aplicando Extract Method.
🛡️ Pré-requisitos
Antes de refatorar, você deve ter testes que capturam o comportamento atual:
@Test
void deveProcessarPedidoValidoComSucesso() {
// Arrange
Pedido pedido = criarPedidoValido();
ProcessadorPedido processador = new ProcessadorPedido();
// Act
String resultado = processador.processar(pedido);
// Assert
assertTrue(resultado.contains("Pedido processado com sucesso"));
assertTrue(resultado.contains("Total: R$ "));
}
@Test
void deveRejeitarPedidoSemCliente() {
// Arrange
Pedido pedido = new Pedido();
pedido.setCliente(null);
ProcessadorPedido processador = new ProcessadorPedido();
// Act
String resultado = processador.processar(pedido);
// Assert
assertEquals("Erro: Cliente não informado", resultado);
}
// ... mais testes para cobrir todos os caminhos✅ Tarefas
Passo 1: Análise (5 min)
- Identifique as diferentes responsabilidades no método
- Liste os métodos que você pretende extrair
- Defina a assinatura de cada método
Passo 2: Refatoração Incremental (20 min)
- Extraia um método por vez
- Execute os testes após cada extração
- Mantenha o método principal legível
Passo 3: Validação (5 min)
- Execute toda a suíte de testes
- Compare performance se necessário
- Faça code review próprio
🎯 Meta de Sucesso
- ✅ Método principal com máximo 15 linhas
- ✅ Pelo menos 4 métodos extraídos
- ✅ Todos os testes continuam passando
- ✅ Nomes de métodos expressivos
📋 Template de Entrega
public class ProcessadorPedido {
public String processar(Pedido pedido) {
// Método principal refatorado - máximo 15 linhas
}
// Métodos extraídos com nomes expressivos
private String validarPedido(Pedido pedido) {
// ...
}
private double calcularTotal(Pedido pedido) {
// ...
}
// ... outros métodos
}🌟 Exercício 3: Decompose Conditional - Avançado
⏱️ Tempo estimado: 25 minutos
📊 Dificuldade: ⭐⭐⭐⭐ (Avançada)
🎯 Objetivo
Aplicar Decompose Conditional e Guard Clauses para simplificar lógica complexa.
📝 Situação
Você precisa refatorar este método de cálculo de frete de uma transportadora:
public class CalculadoraFrete {
public double calcularFrete(Pedido pedido, String cep) {
if (pedido != null) {
if (pedido.getCliente() != null) {
if (cep != null && cep.length() == 8) {
double peso = 0;
for (ItemPedido item : pedido.getItens()) {
peso += item.getProduto().getPeso() * item.getQuantidade();
}
if (peso > 0) {
String regiao = obterRegiao(cep);
if (regiao.equals("SUDESTE")) {
if (peso <= 1.0) {
if (pedido.getCliente().isVip()) {
return 5.0; // VIP sudeste até 1kg
} else {
return 8.0; // Normal sudeste até 1kg
}
} else if (peso <= 5.0) {
if (pedido.getCliente().isVip()) {
return 10.0; // VIP sudeste até 5kg
} else {
return 15.0; // Normal sudeste até 5kg
}
} else {
if (pedido.getCliente().isVip()) {
return peso * 3.0; // VIP sudeste por kg
} else {
return peso * 4.0; // Normal sudeste por kg
}
}
} else if (regiao.equals("NORDESTE")) {
if (peso <= 1.0) {
return pedido.getCliente().isVip() ? 8.0 : 12.0;
} else if (peso <= 5.0) {
return pedido.getCliente().isVip() ? 15.0 : 20.0;
} else {
return peso * (pedido.getCliente().isVip() ? 4.0 : 5.0);
}
} else { // OUTRAS REGIÕES
if (peso <= 1.0) {
return pedido.getCliente().isVip() ? 10.0 : 15.0;
} else if (peso <= 5.0) {
return pedido.getCliente().isVip() ? 18.0 : 25.0;
} else {
return peso * (pedido.getCliente().isVip() ? 5.0 : 6.0);
}
}
} else {
return 0; // Sem peso
}
} else {
return -1; // CEP inválido
}
} else {
return -1; // Cliente inválido
}
} else {
return -1; // Pedido inválido
}
}
}✅ Tarefas
Parte A: Guard Clauses (10 min)
- Substitua as condicionais aninhadas por guard clauses
- Trate casos inválidos primeiro
- Mantenha o fluxo principal linear
Parte B: Extract Conditional Methods (10 min)
- Extraia métodos para validações
- Creates tabela de preços como constantes
- Simplifique cálculo por região
Parte C: Strategy Pattern (5 min)
- Considere implementar Strategy para cada região
- Avalie se vale a pena neste caso
- Compare complexidade antes/depois
🎯 Meta de Sucesso
- ✅ Aninhamento máximo: 2 níveis
- ✅ Método principal com máximo 20 linhas
- ✅ Lógica por região claramente separada
- ✅ Constantes para valores mágicos
- ✅ Validação através de testes
📊 Métricas para Medir
| Métrica | Antes | Depois | Meta |
|---|---|---|---|
| Complexidade ciclomática | ? | ? | < 5 |
| Linhas do método principal | 48 | ? | < 20 |
| Níveis máximos de aninhamento | 7 | ? | < 3 |
| Número de condições por método | 12 | ? | < 4 |
🏆 Exercício 4: Projeto Completo - Mini Sistema
⏱️ Tempo estimado: 45 minutos
📊 Dificuldade: ⭐⭐⭐⭐⭐ (Expert)
🎯 Objetivo
Aplicar todas as técnicas aprendidas em um projeto real completo.
📝 Situação
Você recebeu este sistema legado de uma biblioteca que precisa ser refatorado antes de adicionar novas funcionalidades:
public class SistemaEmprestimo {
public String emprestar(int livroId, int usuarioId, Date dataAtual) {
// Método com 80+ linhas que faz:
// 1. Validação do usuário
// 2. Validação do livro
// 3. Verificação de limites
// 4. Cálculo de multas pendentes
// 5. Registro do empréstimo
// 6. Atualização do estoque
// 7. Envio de notificações
// CÓDIGO COMPLEXO FORNECIDO SEPARADAMENTE
// (simulando um sistema real legado)
}
public String devolver(int emprestimoId, Date dataAtual) {
// Método com 60+ linhas que faz:
// 1. Validação do empréstimo
// 2. Cálculo de multas por atraso
// 3. Atualização do estoque
// 4. Registro da devolução
// 5. Envio de notificações
// CÓDIGO COMPLEXO FORNECIDO SEPARADAMENTE
}
}✅ Tarefas do Projeto
Fase 1: Análise e Planejamento (10 min)
- Leia o código completo fornecido
- Mapeie as responsabilidades de cada método
- Identifique todos os code smells
- Planeje a estrutura final da refatoração
Fase 2: Preparação (10 min)
- Escreva testes de comportamento abrangentes
- Configure ambiente de testes
- Execute testes para estabelecer baseline
Fase 3: Refatoração Sistemática (20 min)
- Aplique Extract Method de forma incremental
- Use Decompose Conditional onde necessário
- Execute testes após cada mudança
- Mantenha commits pequenos e frequentes
Fase 4: Validação e Melhorias (5 min)
- Execute todos os testes
- Meça métricas de código
- Documente mudanças realizadas
📊 Métricas de Sucesso
| Métrica | Meta Mínima | Meta Ideal |
|---|---|---|
| Métodos com < 20 linhas | 80% | 95% |
| Complexidade ciclomática < 5 | 90% | 100% |
| Cobertura de testes | 85% | 95% |
| Zero aninhamento > 3 níveis | ✅ | ✅ |
🎯 Estrutura Final Esperada
public class SistemaEmprestimo {
// Métodos principais - limpos e legíveis
public String emprestar(int livroId, int usuarioId, Date dataAtual) {
// 10-15 linhas máximo
}
public String devolver(int emprestimoId, Date dataAtual) {
// 10-15 linhas máximo
}
// Métodos de validação
private ValidationResult validarUsuario(int usuarioId) { }
private ValidationResult validarLivro(int livroId) { }
// Métodos de negócio
private double calcularMultasPendentes(Usuario usuario) { }
private void registrarEmprestimo(Emprestimo emprestimo) { }
// Métodos auxiliares
private void enviarNotificacao(NotificationType type, String destinatario) { }
private void atualizarEstoque(int livroId, int delta) { }
}🧪 Exercício 5: Testes de Refatoração
⏱️ Tempo estimado: 20 minutos
📊 Dificuldade: ⭐⭐⭐ (Intermediária)
🎯 Objetivo
Criar teste suite completa para validar refatorações de forma segura.
📝 Situação
Usando qualquer um dos exercícios anteriores, você precisa criar testes que garantam que a refatoração preservou o comportamento.
✅ Tarefas
Parte A: Testes de Comportamento (10 min)
@TestMethodOrder(OrderAnnotation.class)
class RefatoracaoTest {
private ClasseAntes exemploAntes;
private ClasseDepois exemploDepois;
@Test
@Order(1)
@DisplayName("Setup: Classes são instanciadas corretamente")
void setup() {
exemploAntes = new ClasseAntes();
exemploDepois = new ClasseDepois();
assertNotNull(exemploAntes);
assertNotNull(exemploDepois);
}
@ParameterizedTest
@Order(2)
@DisplayName("Comportamento idêntico para casos válidos")
@ValueSource(/* seus valores de teste */)
void devePreservarComportamento(/* parâmetros */) {
// Arrange
// Act - execute ambas as versões
// Assert - resultados idênticos
}
@Test
@Order(3)
@DisplayName("Exceções tratadas da mesma forma")
void deveTratarExcecoesIgualmente() {
// Teste que ambas versões falham da mesma forma
}
@Test
@Order(4)
@DisplayName("Performance não degradou significativamente")
void deveManterPerformanceRazoavel() {
// Teste simples de performance se aplicável
}
}Parte B: Testes de Mutação (5 min)
@Test
@DisplayName("Mudanças mínimas quebram testes (validando qualidade dos testes)")
void testesMutacao() {
// Simule pequenas mudanças no código
// Verifique se seus testes capturam essas mudanças
}Parte C: Relatório de Cobertura (5 min)
- Configure Jacoco no projeto
- Execute testes com cobertura
- Analise relatório gerado
- Meta: 90%+ cobertura das linhas refatoradas
📋 Template de Análise
| Caso de Teste | Antes | Depois | Status | Observações |
|---|---|---|---|---|
| Entrada válida | ✅/❌ | |||
| Entrada inválida | ✅/❌ | |||
| Casos extremos | ✅/❌ | |||
| Performance | ✅/❌ |
📊 Critérios de Avaliação
📈 Estrutura da Avaliação (Total: 100 pontos)
| Exercício | Peso | Critérios Específicos | Pontos |
|---|---|---|---|
| Ex 1: Identificação | 15% | Code smells encontrados, classificação correta | 15 |
| Ex 2: Extract Method | 25% | Métodos extraídos, testes passando, nomes expressivos | 25 |
| Ex 3: Decompose Conditional | 25% | Redução de aninhamento, guard clauses, clareza | 25 |
| Ex 4: Projeto Completo | 25% | Refatoração completa, métricas atingidas | 25 |
| Ex 5: Testes | 10% | Cobertura, qualidade dos testes | 10 |
🎯 Critérios de Qualidade
Excelente (90-100%)
- ✅ Todos os exercícios completos
- ✅ Código refatorado segue boas práticas
- ✅ Testes abrangentes e bem escritos
- ✅ Métricas de qualidade atingidas
- ✅ Documentação clara das mudanças
Bom (70-89%)
- ✅ Maioria dos exercícios completos
- ✅ Refatorações aplicadas corretamente
- ✅ Testes básicos funcionando
- ⚠️ Algumas métricas não atingidas
- ⚠️ Documentação incompleta
Satisfatório (50-69%)
- ✅ Exercícios básicos completos
- ⚠️ Refatorações parciais
- ⚠️ Testes mínimos
- ❌ Métricas não atingidas
- ❌ Pouca documentação
Insuficiente (< 50%)
- ❌ Exercícios incompletos
- ❌ Refatorações incorretas
- ❌ Sem testes adequados
- ❌ Code smells persistem
🚀 Entrega e Prazos
📅 Cronograma
- Início: Durante a aula (23/03/2026)
- Entrega: Até domingo 28/03/2026 - 23:59
- Feedback: Quarta-feira 31/03/2026
📦 Formato de Entrega
Aula03_Exercicios_[SeuNome]/
├── README.md # Relatório geral
├── exercicio1/ # Análise de code smells
│ └── analise.md
├── exercicio2/ # Extract Method
│ ├── src/
│ ├── test/
│ └── RELATORIO.md
├── exercicio3/ # Decompose Conditional
│ ├── src/
│ ├── test/
│ └── RELATORIO.md
├── exercicio4/ # Projeto completo
│ ├── src/
│ ├── test/
│ ├── docs/
│ └── RELATORIO.md
└── exercicio5/ # Testes de refatoração
├── src/
├── test/
├── coverage-report/
└── RELATORIO.md
📋 Checklist Final
- Todos exercícios implementados
- Testes executando com sucesso
- Relatórios preenchidos
- Código comentado adequadamente
- Métricas documentadas
- ZIP criado com estrutura correta
🔗 Recursos de Apoio
📖 Leitura Complementar
- Refactoring: Improving the Design of Existing Code - Martin Fowler
- Clean Code - Robert Martin
- Effective Java - Joshua Bloch
🛠️ Ferramentas Recomendadas
- IDE: IntelliJ IDEA (refatorações automáticas)
- Testes: JUnit 5 + AssertJ
- Cobertura: JaCoCo
- Métricas: SonarQube ou plugins do IDE
💡 Dicas de Sucesso
- Comece pequeno: Uma técnica por vez
- Testes primeiro: Sempre valide comportamento
- Commits frequentes: Facilita rollback se necessário
- Métricas objetivas: Use números para medir melhoria
- Code review: Peça feedback de colegas
Bom trabalho! 🚀