Why Most Developers Fear Refactoring
Refactoring fails when there are no tests, unclear scope, or both.
The solution isn't less refactoring -- it's a disciplined approach.
Here's the AI-assisted refactoring workflow that actually works.
Step 1: Characterize Before You Change
Before touching anything, document what the code currently does:
Claude Code prompt:
"Read app/lib/pricing.ts and write a description of what each function does,
including edge cases and assumptions. Don't change any code.
This is documentation for a refactoring I'm about to do."
This catches hidden assumptions that break when you refactor.
Step 2: Generate Tests First
"For the function calculatePrice() in app/lib/pricing.ts,
generate comprehensive tests that cover:
- Normal cases
- Edge cases (zero, negative, maximum values)
- Error conditions
- All the branches I can see in the code
Use Jest. Make the tests pass with the CURRENT implementation.
We'll refactor after the tests are green."
Step 3: Refactor in Small Steps
// Instead of rewriting everything at once:
// Step A: Extract constants (no logic change)
const BASE_RATE = 0.0299
const ENTERPRISE_MULTIPLIER = 0.85
// Step B: Extract helper function (no logic change)
function applyVolumeDiscount(subtotal: number, quantity: number): number {
if (quantity >= 100) return subtotal * 0.9
if (quantity >= 50) return subtotal * 0.95
return subtotal
}
// Step C: Simplify main function using helpers
// Run tests after EACH step -- never accumulate untested changes
The Claude Code Refactoring Prompt
"Refactor the following function. Requirements:
- Do NOT change any behavior -- tests must still pass
- Goals: reduce nesting, extract named helpers, improve readability
- After each logical change, pause and tell me what you changed
- If you're uncertain about behavior, ask instead of assuming
[paste function]"
Detecting Regressions Automatically
# Before refactoring: capture baseline
npm test -- --coverage > test-baseline.txt
# After each refactor step:
npm test
# If any test fails, stop and revert
# Compare coverage:
npm test -- --coverage
# Coverage should not decrease
Refactoring Patterns That Pay Off
// 1. Early returns (eliminate nesting)
// Before:
function process(user: User) {
if (user) {
if (user.isActive) {
if (user.plan === 'pro') {
// 10 lines of logic
}
}
}
}
// After:
function process(user: User) {
if (!user) return
if (!user.isActive) return
if (user.plan !== 'pro') return
// 10 lines of logic -- unindented, clear
}
// 2. Replace magic numbers with named constants
// Before: if (attempts > 3)
// After: if (attempts > MAX_LOGIN_ATTEMPTS)
// 3. Extract complex conditions
// Before: if (user.plan === 'pro' && !user.suspended && user.credits > 0)
const canUseFeature = user.plan === 'pro' && !user.suspended && user.credits > 0
// After: if (canUseFeature)
// 4. Replace switch with map (when logic is data)
// Before: switch (plan) { case 'free': return 10; case 'pro': return 100; }
const PLAN_LIMITS: Record<string, number> = { free: 10, pro: 100, enterprise: 10000 }
// After: return PLAN_LIMITS[plan] ?? 0
When to Stop Refactoring
Stop when:
- Tests pass
- Code is readable on first read
- No function is > 30 lines
- No nesting deeper than 3 levels
Don't keep refactoring because it 'could be cleaner'.
Every change is a risk. Stop when it's good enough.
The /refactor Skill
The Ship Fast Skill Pack includes /refactor -- runs this exact workflow in Claude Code: characterize, test, refactor step-by-step, verify.
$49 one-time at whoffagents.com
Top comments (0)