refactoring
Refactoring Skill
Structured approach to technical debt remediation. Improve code structure while maintaining behavior through incremental, safe changes.
When to Use
| Trigger | Description |
|---|---|
| Guardrail Violations | Functions >50 lines, files >300 lines |
| Complexity Threshold | Cyclomatic complexity >10 |
| Code Duplication | Same logic in 3+ places |
| Quarterly Review | Regular technical debt assessment |
| Pre-Feature | Before adding features to messy code |
| Post-Incident | After bugs caused by confusing code |
Refactoring Principles
Golden Rules
- Behavior Preservation: Output unchanged after refactoring
- Small Steps: One change at a time
- Test First: Ensure tests exist before changing
- Incremental: Commit after each successful change
- Reversible: Easy to roll back if issues arise
When NOT to Refactor
- During active incident response
- Without test coverage
- When deadline is imminent
- If no clear improvement goal
Prerequisites
Before starting:
- Code compiles and all tests pass
- Test coverage exists for target code (>60%)
- Clear refactoring goal defined
- Time allocated (refactoring always takes longer)
- Git working directory is clean
Refactoring Process
Phase 1: Identify Candidates
↓
Phase 2: Impact Analysis
↓
Phase 3: Plan Refactoring
↓
Phase 4: Add Safety Net
↓
Phase 5: Execute Incrementally
↓
Phase 6: Validate & Document
Phase 1: Identify Candidates
1.1 Automated Detection
Long Functions (>50 lines):
# Find functions over 50 lines (varies by language)
# Example: use linter rules or IDE features
Long Files (>300 lines):
# Find files over 300 lines
find src -name "*.ts" -exec wc -l {} \; | awk '$1 > 300'
find src -name "*.py" -exec wc -l {} \; | awk '$1 > 300'
High Complexity:
# Use complexity analyzers
npx escomplex src/**/*.ts
python -m mccabe src/**/*.py
1.2 Code Smells
| Smell | Signs | Priority |
|---|---|---|
| Long Method | >50 lines, multiple responsibilities | High |
| Large Class | >300 lines, low cohesion | High |
| Feature Envy | Method uses other class more than own | Medium |
| Data Clumps | Same group of variables together | Medium |
| Primitive Obsession | Overuse of primitives vs objects | Medium |
| Switch Statements | Large switch blocks | Low |
| Speculative Generality | Unused abstractions | Low |
1.3 Candidate List
Document identified candidates:
## Refactoring Candidates
### High Priority
| File | Issue | Lines | Impact |
|------|-------|-------|--------|
| src/services/order.ts | Long method: processOrder | 120 → 50 | High |
| src/api/users.ts | Large file | 450 → 200 | High |
| src/utils/validation.ts | Duplication | N/A | Medium |
### Medium Priority
| File | Issue | Lines | Impact |
|------|-------|-------|--------|
| src/models/product.ts | Feature envy | N/A | Medium |
| src/services/email.ts | Complex conditionals | N/A | Medium |
Phase 2: Impact Analysis
2.1 Dependency Mapping
For each candidate, identify:
Target: src/services/order.ts
Dependencies (imports from):
- src/models/order.ts
- src/services/inventory.ts
- src/services/payment.ts
Dependents (imported by):
- src/api/orders.ts
- src/workers/orderProcessor.ts
- tests/services/order.test.ts
Impact radius: 5 files
Risk level: Medium
2.2 Risk Assessment
| Factor | Low | Medium | High |
|---|---|---|---|
| Test coverage | >80% | 60-80% | <60% |
| Dependents | <3 | 3-10 | >10 |
| Business criticality | Non-critical | Important | Critical path |
| Complexity | Simple rename | Extract method | Architecture change |
2.3 Risk Matrix
Low Effort High Effort
┌────────────┬────────────┐
High Value │ DO FIRST │ PLAN │
│ │ CAREFULLY │
├────────────┼────────────┤
Low Value │ QUICK WIN │ AVOID │
│ │ │
└────────────┴────────────┘
Phase 3: Plan Refactoring
3.1 Choose Refactoring Technique
| Smell | Technique | Description |
|---|---|---|
| Long method | Extract Method | Pull out logical chunks |
| Large class | Extract Class | Split by responsibility |
| Feature envy | Move Method | Move to appropriate class |
| Data clumps | Extract Parameter Object | Group related params |
| Duplicated code | Extract to shared function | DRY principle |
| Complex conditional | Replace with polymorphism | Strategy pattern |
| Long parameter list | Introduce Parameter Object | Create wrapper class |
3.2 Step-by-Step Plan
Example: Extract Method from Long Function
## Refactoring Plan: processOrder()
### Goal
Reduce processOrder() from 120 lines to <50 lines
### Steps
1. [ ] Add characterization tests for current behavior
2. [ ] Extract validateOrder() (lines 15-35)
3. [ ] Verify tests pass
4. [ ] Commit: "refactor: extract validateOrder"
5. [ ] Extract calculateTotals() (lines 40-65)
6. [ ] Verify tests pass
7. [ ] Commit: "refactor: extract calculateTotals"
8. [ ] Extract processPayment() (lines 70-95)
9. [ ] Verify tests pass
10. [ ] Commit: "refactor: extract processPayment"
11. [ ] Extract sendConfirmation() (lines 100-115)
12. [ ] Verify tests pass
13. [ ] Commit: "refactor: extract sendConfirmation"
14. [ ] Final cleanup and documentation
15. [ ] Commit: "refactor: cleanup processOrder"
### Expected Result
- processOrder(): 120 → 25 lines
- New methods: 4
- Total lines: +10 (net increase for clarity)
Phase 4: Add Safety Net
4.1 Characterization Tests
Before refactoring, add tests that capture current behavior:
describe('processOrder - characterization tests', () => {
it('should match current behavior for valid order', () => {
const order = createValidOrder();
const result = processOrder(order);
// Capture current output exactly
expect(result).toMatchSnapshot();
});
it('should match current behavior for edge cases', () => {
const edgeCases = [
createEmptyOrder(),
createMaxItemOrder(),
createDiscountedOrder(),
];
edgeCases.forEach(order => {
expect(processOrder(order)).toMatchSnapshot();
});
});
});
4.2 Coverage Check
Ensure adequate coverage before proceeding:
# Check coverage for target file
npm test -- --coverage --collectCoverageFrom="src/services/order.ts"
Minimum Coverage: 60% before refactoring (prefer >80%)
4.3 Checkpoint Commit
Create a checkpoint before starting:
git add .
git commit -m "chore: checkpoint before refactoring processOrder"
Phase 5: Execute Incrementally
5.1 One Change at a Time
Bad: Multiple changes in one commit
# Too much at once - hard to debug if tests fail
git commit -m "refactor: refactor entire order module"
Good: Atomic changes
git commit -m "refactor(order): extract validateOrder method"
git commit -m "refactor(order): extract calculateTotals method"
git commit -m "refactor(order): extract processPayment method"
5.2 Red-Green-Refactor
For each change:
1. Run tests → PASS (green)
2. Make one refactoring change
3. Run tests → Should still PASS (green)
4. If FAIL (red) → Revert and try smaller change
5. If PASS → Commit
6. Repeat
5.3 Common Refactoring Patterns
See references/process.md for detailed code examples including:
- Extract Method pattern
- Extract Class pattern
- Replace Conditional with Polymorphism pattern
Phase 6: Validate & Document
6.1 Final Validation
- All tests pass
- Coverage maintained or improved
- No new linter warnings
- Performance unchanged (or improved)
- Behavior identical (check snapshots)
6.2 Document Changes
Update patterns.md (if new pattern emerged):
## Order Processing Pattern
Orders are processed through composable steps:
1. validateOrder() - Input validation
2. calculateTotals() - Price calculations
3. processPayment() - Payment handling
4. sendConfirmation() - Notifications
Create memory entry (if significant):
# Refactoring: Order Processing
**Date**: 2025-01-15
**Files**: src/services/order.ts
## Before
- Single 120-line function
- Difficult to test
- Hard to modify
## After
- 5 focused functions (<30 lines each)
- Easier to test
- Clear responsibilities
## Lessons
- Extract Method is low-risk, high-reward
- Characterization tests prevented regressions
6.3 Final Commit
git add .
git commit -m "refactor(order): complete processOrder decomposition
- Extract validateOrder (20 lines)
- Extract calculateTotals (25 lines)
- Extract processPayment (30 lines)
- Extract sendConfirmation (20 lines)
- Main function now 25 lines (was 120)
No behavior changes. All tests passing."
Refactoring Catalog
Quick Reference
| Refactoring | When | Effort | Risk |
|---|---|---|---|
| Rename | Unclear naming | Low | Low |
| Extract Method | Long function | Low | Low |
| Extract Variable | Complex expression | Low | Low |
| Inline | Over-abstraction | Low | Low |
| Extract Class | Large class | Medium | Medium |
| Move Method | Feature envy | Medium | Medium |
| Extract Parameter Object | Long param list | Medium | Low |
| Replace Conditional with Polymorphism | Complex switch | High | Medium |
| Replace Inheritance with Composition | Rigid hierarchy | High | High |
IDE Support
Most refactorings have IDE shortcuts:
| Action | VS Code | JetBrains |
|---|---|---|
| Rename | F2 | Shift+F6 |
| Extract Method | Ctrl+Shift+R | Ctrl+Alt+M |
| Extract Variable | Ctrl+Shift+R | Ctrl+Alt+V |
| Move | Drag or F2 | F6 |
| Inline | N/A | Ctrl+Alt+N |
Checklist
Before Refactoring
- Clear goal defined
- Tests exist (>60% coverage)
- Impact analysis complete
- Step-by-step plan created
- Checkpoint committed
During Refactoring
- One change at a time
- Tests run after each change
- Commit after each successful change
- No behavior changes introduced
After Refactoring
- All tests pass
- Coverage maintained
- Code cleaner (measurable)
- Documentation updated
- Patterns documented (if applicable)
Related Resources
- Workflows: code-review.md, testing-strategy.md, troubleshooting.md
- Detailed Examples: See
references/process.mdfor code patterns - Refactoring Book: Martin Fowler's "Refactoring: Improving the Design of Existing Code"