🔄 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ão → Um local
- Uma regra → Uma implementação
- Uma mudança → Um arquivo
⚖️ DRY vs. WET
DRY: Don't Repeat Yourself
WET: Write Everything Twice
ou
WET: We Enjoy Typing
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étrica | Duplicado | Centralizado |
|---|---|---|
| Alteração de regra | 5 arquivos | 1 arquivo |
| Tempo de mudança | 2 horas | 15 minutos |
| Risco de inconsistência | 95% | 5% |
| Bugs por alteração | 2-3 bugs | 0-1 bug |
ROI do DRY:
Economia de 87% do tempo de manutenção
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:
- Quantos locais fazem validação de email?
- Quais diferenças existem entre implementações?
- 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ência | Detalhes |
|---|---|
| Identificação | Reconhece 2+ locais duplicados |
| Análise | Identifica diferenças comportamentais |
| Impacto | Prevê bugs de inconsistência |
💡 Dica de identificação:
Procure por padrões como:
•
•
•
em múltiplos locais!
Procure por padrões como:
•
if (email.contains("@"))•
email.matches("...")•
email.length() > Xem 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:
- Extrair método
validarEmail() - Centralizar em classe utilitária
- Substituir todas as validações duplicadas
- 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
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!
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!
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
Nova funcionalidade deve reutilizar validadores existentes
📈 Análise Before/After: Métricas
📊 Resultado da Refatoração
| Métrica | ❌ Antes | ✅ Depois | 📈 Melhoria |
|---|---|---|---|
| Manutenção | 5 locais | 1 local | 80% menos |
| Tempo para mudança | 2 horas | 15 min | 87% menos |
| Bugs inconsistência | 3/sprint | 0/sprint | 100% menos |
| Testes quebrados | 15 falhas | 3 falhas | 80% menos |
| Complexidade | Alta | Baixa | Simplificou |
💰 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
= 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.
É sobre tomar uma decisão uma vez e garantir que ela seja aplicada consistentemente em todo o sistema.
💡 Principais Takeaways:
- Duplicação = Bug futuro garantido quando regras mudarem
- Componentes reutilizáveis devem ser projetados para testar e manter
- Centralização permite evolução controlada do sistema
- 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
Compartilhem casos reais onde viram duplicação problemática
📱 Contato e Recursos
📞 Professor Ricardo Pires
- Email: ricardo.pires@etec.sp.gov.br
- Horário: Seg-Sex, 18h30-21h30
- Sala: Laboratório de Informática 2
📁 Materiais da Aula
- Slides:
/Aula02/material-pedagogico/ - Código:
/Aula02/projeto-pratico/ - Exercícios:
exercicios-praticos.md - Referências:
material-apoio.md
🔗 Links Úteis
Próxima aula:
Refatoração Segura em Java
Segunda-feira, 23/03/2026
Refatoração Segura em Java
Segunda-feira, 23/03/2026