Refactor Code Skill
Improve code quality without changing external behavior. Research-aware.
MANDATORY: Pre-Refactoring Checks
BEFORE refactoring any code, you MUST:
1. Read Relevant Documentation
README.md (project overview)
src/[domain]/@_[domain]-README.md (domain architecture)
CONTRIBUTING.md (code standards)
2. Understand Existing Patterns
Use Grep and SemanticSearch to find:
- How similar code is structured elsewhere in the codebase
- What patterns are already established
- ALL files that import/depend on the code being refactored
3. Map the Blast Radius
Before changing any function, class, or component:
rg "functionName" --type ts # find all callers
rg "import.*from.*module" --type ts # find all importers
List every file that will be affected by the change. If the blast radius is large (10+ files), consider a phased approach.
4. Research Modern Patterns (for non-trivial refactors)
If the refactoring introduces a new pattern, verify it's current best practice:
firecrawl:firecrawl_search
{
"query": "<framework> <pattern> best practice <current year>",
"limit": 5,
"sources": [{ "type": "web" }]
}
Scrape the most authoritative result:
firecrawl:firecrawl_scrape
{
"url": "<best-result-url>",
"formats": ["markdown"],
"onlyMainContent": true
}
This prevents refactoring FROM one outdated pattern TO another outdated pattern.
5. Verification Statement (REQUIRED)
Before refactoring, state:
"Pre-refactoring check:
- README/docs read: [list]
- Dependent files identified: [list files that import this code]
- Blast radius: [N files affected]
- Tests exist: [YES/NO — if NO, write tests first]
- Pattern verified: [YES via research / YES matches codebase / SKIP — trivial refactor]"
Refactoring Principles
- Behavior stays the same — tests pass before and after
- Small steps — one change at a time, verify after each
- Test frequently — run tests after each change
- Commit often — easy to revert if something breaks
- Research first — don't replace old patterns with other old patterns
Code Smells and Fixes
| Smell | Symptom | Solution |
|---|---|---|
| Long Function | >20 lines, does multiple things | Extract functions |
| Duplicate Code | Same logic in 2+ places | Extract shared function/hook |
| Magic Numbers | Unexplained literals | Named constants or config |
| Deep Nesting | 3+ levels of if/loops | Early returns, extract functions |
| Long Parameter List | >3 parameters | Object parameter with interface |
| Feature Envy | Function uses another module's data heavily | Move function to that module |
| God Object | One class/component does everything | Split by responsibility |
| Primitive Obsession | Using strings/numbers where a type would be safer | Create domain types |
| Shotgun Surgery | One change requires editing many files | Consolidate related logic |
| Dead Code | Unreachable or unused code | Delete it (git has history) |
Common Refactorings
Extract Function
Before:
function processOrder(order: Order) {
if (!order.items.length) throw new Error('Empty order');
if (!order.customer) throw new Error('No customer');
if (order.total < 0) throw new Error('Invalid total');
const subtotal = order.items.reduce((sum, i) => sum + i.price, 0);
const tax = subtotal * 0.1;
const total = subtotal + tax;
db.orders.insert({ ...order, total });
}
After:
function processOrder(order: Order) {
validateOrder(order);
const total = calculateTotal(order);
saveOrder({ ...order, total });
}
Flatten Nested Conditionals
Before:
function getDiscount(user: User, order: Order) {
if (user) {
if (user.isPremium) {
if (order.total > 100) {
return 0.2;
} else {
return 0.1;
}
}
}
return 0;
}
After:
function getDiscount(user: User | null, order: Order) {
if (!user) return 0;
if (user.isPremium && order.total > 100) return 0.2;
if (user.isPremium) return 0.1;
return 0;
}
Replace Magic Numbers
// Before
if (password.length < 8) { ... }
if (retries > 3) { ... }
const tax = amount * 0.1;
// After
const MIN_PASSWORD_LENGTH = 8;
const MAX_RETRIES = 3;
const TAX_RATE = 0.1;
Use Object Parameters
// Before — positional args are error-prone
function createUser(name: string, email: string, age: number, role: string) { ... }
// After — named, self-documenting, extensible
interface CreateUserParams {
name: string;
email: string;
age: number;
role: string;
}
function createUser(params: CreateUserParams) { ... }
Refactoring Process
1. Verify tests pass → 2. Make one small change → 3. Verify tests pass → 4. Commit → 5. Repeat
Commit Message Format
refactor(scope): description of structural change
- What was changed and why
- No behavior change
Refactoring Checklist
Before Starting
- Tests pass (if no tests, write them first)
- Understand current behavior
- All dependent files identified
- Pattern researched (if introducing new pattern)
- Specific smell identified
During Refactoring
- One change at a time
- Tests run after each change
- Working states committed
After Refactoring
- All tests still pass
- Code is more readable
- No behavior changes
- Performance not degraded
- All dependent files updated
- Types still correct (no new
anyor casts)
When NOT to Refactor
- No test coverage (write tests first)
- Don't understand the code yet (read and learn first)
- Code is being deleted soon
- The refactoring has no clear benefit
- Under deadline pressure (ship first, refactor in a follow-up)