Understanding Refactoring: Motivation, Standards, and a Practical Java Example
This article explains why refactoring is essential for maintainable code, outlines quality standards such as readability and extensibility, and walks through a concrete Java example that evolves from a monolithic statement method to a clean, modular design using extract method, move method, and replace‑temp‑with‑query techniques.
Why Refactor?
As programmers we often inherit legacy code that is hard to read and modify. The article starts with a vivid description of opening a massive method in an IDE, struggling to understand its logic, and feeling overwhelmed by the tangled code.
Good Code Reference Standards
Good code should be readable, extensible, maintainable, concise, reusable, and testable. Specific criteria include sensible module boundaries, high cohesion and low coupling, adherence to classic design principles, ease of adding new features, reuse potential, test coverage, and compliance with coding conventions.
How Code Decays
Even well‑designed code degrades over time as many developers modify it, leading to a gradual erosion of structure and quality.
What to Do When Code Starts to Rot
The article introduces refactoring as the antidote, emphasizing that code should be improved before adding new features.
What Is Refactoring?
According to "Refactoring: Improving the Design of Existing Code", refactoring is a disciplined technique for changing the internal structure of software without altering its observable behavior, thereby improving understandability and reducing modification cost.
Problems Refactoring Solves
Improves software design and prevents structural decay.
Makes code easier to understand and express its intent.
Helps uncover bugs by clarifying behavior.
Increases development speed by preserving a clean design.
A Small Refactoring Example
The example deals with a movie‑rental system that calculates charges and frequent‑renter points. The initial V0 version contains a large statement() method in the Customer class that mixes formatting, calculation, and business rules.
package chapter1.v0;
public class Customer {
private List<Rental> rentals = new ArrayList<Rental>();
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
String result = "Rental Record for " + getName() + "
";
for (Rental each : rentals) {
double thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2) {
thisAmount += (each.getDaysRented() - 2) * 1.5;
}
break;
case Movie.CHILDREN:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.NEW_RELEASE:
thisAmount += 1.5;
if (each.getDaysRented() > 3) {
thisAmount += (each.getDaysRented() - 3) * 1.5;
}
break;
default:
break;
}
// add frequent renter points
frequentRenterPoints++;
// add bonus for a two‑day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) {
frequentRenterPoints++;
}
// show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "
";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + String.valueOf(totalAmount) + "
";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
}Analysis shows violations of the Single Responsibility Principle and poor extensibility (Open/Closed Principle). The first change—adding an HTML version of the statement—can be done by copying and tweaking the method, but subsequent changes quickly make the code unmaintainable.
Refactoring Step 1: Extract the Logical “Mud”
The switch statement that computes the charge is extracted into a private method amountFor(Rental each):
private double amountFor(Rental each) {
double thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2) {
thisAmount += (each.getDaysRented() - 2) * 1.5;
}
break;
case Movie.CHILDRENS:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.NEW_RELEASE:
thisAmount += 1.5;
if (each.getDaysRented() > 3) {
thisAmount += (each.getDaysRented() - 3) * 1.5;
}
break;
default:
break;
}
return thisAmount;
}Refactoring Step 2: Move Method to the Right Class
Since the calculation uses only data from Rental, the method is moved to the Rental class (Move Method). The Customer statement now calls each.getCharge() and each.getFrequentRenterPoints().
Refactoring Step 3: Eliminate Temporary Variables
Temporary variables such as thisAmount, totalAmount, and frequentRenterPoints are replaced with query methods getCharge(), getTotalAmount(), and getTotalFrequentRenterPoints() respectively.
After these steps the Customer class looks like:
public String statement() {
String result = "Rental Record for " + getName() + "
";
for (Rental each : rentals) {
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "
";
}
result += "Amount owed is " + String.valueOf(getTotalAmount()) + "
";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;
}
private int getTotalFrequentRenterPoints() {
int total = 0;
for (Rental each : rentals) {
total += each.getFrequentRenterPoints();
}
return total;
}
private double getTotalAmount() {
double total = 0;
for (Rental each : rentals) {
total += each.getCharge();
}
return total;
}The corresponding Rental class now contains the charge and points logic:
public double getCharge() {
double thisAmount = 0;
switch (getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (getDaysRented() > 2) {
thisAmount += (getDaysRented() - 2) * 1.5;
}
break;
case Movie.CHILDRENS:
thisAmount += getDaysRented() * 3;
break;
case Movie.NEW_RELEASE:
thisAmount += 1.5;
if (getDaysRented() > 3) {
thisAmount += (getDaysRented() - 3) * 1.5;
}
break;
default:
break;
}
return thisAmount;
}
public int getFrequentRenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1) {
return 2;
}
return 1;
}With the refactored design, adding a new output format (e.g., HTML) or changing pricing rules becomes straightforward, because the core calculation is isolated and reusable.
Next Steps
The article encourages readers to consider further evolution, such as replacing the switch with a strategy or polymorphic hierarchy, while noting that a movie’s category may change at runtime, making a pure subclass approach less suitable.
Conclusion
The piece emphasizes the importance of high‑quality code, introduces refactoring as a vital tool in a developer’s toolbox, and invites readers to practice the techniques on the provided example to experience the satisfaction of cleaner, more maintainable software.
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.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
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.
