📚 Material de Apoio - Aula 03
Refatoração Segura em Java
Desenvolvimento de Sistemas II
Prof. Ricardo Pires
3º Técnico DS
🎯 Quick Reference: Técnicas de Refatoração
🔧 Extract Method
// ANTES
public void longMethod() {
// Validação (10 linhas)
if (data == null) return;
if (data.isEmpty()) return;
// ... mais validações
// Processamento (15 linhas)
for (Item item : data) {
// lógica complexa
}
// Formatação (8 linhas)
String result = format();
// ... formatação
}
// DEPOIS
public void processData() {
if (!isValid(data)) return;
List<ProcessedItem> processed = processItems(data);
String result = formatResult(processed);
saveResult(result);
}
private boolean isValid(Data data) {
return data != null && !data.isEmpty();
}
private List<ProcessedItem> processItems(Data data) {
// lógica extraída
}
private String formatResult(List<ProcessedItem> items) {
// formatação extraída
}🌟 Decompose Conditional
// ANTES - Condicionais Aninhadas
if (user != null) {
if (user.isActive()) {
if (user.hasPermission("READ")) {
if (document != null) {
if (document.isPublished()) {
return document.getContent();
}
}
}
}
}
return null;
// DEPOIS - Guard Clauses
public String getDocumentContent(User user, Document document) {
if (!isUserValid(user)) return null;
if (!isDocumentAccessible(user, document)) return null;
return document.getContent();
}
private boolean isUserValid(User user) {
return user != null &&
user.isActive() &&
user.hasPermission("READ");
}
private boolean isDocumentAccessible(User user, Document document) {
return document != null &&
document.isPublished();
}📊 Code Smells: Catálogo Completo
🚩 1. Método Longo (Long Method)
Sintomas:
- Métodos com 20+ linhas
- Múltiplas responsabilidades
- Dificulta compreensão e teste
Solução: Extract Method
// Quebrar em métodos menores e focados
public void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
applyDiscounts(order);
saveOrder(order);
sendNotification(order);
}🚩 2. Condicionais Complexas (Complex Conditional)
Sintomas:
- Múltiplos níveis de aninhamento
- Condições longas e difíceis de ler
- Lógica boolean complexa
Solução: Decompose Conditional + Guard Clauses
// Em vez de condições aninhadas, usar early returns
public boolean canAccess(User user, Resource resource) {
if (user == null) return false;
if (!user.isActive()) return false;
if (resource == null) return false;
if (!resource.isPublic() && !user.hasRole("ADMIN")) return false;
return true;
}🚩 3. Código Duplicado (Duplicate Code)
Sintomas:
- Mesmo código em múltiplos lugares
- Lógica similar com pequenas variações
- Bug fixes que precisam ser aplicados em vários locais
Solução: Extract Method + Parameterização
// Extrair lógica comum
private double calculateDiscount(Customer customer, double amount, DiscountType type) {
if (!customer.isEligibleForDiscount()) return 0;
return switch (type) {
case VIP -> amount * 0.15;
case REGULAR -> amount * 0.05;
case SEASONAL -> amount * getSeasonalRate();
};
}🚩 4. Lista de Parâmetros Longa (Long Parameter List)
Sintomas:
- Métodos com 4+ parâmetros
- Parâmetros frequentemente passados juntos
- Dificulta chamadas do método
Solução: Parameter Object
// ANTES
public void createUser(String name, String email, int age,
String address, String phone, String department) {
// ...
}
// DEPOIS
public void createUser(UserData userData) {
// ...
}
public class UserData {
private String name, email, address, phone, department;
private int age;
// getters/setters
}🚩 5. Classe Grande (Large Class)
Sintomas:
- Classes com 500+ linhas
- Muitas responsabilidades
- Dificulta navegação e manutenção
Solução: Extract Class
// Separar responsabilidades
public class UserService {
public void createUser(UserData data) { }
public void updateUser(User user) { }
}
public class UserValidator {
public ValidationResult validate(UserData data) { }
}
public class UserNotifier {
public void sendWelcomeEmail(User user) { }
}🚩 6. Nomes Confusos (Unclear Names)
Sintomas:
- Variáveis com nomes genéricos (data, info, temp)
- Métodos que não expressam intenção
- Nomes que mentem sobre funcionalidade
Solução: Rename Method/Variable
// ANTES
public boolean check(User u, double v, int t) {
return u.getCredit() > v && u.getDays() > t;
}
// DEPOIS
public boolean hasEnoughCreditAndExperience(User user,
double requiredCredit,
int minimumDays) {
return user.getCredit() > requiredCredit &&
user.getDays() > minimumDays;
}🚩 7. Comentários Excessivos (Comments)
Sintomas:
- Comentários explicando o que código faz
- Comentários desatualizados
- Código que precisa comentário para ser entendido
Solução: Self-Documenting Code
// ANTES
// Desconto de 10% se cliente VIP com mais de 100 compras
if (c.isVip() && c.getPurchases() > 100) {
total = total * 0.9; // aplica desconto
}
// DEPOIS
if (isEligibleForVipDiscount(customer)) {
total = applyVipDiscount(total);
}
private boolean isEligibleForVipDiscount(Customer customer) {
return customer.isVip() && customer.getPurchases() > 100;
}
private double applyVipDiscount(double total) {
return total * 0.9;
}🛠️ Ferramentas de Refatoração
💡 IntelliJ IDEA - Shortcuts Essenciais
| Refatoração | Atalho | Uso |
|---|---|---|
| Extract Method | Ctrl+Alt+M | Extrair código selecionado para método |
| Extract Variable | Ctrl+Alt+V | Criar variável para expressão |
| Extract Constant | Ctrl+Alt+C | Criar constante para valor literal |
| Rename | Shift+F6 | Renomear símbolo em todo projeto |
| Inline | Ctrl+Alt+N | Remover método/variável desnecessária |
| Change Signature | Ctrl+F6 | Alterar parâmetros de método |
| Move | F6 | Mover método entre classes |
🎯 Eclipse - Atalhos Principais
| Refatoração | Atalho | Uso |
|---|---|---|
| Extract Method | Alt+Shift+M | Extrair método |
| Extract Local Variable | Alt+Shift+L | Criar variável local |
| Rename | Alt+Shift+R | Renomear elemento |
| Quick Fix | Ctrl+1 | Sugestões automáticas |
📊 Métricas de Qualidade
Complexidade Ciclomática
// Complexidade = 1 (caminho linear)
public String getGrade(int score) {
return score >= 60 ? "Pass" : "Fail";
}
// Complexidade = 4 (4 caminhos possíveis)
public String getGrade(int score) {
if (score >= 90) return "A"; // +1
else if (score >= 80) return "B"; // +1
else if (score >= 70) return "C"; // +1
else return "F"; // +1 (base)
}Target: Complexidade ≤ 5 por método
Linhas de Código por Método
// BOM: Método focado (8 linhas)
public User createUser(String email, String password) {
validateEmail(email);
validatePassword(password);
User user = new User(email, encryptPassword(password));
userRepository.save(user);
return user;
}
// RUIM: Método longo (40+ linhas)
public User createUserWithValidationAndEmailAndLogging(...) {
// 40+ linhas fazendo várias coisas
}Target: ≤ 20 linhas por método
🧪 Estratégias de Teste para Refatoração
🛡️ Testes de Caracterização
/**
* Testes que capturam comportamento atual ANTES da refatoração
* Garantem que refatoração preserva funcionalidade
*/
@Test
@DisplayName("Captura comportamento atual para refatoração segura")
void testeCaracterizacao() {
// Arrange - setup idêntico ao sistema em produção
ProcessadorPedido processor = new ProcessadorPedido();
Pedido pedido = criarPedidoCompleto();
// Act - executar método que será refatorado
String resultado = processor.processar(pedido);
// Assert - documentar exatamente o que acontece hoje
assertEquals("Pedido processado. Total: R$ 150.00", resultado);
// Este teste DEFINE o comportamento que deve ser preservado
}🎯 Testes Parametrizados para Cobertura
@ParameterizedTest
@CsvSource({
"100, REGULAR, false, 100.0",
"100, VIP, true, 85.0",
"50, REGULAR, false, 50.0",
"1000, VIP, true, 850.0"
})
@DisplayName("Preserva cálculo de desconto após refatoração")
void testeCalculoDesconto(double valor, CustomerType tipo,
boolean temDesconto, double esperado) {
Customer customer = new Customer(tipo);
double resultado = calculadoraDesconto.calcular(customer, valor);
assertEquals(esperado, resultado, 0.01);
}🔄 Testes de Mutação
@Test
@DisplayName("Validação que testes capturam mudanças essenciais")
void testeMutacao() {
// Simula mudanças pequenas no código para verificar se testes quebram
// Se trocar > por >= aqui, teste DEVE quebrar
assertTrue(validador.isElegivel(customer, 100.01)); // deve passar
assertFalse(validador.isElegivel(customer, 100.0)); // deve falhar
// Se este teste não quebrar quando lógica muda,
// o teste não está validando o comportamento essencial
}⚡ Testes de Performance
@Test
@DisplayName("Refatoração não degradou performance significativamente")
void testePerformance() {
// Medir antes da refatoração
long inicio = System.nanoTime();
for (int i = 0; i < 1000; i++) {
processor.processar(criarPedidoComplexo());
}
long tempo = System.nanoTime() - inicio;
// Aceitar até 20% de degradação
assertTrue(tempo < BASELINE_TEMPO * 1.2,
"Performance degradou mais que 20%");
}📚 Referências e Leitura Adicional
📖 Livros Fundamentais
1. Refactoring: Improving the Design of Existing Code
Autor: Martin Fowler
Por que ler: Catálogo completo de técnicas de refatoração
📌 Capítulos essenciais para esta aula:
- Chapter 1: Refactoring, a First Example
- Chapter 3: Bad Smells in Code
- Chapter 6: Extract Method
- Chapter 9: Simplifying Conditional Expressions
2. Clean Code: A Handbook of Agile Software Craftsmanship
Autor: Robert C. Martin
Por que ler: Princípios de código limpo e legível
📌 Capítulos relevantes:
- Chapter 3: Functions (Extract Method)
- Chapter 17: Smells and Heuristics
- Chapter 5: Formatting
3. Working Effectively with Legacy Code
Autor: Michael Feathers
Por que ler: Técnicas para refatorar código sem testes
📌 Técnicas importantes:
- Characterization Tests
- Seam identification
- Breaking dependencies
🌐 Recursos Online
Sites Essenciais
-
- Explicações visuais de cada técnica
- Exemplos em múltiplas linguagens
- Exercícios interativos
-
- Site oficial do Martin Fowler
- Catálogo completo de refatorações
- Artigos e atualizações
-
- Design patterns + refactoring
- Exemplos práticos
- Code smells ilustrados
Videos e Tutoriais
🎥 YouTube Channels:
- "Code Aesthetic" - Refactoring in practice
- "Continuous Delivery" - Legacy code management
- "Uncle Bob Martin" - Clean code principles
🎯 Specific videos:
- "Refactoring: Extract Method" - IntelliJ Tutorial
- "The Art of Readable Code" - Google Tech Talk
- "Therapeutic Refactoring" - Katrina Owen
🛠️ Ferramentas de Apoio
Análise Estática
<!-- SonarQube Maven Plugin -->
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
</plugin>
<!-- PMD Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.17.0</version>
<configuration>
<rulesets>
<ruleset>/rulesets/java/design.xml</ruleset>
<ruleset>/rulesets/java/controversial.xml</ruleset>
</rulesets>
</configuration>
</plugin>Cobertura de Testes
<!-- JaCoCo Coverage Plugin -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>Métricas de Código
<!-- Metrics Maven Plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>javancss-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<failOnViolation>true</failOnViolation>
<ccnLimit>5</ccnLimit>
<ncssLimit>50</ncssLimit>
</configuration>
</plugin>💡 Dicas Avançadas
🎯 Quando NÃO Refatorar
❌ Situações de Alto Risco
// 1. Código sem testes adequados
public criticalPaymentMethod() {
// Sem testes = refatoração perigosa
// PRIMEIRO: escrever testes
// DEPOIS: refatorar
}
// 2. Sistema instável
if (productionErrors.count() > 5) {
// FOQUE: estabilizar primeiro
// Refatoração depois da estabilidade
}
// 3. Deadline crítico
if (deploymentIn < 2.days()) {
// EVITE: mudanças desnecessárias
// Refatore na próxima sprint
}⚖️ Trade-offs de Refatoração
// BENEFÍCIO vs. CUSTO
// ALTO BENEFÍCIO (refatorar!):
// - Código alterado frequentemente
// - Time crescendo (novos devs)
// - Bugs recorrentes na área
// BAIXO BENEFÍCIO (evitar):
// - Código legacy estável
// - Funcionalidade será removida
// - Time pequeno e experiente🔄 Refatoração Incremental
📅 Estratégia “Boy Scout Rule”
// A cada alteração, deixe código um pouco melhor
// ANTES de implementar feature:
git commit -m "Refactor: extract validation method"
// DURANTE implementação:
git commit -m "Feature: add new validation rule"
// RESULTADO: melhoria contínua sem esforço extra🎯 Refatoração por Sprints
Sprint N: Extract Methods
Sprint N+1: Decompose Conditionals
Sprint N+2: Extract Classes
Sprint N+3: Introduce Patterns
// Pequenas melhorias constantes > grandes refatorações esporádicas
👥 Refatoração em Equipe
📋 Code Review Checklist
□ Comportamento preservado (testes passando)?
□ Nomes de métodos expressivos?
□ Single Responsibility mantida?
□ Complexidade ciclomática melhorou?
□ Duplicação eliminada?
□ Performance não degradou?
□ Documentação atualizada se necessário?
🏆 Métricas de Team
// Tracking de melhoria contínua
public class RefactoringMetrics {
// Antes: 50 métodos com 20+ linhas
// Meta Sprint 1: 40 métodos com 20+ linhas
// Meta Sprint 2: 30 métodos com 20+ linhas
@Scheduled(weekly)
public void trackProgress() {
int longMethods = countMethodsOver20Lines();
double improvement = (baseline - current) / baseline * 100;
slackNotify("Refactoring progress: " + improvement + "% improvement");
}
}🎯 Exercícios Complementares
🏋️ Exercício A: Code Smell Detection
// Analise este código e identifique TODOS os code smells
public class OrderManager {
public String processOrder(String customerName, String customerEmail,
int customerAge, String customerAddress,
String productName, double productPrice,
int quantity, String couponCode,
boolean isExpress, String paymentMethod) {
// Validation
if (customerName == null || customerName.isEmpty()) {
return "Error: Invalid customer name";
}
if (customerEmail == null || !customerEmail.contains("@")) {
return "Error: Invalid email";
}
if (customerAge < 18) {
return "Error: Customer must be 18+";
}
if (productName == null || productName.isEmpty()) {
return "Error: Invalid product";
}
if (productPrice <= 0) {
return "Error: Invalid price";
}
if (quantity <= 0) {
return "Error: Invalid quantity";
}
// Calculate total
double total = productPrice * quantity;
// Apply coupon
if (couponCode != null && !couponCode.isEmpty()) {
if (couponCode.equals("SAVE10")) {
total = total * 0.9;
} else if (couponCode.equals("SAVE20")) {
total = total * 0.8;
} else if (couponCode.equals("FIRST")) {
if (isFirstTimeCustomer(customerEmail)) {
total = total * 0.85;
}
}
}
// Express shipping
double shipping = 0;
if (isExpress) {
if (total > 100) {
shipping = 15.0;
} else {
shipping = 25.0;
}
} else {
if (total > 50) {
shipping = 5.0;
} else {
shipping = 10.0;
}
}
total += shipping;
// Payment processing
if (paymentMethod.equals("CREDIT")) {
// Credit card processing...
// 20 lines of credit card logic
} else if (paymentMethod.equals("DEBIT")) {
// Debit card processing...
// 15 lines of debit card logic
} else if (paymentMethod.equals("PAYPAL")) {
// PayPal processing...
// 18 lines of PayPal logic
}
return "Order processed successfully. Total: $" + total;
}
private boolean isFirstTimeCustomer(String email) {
// Database lookup logic...
return false;
}
}Sua tarefa:
- Liste todos os code smells encontrados
- Classifique por severidade (Alto/Médio/Baixo)
- Propor plano de refatoração
- Estimar impacto da refatoração
🏋️ Exercício B: Refatoração Completa
// Refatore completamente este código aplicando todas as técnicas
public class ReportGenerator {
public String generateSalesReport(Date startDate, Date endDate,
String format, boolean includeCharts,
String salesPersonFilter,
String regionFilter) {
StringBuilder report = new StringBuilder();
// Header
if (format.equals("HTML")) {
report.append("<html><head><title>Sales Report</title></head><body>");
report.append("<h1>Sales Report from " + startDate + " to " + endDate + "</h1>");
} else if (format.equals("PDF")) {
report.append("SALES REPORT\n");
report.append("Period: " + startDate + " to " + endDate + "\n");
report.append("=====================================\n");
} else {
report.append("Sales Report - " + startDate + " to " + endDate + "\n");
}
// Data fetching and processing
List<Sale> sales = new ArrayList<>();
Connection conn = DatabaseManager.getConnection();
try {
String sql = "SELECT * FROM sales WHERE sale_date BETWEEN ? AND ?";
if (salesPersonFilter != null && !salesPersonFilter.isEmpty()) {
sql += " AND salesperson = ?";
}
if (regionFilter != null && !regionFilter.isEmpty()) {
sql += " AND region = ?";
}
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setDate(1, startDate);
stmt.setDate(2, endDate);
int paramIndex = 3;
if (salesPersonFilter != null && !salesPersonFilter.isEmpty()) {
stmt.setString(paramIndex++, salesPersonFilter);
}
if (regionFilter != null && !regionFilter.isEmpty()) {
stmt.setString(paramIndex, regionFilter);
}
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
Sale sale = new Sale();
sale.setId(rs.getInt("id"));
sale.setAmount(rs.getDouble("amount"));
sale.setSalesperson(rs.getString("salesperson"));
sale.setRegion(rs.getString("region"));
sale.setDate(rs.getDate("sale_date"));
sales.add(sale);
}
} catch (SQLException e) {
return "Error generating report: " + e.getMessage();
}
// Statistics calculation
double totalAmount = 0;
Map<String, Double> salesPersonTotals = new HashMap<>();
Map<String, Double> regionTotals = new HashMap<>();
for (Sale sale : sales) {
totalAmount += sale.getAmount();
String person = sale.getSalesperson();
salesPersonTotals.put(person,
salesPersonTotals.getOrDefault(person, 0.0) + sale.getAmount());
String region = sale.getRegion();
regionTotals.put(region,
regionTotals.getOrDefault(region, 0.0) + sale.getAmount());
}
// Format output
if (format.equals("HTML")) {
report.append("<h2>Summary</h2>");
report.append("<p>Total Sales: $" + String.format("%.2f", totalAmount) + "</p>");
report.append("<p>Number of Transactions: " + sales.size() + "</p>");
report.append("<h2>By Salesperson</h2><ul>");
for (Map.Entry<String, Double> entry : salesPersonTotals.entrySet()) {
report.append("<li>" + entry.getKey() + ": $" +
String.format("%.2f", entry.getValue()) + "</li>");
}
report.append("</ul>");
report.append("<h2>By Region</h2><ul>");
for (Map.Entry<String, Double> entry : regionTotals.entrySet()) {
report.append("<li>" + entry.getKey() + ": $" +
String.format("%.2f", entry.getValue()) + "</li>");
}
report.append("</ul>");
if (includeCharts) {
report.append("<h2>Charts</h2>");
report.append("<div>Chart data would be generated here...</div>");
}
report.append("</body></html>");
} else if (format.equals("PDF")) {
report.append("\nSUMMARY\n");
report.append("Total Sales: $" + String.format("%.2f", totalAmount) + "\n");
report.append("Number of Transactions: " + sales.size() + "\n\n");
report.append("BY SALESPERSON\n");
for (Map.Entry<String, Double> entry : salesPersonTotals.entrySet()) {
report.append(entry.getKey() + ": $" +
String.format("%.2f", entry.getValue()) + "\n");
}
report.append("\nBY REGION\n");
for (Map.Entry<String, Double> entry : regionTotals.entrySet()) {
report.append(entry.getKey() + ": $" +
String.format("%.2f", entry.getValue()) + "\n");
}
} else {
report.append("\nSummary:\n");
report.append("Total Sales: $" + String.format("%.2f", totalAmount) + "\n");
report.append("Transactions: " + sales.size() + "\n");
}
return report.toString();
}
}Meta da Refatoração:
- Reduzir método principal para < 15 linhas
- Eliminar duplicação de formatação
- Separar responsabilidades (data access, processing, formatting)
- Introduzir Strategy pattern para formatters
- Melhorar tratamento de erro
- Facilitar adição de novos formatos
Estrutura Final Esperada:
public class ReportGenerator {
public String generateSalesReport(ReportRequest request) {
// Método principal limpo
}
private List<Sale> fetchSalesData(ReportRequest request) { }
private SalesStatistics calculateStatistics(List<Sale> sales) { }
private String formatReport(SalesStatistics stats, ReportFormat format) { }
}📖 Use este material como referência durante e após a aula!