🔄 Aula 02

Reutilização de Código em Java

Princípio DRY (Don’t Repeat Yourself)

Desenvolvimento de Sistemas II
Prof. Ricardo Pires
3º Técnico DS | 16/03/2026


🎯 Objetivos da Aula

Conceituais

  • Compreender o princípio DRY
  • Identificar violações e impactos
  • Conhecer estratégias de centralização

Práticos

  • Detectar código duplicado
  • Extrair componentes reutilizáveis
  • Implementar validadores centralizados

Atitudinais

  • Valorizar consistência comportamental
  • Desenvolver responsabilidade pela manutenção
  • Cultivar disciplina de centralização

📈 Meta de sucesso:

75%+ dos alunos identificam corretamente duplicação e aplicam refatoração DRY

Problema Real: Bug de Inconsistência

😱 Cenário de Produção

// Controller de Cadastro
public class CadastroController {
    public void validarEmail(String email) {
        if (email.toLowerCase().matches(
            "^[a-z0-9+_.-]+@(.+)$")) {
            // Aceita maiúsculas
        }
    }
}
 
// Controller de Login
public class LoginController {
    public void validarEmail(String email) {
        if (email.matches(
            "^[a-z0-9+_.-]+@(.+)$")) {
            // Rejeita maiúsculas
        }
    }
}

🐛 Resultado:

  • Usuário cadastra com João@exemplo.com ✅
  • Usuário tenta logar com João@exemplo.com ❌
  • Suporte recebe chamado "não consigo acessar minha conta"
  • Cliente insatisfeito → abandona sistema

💰 Custo Real

  • 1 hora de desenvolvimento para cada correção
  • N locais para alterar = N × tempo
  • Bug em produção = perda de confiança

🔍 O que é DRY?

Don’t Repeat Yourself

“Toda informação deve ser expressa uma única vez no sistema. Mudanças requerem alterar apenas um local.”

🎯 Essência do DRY:

  • Uma decisãoUm local
  • Uma regraUma implementação
  • Uma mudançaUm arquivo

⚖️ DRY vs. WET

DRY: Don't Repeat Yourself
WET: Write Everything Twice
ou
WET: We Enjoy Typing

💡 Indicador simples:

Se você está fazendo “buscar e substituir” em múltiplos arquivos para a mesma alteração, você tem duplicação!


🔴 Tipos de Duplicação

1. Duplicação Forçada

// Documentação e código separados
/**
 * Email: máximo 254 chars (RFC 5321)
 */
public boolean validar(String email) {
    return email.length() <= 254; // Duplicação!
}

2. Duplicação Inadvertida

// Times diferentes, soluções diferentes
class TimeA { // Aceita .co.uk
    boolean validEmail(String e) { /* impl A */ }
}
class TimeB { // Só aceita .com
    boolean validEmail(String e) { /* impl B */ }
}

3. Duplicação Impatiente

// "É mais rápido copiar que reutilizar"
public void processarPedido(Pedido p) {
    // Copiado de validarCadastro()
    if (p.getEmail().isEmpty()) { /* ... */ }
    if (!p.getEmail().contains("@")) { /* ... */ }
}

4. Duplicação Entre Desenvolvedores

// Duas equipes fazem a mesma coisa
package com.timeA;
public class EmailUtils { /* validação */ }
 
package com.timeB;
public class ValidationHelper { /* validação */ }

💸 O Custo da Duplicação

Manutenção Multiplicada

ANTES: 5 locais alterados

UserValidator.java    // Local 1
ContactValidator.java // Local 2
LoginService.java     // Local 3
RegistroService.java  // Local 4
EmailService.java     // Local 5

DEPOIS: 1 local alterado

EmailValidator.java   // Local único
// Todos os outros USAM este

📊 Impactos Mensuráveis

MétricaDuplicadoCentralizado
Alteração de regra5 arquivos1 arquivo
Tempo de mudança2 horas15 minutos
Risco de inconsistência95%5%
Bugs por alteração2-3 bugs0-1 bug
ROI do DRY:
Economia de 87% do tempo de manutenção

Demonstração: Live Refatoração

Código com Duplicação

public class CadastroService {
    public void cadastrar(User user) {
        // Validação inline duplicada
        if (user.getEmail() == null ||
            user.getEmail().trim().isEmpty()) {
            throw new IllegalArgumentException(
                "Email obrigatório");
        }
        if (user.getEmail().length() > 200) {
            throw new IllegalArgumentException(
                "Email muito longo");
        }
        if (!user.getEmail().contains("@")) {
            throw new IllegalArgumentException(
                "Email inválido");
        }
    }
 
    public void editar(User user) {
        // MESMA validação duplicada!
        if (user.getEmail() == null ||
            user.getEmail().trim().isEmpty()) {
            throw new IllegalArgumentException(
                "Email obrigatório");
        }
        // ... resto igual
    }
}

Código DRY Refatorado

// Validador centralizado
public class EmailValidator {
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
 
    public static boolean isValid(String email) {
        return !StringUtils.isBlank(email) &&
               email.length() <= 254 &&
               EMAIL_PATTERN.matcher(email).matches();
    }
 
    public static void validate(String email) {
        if (!isValid(email)) {
            throw new IllegalArgumentException(
                "Email inválido: " + email);
        }
    }
}
 
// Uso consistente
public class CadastroService {
    public void cadastrar(User user) {
        EmailValidator.validate(user.getEmail());
        // Uma linha = comportamento garantido
    }
 
    public void editar(User user) {
        EmailValidator.validate(user.getEmail());
        // Uma linha = comportamento garantido
    }
}

🛠️ Estratégias de Centralização

1. Extração de Métodos

// Extrair lógica comum
private void validarNome(String nome) {
    if (StringUtils.isBlank(nome)) {
        throw new IllegalArgumentException(
            "Nome obrigatório");
    }
    if (nome.length() < 2) {
        throw new IllegalArgumentException(
            "Nome muito curto");
    }
}

2. Classes Utilitárias

public class Validators {
    public static class Email { /* ... */ }
    public static class Cpf { /* ... */ }
    public static class Phone { /* ... */ }
}

3. Componentes de Domínio

// Específico do domínio
public class UserValidator {
    public void validate(User user) {
        EmailValidator.validate(user.getEmail());
        CpfValidator.validate(user.getCpf());
        // Validações de User específicas
    }
}

⚖️ Trade-offs importantes:

  • Muito DRY = abstrações prematuras
  • Pouco DRY = duplicação difícil de manter
  • DRY ideal = centralizar regras de negócio

🧪 Exemplo: CPF Validator Completo

Implementação Robusta

public class CpfValidator {
 
    public static boolean isValid(String cpf) {
        String normalized = normalize(cpf);
        return isValidFormat(normalized) &&
               isValidCheckDigits(normalized);
    }
 
    public static String normalize(String cpf) {
        return StringUtils.isBlank(cpf) ? ""
               : cpf.replaceAll("\\D", "");
    }
 
    static boolean isValidFormat(String cpf) {
        return cpf.length() == 11 &&
               !cpf.matches("(\\d)\\1{10}");
    }
 
    static boolean isValidCheckDigits(String cpf) {
        return calculateDigit(cpf, 10) ==
                 getDigit(cpf, 9) &&
               calculateDigit(cpf, 11) ==
                 getDigit(cpf, 10);
    }
}

Características do Bom Validador:

Design para Reutilização

  • Métodos públicos bem documentados
  • Entrada flexível (com/sem máscara)
  • Saída consistente (boolean/exception)
  • Sem efeitos colaterais escondidos

Fail-Fast com Mensagens Úteis

  • Validação rápida (falha na primeira inconsistência)
  • Mensagens específicas (qual exatamente é o problema)
  • Informação suficiente para debug

Testabilidade

  • Métodos package-private para testar internos
  • Sem dependências externas complexas

🎯 Exercício Prático: Identificação

📝 Exercício 1 (10 min)

Analise este código e identifique:

  1. Quantos locais fazem validação de email?
  2. Quais diferenças existem entre implementações?
  3. Que bugs podem ocorrer?
public class UserController {
    public ResponseEntity<?> create(
        @RequestBody CreateUserRequest req) {
 
        if (req.getEmail() == null ||
            req.getEmail().trim().isEmpty()) {
            return ResponseEntity.badRequest()
                .body("Email obrigatório");
        }
        if (!req.getEmail().contains("@")) {
            return ResponseEntity.badRequest()
                .body("Email inválido");
        }
    }
}

🎯 Critérios de Avaliação:

CompetênciaDetalhes
IdentificaçãoReconhece 2+ locais duplicados
AnáliseIdentifica diferenças comportamentais
ImpactoPrevê bugs de inconsistência
💡 Dica de identificação:
Procure por padrões como:
if (email.contains("@"))
email.matches("...")
email.length() > X
em múltiplos locais!

⏱️ Timing:

  • 5 min análise individual
  • 5 min discussão em duplas

🔧 Exercício Prático: Refatoração

📝 Exercício 2 (15 min)

Refatore o código do Exercício 1:

Passos:

  1. Extrair método validarEmail()
  2. Centralizar em classe utilitária
  3. Substituir todas as validações duplicadas
  4. Verificar se testes passam

Template:

public class EmailValidator {
    public static boolean isValid(String email) {
        // TODO: implementar validação robusta
        return /* ... */;
    }
 
    public static void validate(String email) {
        if (!isValid(email)) {
            throw new IllegalArgumentException(
                "Email inválido: " + email);
        }
    }
}

🏆 Critérios de Sucesso:

Funcional:

  • Elimina duplicação completamente
  • Comportamento consistente em todos locais
  • Testes continuam passando

Qualidade:

  • Mensagens de erro úteis
  • Tratamento de edge cases (null, empty)
  • Documentação JavaDoc
Meta da refatoração:
Mudança de validação requer alterar apenas 1 arquivo

⏱️ Timing:

  • 10 min refatoração individual
  • 5 min code review em duplas

🧪 Testes Parametrizados: Qualidade Garantida

Teste de Casos Válidos

@ParameterizedTest
@ValueSource(strings = {
    "user@example.com",
    "test.email@domain.co.uk",
    "user+tag@example.org",
    "firstname.lastname@company.travel"
})
void shouldAcceptValidEmails(String email) {
    assertTrue(EmailValidator.isValid(email),
        () -> "Email válido rejeitado: " + email);
}

Teste de Casos Inválidos

@ParameterizedTest
@ValueSource(strings = {
    "",                    // Vazio
    "plainaddress",       // Sem @
    "@missinglocal.com",  // Sem parte local
    "missing@.com"        // Domínio inválido
})
void shouldRejectInvalidEmails(String email) {
    assertFalse(EmailValidator.isValid(email),
        () -> "Email inválido aceito: " + email);
}

🎯 Benefícios dos Testes Parametrizados:

Cobertura Ampla:

  • Múltiplos cenários em um teste
  • Edge cases cobertos sistematicamente
  • Casos reais de produção

Maintibilidade:

  • Adicionar casos = adicionar linha
  • Falha específica mostra exatamente qual input
  • Documentação viva do comportamento esperado
💡 Regra de ouro:
Se você tem um validador centralizado, deve ter testes parametrizados cobrindo todos os casos possíveis!

📈 Meta de Cobertura:

  • 85%+ cobertura de instruções
  • 80%+ cobertura de branches

⚠️ Armadilhas Comuns do DRY

❌ Premature Abstraction

// Ruim: abstrair coisas diferentes
public class GenericValidator<T> {
    public boolean isValid(T input,
                          String regex,
                          int minLength,
                          int maxLength,
                          boolean allowNull) {
        // Muito genérico = difícil de usar
    }
}

❌ Wrong Level of Abstraction

// Ruim: misturar níveis
public class Validator {
    public static boolean isValidEmailOrCpf(
        String input) {
        // Conceitos diferentes juntos
    }
}

✅ DRY Adequado

// Bom: conceitos relacionados
public class EmailValidator {
    public static boolean isValid(String email) {
        return isValidFormat(email) &&
               isValidLength(email);
    }
}
 
// Bom: separação clara
public class CpfValidator {
    public static boolean isValid(String cpf) {
        // Lógica específica de CPF
    }
}
⚖️ Regra de equilíbrio:
DRY se aplica a regras de negócio, não a coincidências sintáticas!

🏆 Melhores Práticas DRY

🎯 Design Guidelines

1. Single Responsibility

  • Um validador = Uma regra de negócio
  • Fácil de testar independentemente
  • Fácil de documentar claramente

2. Clear Interface

// Interface clara e previsível
public static boolean isValid(String input)
public static void validate(String input)
public static String normalize(String input)

3. Fail-Fast Design

  • Validação rápida (primeira falha para)
  • Mensagens específicas sobre o problema
  • Zero surpresas no comportamento

📊 Métricas de Qualidade

Indicadores de Sucesso DRY:

  • Zero duplicação de regras de negócio
  • Comportamento consistente entre módulos
  • Alteração única para mudança de regra
  • Mensagens úteis nas falhas
  • Testes abrangentes dos validadores

Sinais de Alerta:

  • 🚨 “Como validar X?” perguntado mais de uma vez
  • 🚨 Bug de inconsistência entre features
  • 🚨 PR tocando “mesma lógica em vários arquivos”
  • 🚨 Discussão sobre “qual regex usar”
Meta da equipe:
Nova funcionalidade deve reutilizar validadores existentes

📈 Análise Before/After: Métricas

📊 Resultado da Refatoração

Métrica❌ Antes✅ Depois📈 Melhoria
Manutenção5 locais1 local80% menos
Tempo para mudança2 horas15 min87% menos
Bugs inconsistência3/sprint0/sprint100% menos
Testes quebrados15 falhas3 falhas80% menos
ComplexidadeAltaBaixaSimplificou

💰 ROI Calculado

Economia por Sprint:

  • Tempo desenvolvimento: 6h → 1h = 5h economizadas
  • Bugs evitados: 3 × 2h debug = 6h economizadas
  • Refactoring seguro: 4h → 30min = 3.5h economizadas

📈 Total econômico:

14.5 horas economizadas por sprint
= 1.8 dias de desenvolvimento
= 20% do tempo da equipe

DRY não é dogma teórico, é economia real!


🎯 Consolidação e Próximos Passos

🔑 Mensagem-Chave da Aula

DRY não é sobre evitar digitar código duas vezes.
É sobre tomar uma decisão uma vez e garantir que ela seja aplicada consistentemente em todo o sistema.

💡 Principais Takeaways:

  1. Duplicação = Bug futuro garantido quando regras mudarem
  2. Componentes reutilizáveis devem ser projetados para testar e manter
  3. Centralização permite evolução controlada do sistema
  4. Fail-fast com mensagens úteis economiza horas de debug

🚀 Próxima Aula 03: Refatoração Segura

Conecta com DRY porque:

  • Validadores centralizados serão base para refatorações
  • Testes abrangentes garantem segurança das mudanças
  • Componentes bem definidos facilitam extrações

📚 Estudo dirigido:

  • Identificar 3 casos de duplicação em código próprio
  • Implementar um validador adicional seguindo padrões
  • Ler: Clean Code (Cap. 3) sobre funções pequenas

🎯 Prepare-se para:

  • Técnicas de refactoring step-by-step
  • Como manter compatibilidade durante mudanças
  • Ferramentas automatizadas de refatoração

🙋‍♂️ Perguntas e Discussão

Dúvidas sobre DRY?

Vamos compartilhar experiências:

  • Alguém já teve bug de validação inconsistente?
  • Qual foi o impacto de código duplicado que vocês viram?
  • Como aplicariam DRY nos projetos atuais?
💬 Discussão colaborativa:
Compartilhem casos reais onde viram duplicação problemática

📱 Contato e Recursos

📞 Professor Ricardo Pires

📁 Materiais da Aula

  • Slides: /Aula02/material-pedagogico/
  • Código: /Aula02/projeto-pratico/
  • Exercícios: exercicios-praticos.md
  • Referências: material-apoio.md
Próxima aula:
Refatoração Segura em Java
Segunda-feira, 23/03/2026