🎯 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:

  1. Identificar code smells em código Java real
  2. Aplicar técnicas Extract Method e Decompose Conditional
  3. Validar refatorações através de testes automatizados
  4. Medir o impacto da refatoração na manutenibilidade
  5. 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

  1. Identifique pelo menos 5 code smells diferentes no código acima
  2. Classifique cada smell encontrado (ex: método longo, condicionais aninhadas, etc.)
  3. Estime o número de responsabilidades que o método processar tem
  4. Propor uma estrutura de métodos após a refatoração

📋 Template de Resposta

#Code SmellClassificaçãoLinhas AfetadasImpacto 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)

  1. Identifique as diferentes responsabilidades no método
  2. Liste os métodos que você pretende extrair
  3. Defina a assinatura de cada método

Passo 2: Refatoração Incremental (20 min)

  1. Extraia um método por vez
  2. Execute os testes após cada extração
  3. Mantenha o método principal legível

Passo 3: Validação (5 min)

  1. Execute toda a suíte de testes
  2. Compare performance se necessário
  3. 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)

  1. Substitua as condicionais aninhadas por guard clauses
  2. Trate casos inválidos primeiro
  3. Mantenha o fluxo principal linear

Parte B: Extract Conditional Methods (10 min)

  1. Extraia métodos para validações
  2. Creates tabela de preços como constantes
  3. Simplifique cálculo por região

Parte C: Strategy Pattern (5 min)

  1. Considere implementar Strategy para cada região
  2. Avalie se vale a pena neste caso
  3. 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étricaAntesDepoisMeta
Complexidade ciclomática??< 5
Linhas do método principal48?< 20
Níveis máximos de aninhamento7?< 3
Número de condições por método12?< 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)

  1. Leia o código completo fornecido
  2. Mapeie as responsabilidades de cada método
  3. Identifique todos os code smells
  4. Planeje a estrutura final da refatoração

Fase 2: Preparação (10 min)

  1. Escreva testes de comportamento abrangentes
  2. Configure ambiente de testes
  3. Execute testes para estabelecer baseline

Fase 3: Refatoração Sistemática (20 min)

  1. Aplique Extract Method de forma incremental
  2. Use Decompose Conditional onde necessário
  3. Execute testes após cada mudança
  4. Mantenha commits pequenos e frequentes

Fase 4: Validação e Melhorias (5 min)

  1. Execute todos os testes
  2. Meça métricas de código
  3. Documente mudanças realizadas

📊 Métricas de Sucesso

MétricaMeta MínimaMeta Ideal
Métodos com < 20 linhas80%95%
Complexidade ciclomática < 590%100%
Cobertura de testes85%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)

  1. Configure Jacoco no projeto
  2. Execute testes com cobertura
  3. Analise relatório gerado
  4. Meta: 90%+ cobertura das linhas refatoradas

📋 Template de Análise

Caso de TesteAntesDepoisStatusObservações
Entrada válida✅/❌
Entrada inválida✅/❌
Casos extremos✅/❌
Performance✅/❌

📊 Critérios de Avaliação

📈 Estrutura da Avaliação (Total: 100 pontos)

ExercícioPesoCritérios EspecíficosPontos
Ex 1: Identificação15%Code smells encontrados, classificação correta15
Ex 2: Extract Method25%Métodos extraídos, testes passando, nomes expressivos25
Ex 3: Decompose Conditional25%Redução de aninhamento, guard clauses, clareza25
Ex 4: Projeto Completo25%Refatoração completa, métricas atingidas25
Ex 5: Testes10%Cobertura, qualidade dos testes10

🎯 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

🛠️ 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

  1. Comece pequeno: Uma técnica por vez
  2. Testes primeiro: Sempre valide comportamento
  3. Commits frequentes: Facilita rollback se necessário
  4. Métricas objetivas: Use números para medir melhoria
  5. Code review: Peça feedback de colegas

Bom trabalho! 🚀