Metadata Card
- Prerequisites: Chapter 4 (Testing Strategies)
- Estimated Time: 45 minutes
- Core Difficulty: Foundation-building
- Completion Milestone: Be able to recognize common code smells and apply refactoring techniques confidently
Your Progress
The forge is running. Tests pass. But when you open the machine panel, you see a tangle of wires — same wire running in three directions, redundant control levers, and a valve that's labeled "do not touch, works somehow."
In the City of Artisans, old machines carry the scars of haste: patches added on top of patches, shortcut workarounds that became permanent. The craftsman says: "Before adding a new gear, I clean out the old dust."
This is refactoring — restructuring code without changing its behavior. Your Task
Refactoring is the art of improving code structure while keeping its behavior identical. You change the internal organization, not the external interface. The safety net is your test suite. This chapter covers common code smells (signs it's time to refactor) and the corresponding refactoring techniques.
Chapter tiers
- Required reading: What refactoring is and isn't, code smells overview, Extract Method, Rename
- Selective: Move Method/Field, Replace Conditional with Polymorphism
- Advanced: Large refactoring strategies (Strangler Fig in code)
This chapter won't require you to master
- AST-level refactoring tools
- Architecture-level refactoring (that's Chapters 11-13)
Breaking Ground · Tracing the Source
Behold the classic "god function":
public void processOrder(String orderId, String userId, String item, int quantity,
double price, String address, String paymentMethod,
boolean isGift, String giftMessage, boolean expedite) {
// 200 lines of:
// - validation
// - payment processing
// - inventory update
// - shipping calculation
// - notification
// - everything mixed together
}This function knows everything, does everything, and nothing can be reused. A simple fix in "notification" risks breaking "payment processing." One developer looks at this, closes the file, and opens a job board.
First Layer: Recognizing Code Smells
Code smells are surface indicators that usually correspond to deeper design problems.
Mysterious Name: The function is called processData. What data? What processing? Rename it to what it actually does.
Duplicated Code:
// In class A:
if (player.getLevel() >= 5) {
tournament.register(player);
}
// In class B:
if (player.getLevel() >= 5) {
rewardService.grantBonus(player);
}Extract to a method boolean canParticipate(Player player).
Long Method: A method longer than 10-20 lines. Extract smaller methods with descriptive names.
Long Parameter List: Methods with 5+ parameters. Introduce Parameter Object.
Primitive Obsession: Using int for Money, String for PhoneNumber, etc. Replace with value objects.
Feature Envy: A method in class A that spends most of its time using data from class B. Move it to class B.
Shotgun Surgery: A change causes you to modify many classes. Consolidate the scattered logic.
Switch Statements (that appear in multiple places): The same switch on the same type shows up in different methods. Replace with polymorphism.
Temporary Field: An object has a field that's only set in certain code paths. Extract the conditional behavior into its own class.
Inappropriate Intimacy: Two classes know too much about each other's internals. Break the connection.
Divergent Change: A class changes for different reasons. Extract each reason into its own class.
Second Layer: Refactoring Safely — The Workflow
- Ensure you have tests covering the code you're refactoring
- Make one change at a time — compile, test, verify green
- When a test fails, undo the last change and try again
- Commit frequently — each refactoring step should be a clean commit
The rule: refactoring does NOT add features. If you're fixing a bug while refactoring, you're doing it wrong. Separate "change structure" from "change behavior."
Third Layer: Key Refactoring Techniques
Extract Method — Turn a code block into a method with a descriptive name:
// Before
void printPlayerInfo(Player p) {
System.out.println("Name: " + p.getName());
System.out.println("Level: " + p.getLevel());
System.out.println("Score: " + p.getScore());
}
// After
void printPlayerInfo(Player p) {
printHeader();
printPlayerDetails(p);
}
private void printHeader() {
System.out.println("=== Player Info ===");
}
private void printPlayerDetails(Player p) {
System.out.println("Name: " + p.getName());
System.out.println("Level: " + p.getLevel());
System.out.println("Score: " + p.getScore());
}Introduce Parameter Object — Group related parameters:
// Before
public void createTournament(String name, int minLevel, int maxPlayers,
String startDate, String endDate) { ... }
// After
public record TournamentConfig(
String name,
LevelRange levelRange,
int maxPlayers,
DateRange eventPeriod
) {}
public void createTournament(TournamentConfig config) { ... }Replace Conditional with Polymorphism — Remove "if type == X" with subclasses:
// Before
public int calculatePrize(String matchType, int baseScore) {
if ("normal".equals(matchType)) return baseScore;
else if ("ranked".equals(matchType)) return baseScore * 2;
else if ("championship".equals(matchType)) return baseScore * 5;
else throw new IllegalArgumentException();
}
// After
public interface PrizeStrategy {
int calculate(int baseScore);
}
public class NormalPrize implements PrizeStrategy {
public int calculate(int baseScore) { return baseScore; }
}
public class RankedPrize implements PrizeStrategy {
public int calculate(int baseScore) { return baseScore * 2; }
}Fourth Layer: When NOT to Refactor
- Code that works and won't change again — if it's stable, leave it
- Code being rewritten anyway — don't polish something about to be replaced
- During a deadline — refactoring carries risk; don't do it under time pressure
- Without tests — you need a safety net. If tests don't exist, write them first
Common Pitfalls
Pitfall 1: Refactoring and adding features at the same time. "I'll clean up this method and add the new flag parameter while I'm at it." Now if something breaks, is it the refactoring or the feature? Never mix.
Pitfall 2: Big bang refactoring. "I'll rewrite this module over the weekend." If it takes more than a few hours, it's not refactoring—it's rewriting. Refactoring is a series of small, safe steps.
Pitfall 3: Over-refactoring. A simple 3-line method gets extracted into an interface + 4 implementation classes + a factory. Refactoring has diminishing returns — stop when the code is "good enough."
Pitfall 4: Not using your IDE's refactoring tools. IntelliJ's "Extract Method" and "Rename" are safe (they handle all references). Manual refactoring is error-prone.
Passing Challenges
- Warm up: Find a method in your codebase longer than 30 lines. Apply Extract Method to split it into smaller pieces. Run the tests — still green?
- Challenge: Find a Switch statement or if-else chain that appears in multiple places. Apply Replace Conditional with Polymorphism.
- Observe: Use your IDE to check the "Lines Changed" count in your last commit. How much of that was adding features vs refactoring? Aim for 20-30% refactoring healthy codebases.
Traveler's Notes
Refactoring is maintenance without new features. It's the craftsman cleaning the forge after each job, so the next job starts with clean equipment. Code smells tell you when to clean, and techniques like Extract Method, Rename, and Replace Conditional with Polymorphism tell you how. The safety net is your test suite — without it, don't clean.
Preview of Next Chapter
Your code is clean, tested, and well-structured. But how do you get it from your machine to production? And how do you ensure the process is repeatable and automated? Next: CI/CD and DevOps.