Claude Code excels at refactoring because it can read the entire codebase, find all call sites, update every reference, and verify tests still pass — all in one flow. The engineer’s job is to decide what to refactor and verify the result. Claude handles the tedious, error-prone parts.
The Golden Rule
Always have tests before refactoring. If the code does not have tests, write them first:
Read lib/report_generator.rb and write characterization tests that
capture its current behavior. Don't change the implementation -- just
lock down what it does today so I can refactor safely.
Once tests pass, THEN refactor. If any test fails after refactoring, you changed behavior — and that is a bug, not a refactoring.
The Safe Refactoring Workflow
1. Ensure tests exist (write characterization tests if needed)
2. Make ONE refactoring move
3. Review the diff
4. Run the tests
5. Commit
6. Repeat
One move at a time. Commit after each. If anything breaks, you can revert just the last change.
Step 1: Ensure Tests Exist
What's the test coverage for this file? List the tested and
untested behaviors.
If coverage is low:
Write characterization tests that capture the current behavior of
[method]. Don't clean up the code -- just lock down what it does.
Step 2: Make One Refactoring Move
Apply Extract Method to [specific code]. Keep all tests passing.
Step 3: Review with /diff
/diff
Verify that Claude changed only what you asked for.
Step 4: Run Tests
Run the tests to verify nothing broke.
Step 5: Commit
Commit with message: "refactor: extract validation helpers from register method"
Step 6: Repeat
Next move. One at a time. Commit after each.
Common Refactoring Patterns
Pattern 1: Extract Method
When to use: A method is too long, does multiple things, or has deeply nested logic.
The generate_report method in report_generator.rb is 180 lines long.
Extract the data processing logic for each report type into separate
private methods. Keep the main method as a dispatcher that calls the
right one. Don't change any behavior -- all existing tests must pass.
After Claude makes the changes, verify:
- The main method is now short and readable
- Each extracted method has a clear name that describes what it does
- No logic was accidentally changed during extraction
- Tests still pass
Iterative extraction — don’t try to refactor everything at once:
Now extract the formatting logic from the daily_summary_data method
into a separate TextFormatter class. Keep the same output.
Test after each step. If something breaks, you know exactly which change caused it.
Pattern 2: Replace Conditional with Polymorphism
When to use: Long if/elsif/else chains or switch statements that choose behavior based on a type.
Identify the smell first:
Read this file and identify any long conditional chains that could be
replaced with polymorphism or a strategy pattern. Show me which
conditions would become which classes.
Claude might identify:
if format == "text" -> TextFormatter
elsif format == "csv" -> CsvFormatter
elsif format == "html" -> HtmlFormatter
Then refactor:
Replace the format conditional in generate_report with a strategy
pattern. Create TextFormatter, CsvFormatter, and HtmlFormatter classes
that each implement a format(data) method. The output must be identical
to the current implementation.
The phrase “output must be identical” is critical. Without it, Claude might “improve” the HTML output or change CSV delimiters. You want a pure refactoring — same behavior, better structure.
Pattern 3: Introduce Parameter Object
When to use: A method takes too many parameters, or the same group of parameters appears in multiple methods.
Find methods in this codebase that take more than 4 parameters.
For each one, suggest a parameter object that would group related
parameters together.
Then:
The processOrder method takes (userId, productId, quantity, price,
discountCode, shippingAddress, billingAddress). Create an OrderRequest
class that groups these, and update processOrder to accept it.
Update all callers.
Claude updates all the callers — this is where AI shines. Finding and updating every call site is tedious for humans but trivial for Claude.
Pattern 4: Simplify Nested Conditionals
When to use: Code has 3+ levels of nesting, making it hard to follow the logic.
Before:
public void process(Order order) {
if (order != null) {
if (order.isValid()) {
if (order.hasStock()) {
// actual logic buried 3 levels deep
} else {
throw new OutOfStockException();
}
} else {
throw new InvalidOrderException();
}
} else {
throw new IllegalArgumentException("order is null");
}
}
The prompt:
Flatten the nested conditionals in the process method using guard
clauses (early returns). Keep the same behavior -- all error cases
should still throw the same exceptions.
After:
public void process(Order order) {
if (order == null) throw new IllegalArgumentException("order is null");
if (!order.isValid()) throw new InvalidOrderException();
if (!order.hasStock()) throw new OutOfStockException();
// actual logic at top level, easy to read
}
Pattern 5: Remove Dead Code
When to use: You suspect there is code that is no longer called.
Search the codebase for any methods in UserService.java that are
never called by any other file. List them with their line numbers.
Then:
Remove the unused methods you identified. Verify nothing references
them by checking imports and test files too.
Caution: Claude might miss reflection-based calls or dynamic dispatch. If the code uses runtime class loading:
Some of these methods might be called via reflection. Only remove
methods that are clearly dead -- skip anything that looks like it
could be a plugin hook or callback.
Pattern 6: Extract Class (Split God Objects)
When to use: A class has too many responsibilities — it handles data access, business logic, and formatting all in one place.
Get a proposal first:
This ReportGenerator class handles data processing, score calculation,
and output formatting for 3 report types and 3 formats. That's too
many responsibilities. Suggest how to split it into focused classes.
Don't make any changes yet -- just show me the proposed structure.
Review the proposal, then implement incrementally:
That plan looks good. Start with extracting the formatters into
separate classes. Do one class at a time and run tests after each.
Complete Walkthrough: Refactoring Before Building a Feature
Scenario: You need to add BOGO (buy-one-get-one) discounts, but the discount logic is tangled into a 400-line method.
Step 1: Assess the Current State
Read @src/OrderProcessor.java. I need to add a new discount type
(buy-one-get-one), but this class is too tangled. What's the minimum
refactoring needed to make it safe to add a new discount type?
Don't suggest a full rewrite -- just the targeted extraction.
Step 2: Refactor First
Extract the discount logic (lines 80-115) into a new DiscountEngine
class. The OrderProcessor should call DiscountEngine.calculate().
Keep behavior identical -- no new features yet. Run all existing tests.
Step 3: Verify the Refactor
Run the full test suite. Show me the test results and confirm
nothing changed in behavior.
Step 4: NOW Add the Feature
Now add BOGO (buy-one-get-one-free) support to the DiscountEngine.
The BOGO discount gives the cheapest item free when buying 2+ of
the same product. Write tests for it first, then implement.
Why this sequence matters:
- Step 2 is a pure refactor — behavior does not change, tests still pass
- Step 4 adds new behavior to clean code — much lower risk
- If something breaks, you know exactly which step caused it
Common Refactoring Prompts
| What You Want | Prompt |
|---|---|
| Make code readable | ”Break this 80-line method into smaller methods with descriptive names” |
| Remove duplication | ”These three methods have similar logic. Extract the shared parts into a helper” |
| Simplify flow | ”Flatten these nested ifs into guard clauses with early returns” |
| Fix naming | ”Rename variables and methods in this class to better describe what they do” |
| Separate concerns | ”Split this class into a data-processing class and a formatting class” |
| Replace magic values | ”Replace the magic numbers 90, 75, and 60 with named constants” |
What Claude Gets Wrong (and How to Handle It)
Over-Refactoring
Claude sometimes refactors more than you asked. If you said “extract this method,” Claude might also rename variables, reorder imports, and add type annotations.
Fix: Be explicit: “Only extract the method. Don’t change anything else.”
Changing Behavior During Refactoring
Sometimes Claude “fixes” a bug while refactoring. This is dangerous — you want the refactoring to be pure.
Fix: “Don’t fix any bugs during this refactoring. Keep the exact same behavior, even if you see issues. We’ll fix bugs separately.”
Creating Unnecessary Abstractions
Claude might introduce interfaces, abstract classes, or generics that add complexity without value.
Fix: “Keep it simple. Don’t add abstractions unless there are at least 2 concrete implementations that need them.”
Safety Practices Summary
| Practice | Why It Matters |
|---|---|
| Tests before refactoring | You need a safety net to catch behavior changes |
| One move at a time | Small changes are easy to review and revert |
| Commit after each move | You can always get back to a working state |
| Review every diff | Claude may change more than you asked for |
| ”Keep behavior identical” | Prevents Claude from mixing refactoring with feature work |
| Run tests after every change | Catch regressions immediately, not 5 changes later |