Mastering Code Refactoring: Practical Techniques and Real-World Examples
This article explains the definition, goals, and common techniques of code refactoring, illustrated with clear before‑and‑after Java examples such as method extraction, variable extraction, conditional simplification, and abstract‑class extraction, and concludes with a brief Q&A on best practices.
Any fool can write code that a computer can understand; only a good programmer writes code that humans can understand. — Martin Fowler, "Refactoring"
Introduction
A humorous story about an elevator that skips the 4th floor illustrates how mismatched code modules can cause unexpected behavior, setting the stage for why systematic refactoring is essential.
What Is Code Refactoring?
Code refactoring means improving the internal structure and presentation of code without changing its external behavior, aiming to enhance readability, maintainability, performance, and extensibility.
Improve code structure for clarity.
Eliminate duplicate code.
Increase readability.
Boost performance and reduce resource usage.
Enhance extensibility.
Purpose of Refactoring
The main goals are to raise code quality, make the code easier to understand, maintain, and extend, and to reduce bugs and technical debt.
Improve readability.
Reduce redundancy.
Increase performance.
Improve extensibility.
Common Refactoring Techniques
Extract Method
Long methods are split into smaller, focused methods to reduce duplication and improve clarity.
public void printInvoice(Invoice invoice) {
System.out.println("Invoice Number: " + invoice.getNumber());
System.out.println("Customer Name: " + invoice.getCustomer().getName());
System.out.println("Invoice Date: " + invoice.getDate());
System.out.println("Total Amount: " + invoice.getTotalAmount());
System.out.println("Items:");
for (InvoiceItem item : invoice.getItems()) {
System.out.println(item.getName() + " - " + item.getPrice() + " - " + item.getQuantity());
}
} public void printInvoice(Invoice invoice) {
printInvoiceHeader(invoice);
printInvoiceItems(invoice.getItems());
}
private void printInvoiceHeader(Invoice invoice) {
System.out.println("Invoice Number: " + invoice.getNumber());
System.out.println("Customer Name: " + invoice.getCustomer().getName());
System.out.println("Invoice Date: " + invoice.getDate());
System.out.println("Total Amount: " + invoice.getTotalAmount());
}
private void printInvoiceItems(List<InvoiceItem> items) {
System.out.println("Items:");
for (InvoiceItem item : items) {
System.out.println(item.getName() + " - " + item.getPrice() + " - " + item.getQuantity());
}
}Extract Variable
Complex expressions are assigned to well‑named variables to improve readability.
public double calculateTotalAmount(List<InvoiceItem> items) {
double totalAmount = 0;
for (InvoiceItem item : items) {
totalAmount += item.getPrice() * item.getQuantity();
}
if (totalAmount > 100) {
totalAmount *= 0.9;
}
return totalAmount;
} public double calculateTotalAmount(List<InvoiceItem> items) {
double totalAmount = 0;
for (InvoiceItem item : items) {
double itemAmount = item.getPrice() * item.getQuantity();
totalAmount += itemAmount;
}
if (totalAmount > 100) {
totalAmount *= 0.9;
}
return totalAmount;
}Refactor Conditional Statements
Multiple related conditions are consolidated into a single helper method, often paving the way for strategy patterns.
public boolean canCreateAccount(Customer customer) {
boolean canCreate = true;
if (customer.getAge() < 18) {
canCreate = false;
}
if (customer.getAccountNumber() != null && customer.getAccountNumber().length() != 0) {
canCreate = false;
}
if (customer.getCreditScore() < 500) {
canCreate = false;
}
return canCreate;
} public boolean canCreateAccount(Customer customer) {
boolean canCreate = true;
if (!isCustomerEligible(customer)) {
canCreate = false;
}
return canCreate;
}
private boolean isCustomerEligible(Customer customer) {
if (customer.getAge() < 18) {
return false;
}
if (customer.getAccountNumber() != null && customer.getAccountNumber().length() != 0) {
return false;
}
if (customer.getCreditScore() < 500) {
return false;
}
return true;
}Extract Abstract Class
Common functionality across classes is moved into an abstract superclass to reduce duplication.
public class SavingsAccount {
private double balance;
private double interestRate;
public SavingsAccount(double balance, double interestRate) {
this.balance = balance;
this.interestRate = interestRate;
}
public double getBalance() { return balance; }
public double getInterestRate() { return interestRate; }
public double calculateInterest() { return balance * interestRate; }
}
public class CheckingAccount {
private double balance;
private double transactionFee;
public CheckingAccount(double balance, double transactionFee) {
this.balance = balance;
this.transactionFee = transactionFee;
}
public double getBalance() { return balance; }
public double getTransactionFee() { return transactionFee; }
public double calculateTransactionFee() { return transactionFee; }
} public abstract class Account {
protected double balance;
public Account(double balance) { this.balance = balance; }
public double getBalance() { return balance; }
public abstract double calculateInterest();
}
public class SavingsAccount extends Account {
private double interestRate;
public SavingsAccount(double balance, double interestRate) {
super(balance);
this.interestRate = interestRate;
}
public double getInterestRate() { return interestRate; }
public double calculateInterest() { return balance * interestRate; }
}
public class CheckingAccount extends Account {
private double transactionFee;
public CheckingAccount(double balance, double transactionFee) {
super(balance);
this.transactionFee = transactionFee;
}
public double getTransactionFee() { return transactionFee; }
public double calculateInterest() { return transactionFee; }
}Q&A
1. What is the relationship between refactoring and design? Refactoring improves existing code locally, while design shapes the overall architecture; good design reduces the need for extensive refactoring.
2. When should you refactor versus rewrite? Refactor when the existing code can be incrementally improved; rewrite when the technology stack is obsolete or the codebase is beyond reasonable repair.
3. How to avoid introducing bugs during refactoring? Rely on comprehensive unit tests with high coverage to catch regressions early.
Conclusion
Refactoring is not a magical fix but a disciplined practice that breaks large changes into small, safe steps while preserving behavior. By mastering basic techniques such as method extraction, variable extraction, conditional simplification, and abstract‑class extraction, developers can keep codebases clean, maintainable, and ready for future evolution.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
