- Published on
Reading Notes: Refactoring
9 min read
- Authors
- Name
- Shuwen
Table of Contents
- Reading Notes: Refactoring
- What This Book Taught Me
- My Personal Takeaways
- Favorite Line
- Practical Refactoring Rules (with Java Examples)
- 1. Extract Method
- 2. Replace Conditional with Polymorphism
- 3. Introduce Parameter Object
- 4. Replace Magic Number with Named Constant
- 5. Rename Method or Variable
- 6. Decompose Conditional
- Core Principles Recap
- Final Reflection
Reading Notes: Refactoring
While reading Refactoring by Martin Fowler, one idea struck me more than anything else:
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
That single sentence completely reframed how I think about programming.
We often treat code as instructions for machines, but machines do not care whether the code is elegant or ugly. Humans do. We care because we have to read it, maintain it, and change it. Fowler reminds us that the true audience of software is future humans: teammates, maintainers, and even our future selves.
What This Book Taught Me
- Code is for people, not for machines Good code tells a story. It communicates intent, not just logic. The best variable names, the clearest methods, and the well-structured classes all exist to help people understand what is happening without mental gymnastics.
Readable code is also faster in the long run. It reduces bugs, shortens onboarding time, and makes optimization safer.
- The best code is easy to change You can tell a design is good not because it looks pretty on a UML diagram, but because you can change it confidently tomorrow.
Every new requirement and every bug fix is really a test of design flexibility. If modifying a function feels scary or fragile, the design has hardened too soon.
Refactoring is the antidote. It keeps your code soft and evolvable, changing the internal structure without altering behavior, one small, safe step at a time.
- Refactoring is not rewriting Fowler draws a sharp line between refactoring and rewriting.
Rewriting throws code away and starts over, which is risky and expensive. Refactoring reshapes existing code while preserving behavior. The key to safe refactoring is tests. Automated tests are the safety net that lets you clean code confidently. Without them, refactoring feels like walking a tightrope blindfolded.
- Code smells are signals, not sins The book popularized "code smells", clues that something might be wrong in your design. A few classic examples:
- Long methods
- Duplicated code
- Large classes
- Primitive obsession
- Feature envy
Smells do not mean "bad programmer." They mean "opportunity to improve." Once you recognize a smell, you can apply the right refactoring technique, a small transformation that makes the design clearer.
- Refactoring is a daily habit The most practical insight I got is that refactoring is not a one-time cleanup before release. It is a habit, something you do continuously as you read, debug, or extend code.
Small, constant improvements keep a codebase healthy. It is like brushing your teeth: a few minutes a day prevents major pain later.
My Personal Takeaways
- Readability is the ultimate optimization.
- Elegant algorithms matter less than code people can understand and modify safely.
- Tests give you freedom. With a good test suite, you can experiment fearlessly.
- Small steps compound. Each refactor pays off later when changes take minutes instead of days.
- Design is not static. A good design evolves as requirements change, and that is a good thing.
Favorite Line
"When you find something confusing or repetitive, clean it up. Leave the code a little better than you found it."
This mindset, small improvements over time, is the essence of craftsmanship.
Practical Refactoring Rules (with Java Examples)
Reading theory is good. Seeing it in action is better. Here are a few core refactoring techniques from Fowler's book, rewritten in Java for modern developers.
1. Extract Method
Goal: simplify long methods by breaking them into smaller, named parts.
Before:
void printOwing() {
printBanner();
double outstanding = 0.0;
for (Order order : orders) {
outstanding += order.getAmount();
}
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
After:
void printOwing() {
printBanner();
double outstanding = calculateOutstanding();
printDetails(outstanding);
}
private double calculateOutstanding() {
return orders.stream().mapToDouble(Order::getAmount).sum();
}
private void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
Each method now has one purpose and a clear name, making the code easier to read and test.
2. Replace Conditional with Polymorphism
Goal: replace complex switch or if-else statements with subclasses.
Before:
double getPayAmount(Employee emp) {
switch (emp.getType()) {
case ENGINEER:
return emp.getMonthlySalary();
case MANAGER:
return emp.getMonthlySalary() + emp.getBonus();
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
default:
throw new IllegalArgumentException("Unknown type");
}
}
After:
abstract class Employee {
abstract double getPayAmount();
}
class Engineer extends Employee {
double getPayAmount() { return monthlySalary; }
}
class Manager extends Employee {
double getPayAmount() { return monthlySalary + bonus; }
}
class Salesman extends Employee {
double getPayAmount() { return monthlySalary + commission; }
}
Adding a new type no longer touches existing code, which is the Open/Closed Principle in action.
3. Introduce Parameter Object
Goal: group related parameters into one domain object.
Before:
double getDistance(double speed, double time, double acceleration) {
return speed * time + 0.5 * acceleration * time * time;
}
After:
class Motion {
double speed;
double time;
double acceleration;
}
double getDistance(Motion m) {
return m.speed * m.time + 0.5 * m.acceleration * m.time * m.time;
}
Grouping related values reduces duplication and clarifies intent.
4. Replace Magic Number with Named Constant
Goal: give meaning to unexplained constants.
Before:
if (radius > 3.14159) throw new IllegalArgumentException();
After:
private static final double MAX_RADIUS = Math.PI;
if (radius > MAX_RADIUS) throw new IllegalArgumentException();
Names make intent visible and prevent misunderstandings.
5. Rename Method or Variable
Before:
int getThem() {
int sum = 0;
for (Order o : orders) {
if (o.isSpecial()) sum += o.getValue();
}
return sum;
}
After:
int calculateTotalForSpecialOrders() {
return orders.stream()
.filter(Order::isSpecial)
.mapToInt(Order::getValue)
.sum();
}
The name now explains what the method does, not how it works.
6. Decompose Conditional
Goal: split complicated if logic into readable helper methods.
Before:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
} else {
charge = quantity * summerRate;
}
After:
if (isWinter(date)) {
charge = winterCharge(quantity);
} else {
charge = summerCharge(quantity);
}
private boolean isWinter(Date date) {
return date.before(SUMMER_START) || date.after(SUMMER_END);
}
private double winterCharge(int quantity) {
return quantity * winterRate + winterServiceCharge;
}
private double summerCharge(int quantity) {
return quantity * summerRate;
}
The code now reads like plain English.
Core Principles Recap
| Principle | Description |
|---|---|
| Small steps | Refactor in tiny, verifiable increments. |
| Tests | Refactor only when behavior is protected by tests. |
| Readable over clever | Code that is easy to understand is easy to improve. |
| Single responsibility | One method, one reason to change. |
| Behavior preservation | Refactoring changes structure, not behavior. |
Final Reflection
Refactoring taught me that clean code is not a luxury, it is a discipline. It is how we keep systems alive, understandable, and adaptable in a changing world.
The book is not just about patterns or rules. It is about respect for the code and for the humans who will live with it.
