Refatorar código é essencial para garantir a qualidade e a manutenção de um software ao longo do tempo. Neste post, exploramos as melhores práticas para refatoração, com exemplos em Java para ilustrar como melhorar a legibilidade, estrutura e eficiência do seu código.
1. Mantenha os Testes
Antes de iniciar a refatoração, é crucial garantir que haja uma suíte de testes automatizados em vigor. Isso ajuda a verificar que o comportamento do sistema não foi alterado após a refatoração. No Java, você pode usar frameworks como JUnit para garantir que seus testes sejam executados corretamente.
Exemplo:
Antes da refatoração:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Suíte de testes com JUnit:
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
2. Refatore em Pequenos Passos
Refatorar o código em pequenos incrementos permite que você teste cada mudança de maneira isolada. Isso facilita a identificação de problemas, caso eles surjam.
Exemplo:
Suponha que você tenha um método que realiza várias operações dentro de um único método. Em vez de refatorar tudo de uma vez, faça a refatoração em partes:
Antes da refatoração:
public class Order {
public double calculateTotalPrice(double price, int quantity, double discount) {
double total = price * quantity;
total -= total * discount;
total = applyTax(total);
return total;
}
private double applyTax(double price) {
return price * 1.1; // aplicação de 10% de imposto
}
}
Então para refatoração em pequenos passos, crie um método separado para calcular o desconto:
public class Order {
public double calculateTotalPrice(double price, int quantity, double discount) {
double total = price * quantity;
total -= calculateDiscount(total, discount);
total = applyTax(total);
return total;
}
private double calculateDiscount(double price, double discount) {
return price * discount;
}
private double applyTax(double price) {
return price * 1.1;
}
}
3. Não Faça Mudanças Desnecessárias
Durante a refatoração, evite mudar partes do código que não afetam a funcionalidade. O objetivo é melhorar o código sem introduzir riscos ou mudanças desnecessárias.
Exemplo:
Antes da refatoração:
public class Employee {
private String name;
private double salary;
public void setName(String name) {
this.name = name;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double calculateSalary() {
return salary;
}
}
Se o cálculo de salário não precisa ser alterado, evite mudanças desnecessárias. Refatore apenas o que for necessário, como renomear variáveis ou melhorar a legibilidade.
public class Employee {
private String name;
private double salary;
public void setName(String name) {
this.name = name;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getSalary() {
return salary;
}
}
4. Aplicar os Princípios de Design de Software
A refatoração deve melhorar a aderência aos princípios de design, como o SOLID, para criar código modular e fácil de manter.
Exemplo:
Antes da refatoração (Violando o Princípio da Responsabilidade Única - SRP):
public class Invoice {
public void printInvoice() {
// código para gerar e imprimir a fatura
}
public void saveInvoiceToDatabase() {
// código para salvar a fatura no banco de dados
}
}
Refatoração aplicando o SRP:
public class Invoice {
private InvoicePrinter printer;
private InvoiceRepository repository;
public Invoice(InvoicePrinter printer, InvoiceRepository repository) {
this.printer = printer;
this.repository = repository;
}
public void processInvoice() {
printer.print(this);
repository.save(this);
}
}
class InvoicePrinter {
public void print(Invoice invoice) {
// código para gerar e imprimir a fatura
}
}
class InvoiceRepository {
public void save(Invoice invoice) {
// código para salvar a fatura no banco de dados
}
}
5. Renomeação Clara e Consistente
Um bom nome de variável ou método torna o código mais legível e fácil de entender. Durante a refatoração, aproveite para melhorar a clareza dos nomes.
Exemplo:
Antes da refatoração:
public class User {
private String n;
private String em;
public String getN() {
return n;
}
public String getEm() {
return em;
}
}
Refatoração com nomes mais claros:
public class User {
private String name;
private String email;
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
6. Evitar a Complexidade Desnecessária
A refatoração deve buscar simplificar o código, eliminando redundâncias ou lógicas complexas.
Exemplo:
Antes da refatoração:
public class DiscountCalculator {
public double calculateDiscount(double price, String discountType) {
if (discountType.equals("percentage")) {
return price * 0.1;
} else if (discountType.equals("fixed")) {
return 10;
} else {
return 0;
}
}
}
Refatoração para simplificar a lógica:
public class DiscountCalculator {
public double calculateDiscount(double price, DiscountType discountType) {
switch (discountType) {
case PERCENTAGE:
return price * 0.1;
case FIXED:
return 10;
default:
return 0;
}
}
}
enum DiscountType {
PERCENTAGE, FIXED
}
7. Refatoração Contínua
Refatoração contínua não é um evento único, mas um processo contínuo. À medida que o sistema evolui, novas refatorações devem ser feitas para manter o código limpo e sustentável. Um exemplo disso, pode ser que a medida que novas funcionalidades forem adicionadas ao sistema, como a adição de mais tipos de desconto no exemplo acima, a refatoração contínua garantirá que o código se mantenha organizado e fácil de entender, sem se tornar um emaranhado de lógicas e regras.