| name | refactoring-workflow |
| description | Universal code refactoring workflow. Use when improving code structure, reducing duplication, or simplifying complex functions. Covers test-driven refactoring, incremental changes, extract function patterns, and rollback procedures. Framework-agnostic - works for Python, JavaScript, TypeScript, Rust, Go, or any language. |
Refactoring Workflow
Step-by-step guide for safely refactoring code without breaking functionality in any codebase.
When to Use This Skill
Use when:
- User asks "How do I refactor this code?" or "Can I simplify this?"
- Reducing code duplication (DRY violations)
- Simplifying complex functions (>50 lines or cyclomatic complexity >10)
- Extracting reusable logic
- Improving code structure or naming
- Addressing technical debt
- User mentions: "refactor", "clean up", "simplify", "reduce duplication"
Do NOT use for:
- Style preferences without measurable benefit
- Just before deadlines
- Code without existing tests
- Changes that alter behavior (that's feature work, not refactoring)
Core Principles
- Test-Driven: Tests must exist BEFORE refactoring
- Incremental: One change at a time, test after each
- Behavioral Preservation: Same inputs → same outputs
- Reversible: Each step committed separately for rollback
- Measurable: Complexity reduced, duplication eliminated
Prerequisites (CRITICAL)
NEVER refactor without tests!
npm test -- --coverage
python -m pytest --cov
cargo test
go test -cover ./...
dotnet test /p:CollectCoverage=true
Minimum requirements:
Phase 1: Preparation
Step 1: Document Current Behavior
Create temporary documentation:
# Refactoring: [Module/Function Name]
## Current Behavior
- Input: X
- Output: Y
- Side effects: Z
- Edge cases: A, B, C
## Existing Tests
- test_case_1: normal flow
- test_case_2: error handling
- test_case_3: edge case
## Success Criteria
After refactor: all tests pass, same behavior
Step 2: Create Safety Backup
git checkout -b backup-before-refactor
git checkout -b refactor/improve-module-x
Step 3: Create Refactoring Checklist
## Refactoring Checklist
### Before
- [ ] All existing tests pass
- [ ] Coverage documented
- [ ] Behavior documented
- [ ] Backup branch created
### During
- [ ] ONE change at a time
- [ ] Run tests after EACH change
- [ ] Commit after EACH success
### After
- [ ] All tests still pass
- [ ] No errors in test output
- [ ] Manual testing complete
- [ ] Performance unchanged/better
- [ ] Documentation updated
Phase 2: Refactoring Patterns
Pattern A: Extract Function (Reduce Complexity)
When: Function >50 lines or cyclomatic complexity >10
Before (TypeScript example):
async function processOrder(order: Order) {
if (!order.customerId) throw new Error('Customer required')
if (!order.items || order.items.length === 0) throw new Error('Items required')
if (order.total < 0) throw new Error('Invalid total')
let tax = 0
for (const item of order.items) {
const rate = item.taxable ? 0.08 : 0
tax += item.price * item.quantity * rate
}
const finalOrder = { ...order, tax, grandTotal: order.total + tax }
return await database.orders.create(finalOrder)
}
After:
async function processOrder(order: Order) {
validateOrder(order)
const tax = calculateTax(order.items)
return await saveOrder(order, tax)
}
function validateOrder(order: Order) {
if (!order.customerId) throw new Error('Customer required')
if (!order.items || order.items.length === 0) throw new Error('Items required')
if (order.total < 0) throw new Error('Invalid total')
}
function calculateTax(items: OrderItem[]): number {
return items.reduce((sum, item) => {
const rate = item.taxable ? 0.08 : 0
return sum + (item.price * item.quantity * rate)
}, 0)
}
async function saveOrder(order: Order, tax: number) {
const grandTotal = order.total + tax
return await database.orders.create({ ...order, tax, grandTotal })
}
Python equivalent:
def process_order(order):
if not order.customer_id:
raise ValueError('Customer required')
tax = sum(item.price * item.quantity * 0.08
for item in order.items if item.taxable)
return db.orders.create({**order, 'tax': tax})
def process_order(order):
validate_order(order)
tax = calculate_tax(order.items)
return save_order(order, tax)
def validate_order(order):
if not order.customer_id:
raise ValueError('Customer required')
if not order.items:
raise ValueError('Items required')
def calculate_tax(items):
return sum(item.price * item.quantity * 0.08
for item in items if item.taxable)
def save_order(order, tax):
return db.orders.create({**order, 'tax': tax})
Steps:
- Extract ONE function at a time
- Run tests after each extraction
- Commit each success
- Add tests for new functions
Pattern B: Extract Utility (Eliminate Duplication)
When: Same logic duplicated across 3+ locations
Before (JavaScript example - 5 files with this code):
const imageUrl = data?.image?.url || data?.image?.file_url || '/placeholder.png'
After:
export function getImageUrl(imageData, fallback = '/placeholder.png') {
if (!imageData) return fallback
return imageData.url || imageData.file_url || fallback
}
const imageUrl = getImageUrl(data?.image)
Rust equivalent:
let image_url = image_data
.and_then(|img| img.url.or(img.file_url))
.unwrap_or_else(|| "/placeholder.png".to_string());
pub fn get_image_url(image_data: Option<&ImageData>) -> String {
image_data
.and_then(|img| img.url.as_ref().or(img.file_url.as_ref()))
.cloned()
.unwrap_or_else(|| "/placeholder.png".to_string())
}
let image_url = get_image_url(image_data.as_ref());
Steps:
- Create utility function
- Write comprehensive tests for utility
- Replace in ONE location
- Test
- Commit
- Repeat for each remaining location
Pattern C: Replace Magic Numbers/Strings with Constants
When: Same literal value appears 5+ times
Before (Python example):
if user.status == "active":
process_user(user)
if account.status == "active":
charge(account)
After:
USER_STATUS_ACTIVE = "active"
USER_STATUS_INACTIVE = "inactive"
if user.status == USER_STATUS_ACTIVE:
process_user(user)
if account.status == USER_STATUS_ACTIVE:
charge(account)
Go equivalent:
if user.Status == "active" {
processUser(user)
}
const (
StatusActive = "active"
StatusInactive = "inactive"
)
if user.Status == constants.StatusActive {
processUser(user)
}
Pattern D: Simplify Complex Conditionals
When: Nested conditions >3 levels deep
Before:
if (user) {
if (user.isActive) {
if (user.subscription) {
if (user.subscription.plan === 'premium') {
return true
}
}
}
}
return false
After:
function hasPremiumSubscription(user: User | null): boolean {
if (!user) return false
if (!user.isActive) return false
if (!user.subscription) return false
return user.subscription.plan === 'premium'
}
function hasPremiumSubscription(user: User | null): boolean {
if (!user || !user.isActive || !user.subscription) {
return false
}
return user.subscription.plan === 'premium'
}
Phase 3: Incremental Execution
CRITICAL WORKFLOW: One change → Test → Commit
git checkout -b refactor/improve-module-x
npm test
python -m pytest
cargo test
go test ./...
git add .
git commit -m "refactor: extract validate_order function
- Moved validation logic from process_order
- All tests passing
- No behavior changes"
npm test
git commit -m "refactor: extract calculate_tax"
Testing After EVERY Change
npm test
pytest
cargo test
go test ./...
dotnet test
npm run type-check
mypy .
cargo check
npm run lint
pylint src/
cargo clippy
golangci-lint run
git reset --hard HEAD
Phase 4: Validation
Comprehensive Testing Checklist
Automated:
Manual:
Code Quality:
Performance Verification
npm run bench
python -m pytest --benchmark-only
cargo bench
go test -bench=.
Phase 5: Documentation
Update Project Docs
If new patterns introduced, update your project's documentation:
## Code Conventions
### Image URL Handling
Always use `getImageUrl()` from `utils/image`. Prevents silent failures from null/undefined.
**Example:**
\`\`\`javascript
const url = getImageUrl(data?.image) // ✅ Safe
const url = data?.image?.url // ❌ Doesn't handle fallback
\`\`\`
Update Changelog
### Session: Refactor Order Processing (Feb 3, 2026)
**Goal**: Eliminate complex nested logic in processOrder
**Changes**:
- Extracted `validateOrder()` function
- Extracted `calculateTax()` function
- Extracted `saveOrder()` function
- Reduced cyclomatic complexity from 12 to 3
**Impact**:
- Improved testability (each function tested separately)
- Reduced duplication by ~40 lines
- Easier to modify validation logic
**Validation**:
- ✅ All 164 tests pass
- ✅ No behavior changes
- ✅ Performance unchanged (benchmarked)
**Commits**: abc123, def456, ghi789
Anti-Patterns (DON'T DO THIS)
❌ Big Bang Refactor
git commit -m "refactor: everything"
git commit -m "refactor: extract validation"
git commit -m "refactor: extract calculation"
git commit -m "refactor: simplify main function"
❌ Refactor Without Tests
def refactored_function():
pass
def test_refactored_function():
assert refactored_function(input) == expected
def refactored_function():
pass
❌ Change Behavior
function validateOrder(order: Order) {
if (!order.customerId) throw new Error('Customer required')
if (!order.email) throw new Error('Email required')
}
function validateOrder(order: Order) {
if (!order.customerId) throw new Error('Customer required')
}
❌ Premature Optimization
❌ Refactor Under Pressure
// WRONG: "Production deploy tomorrow, let me refactor today!"
// CORRECT: Refactor when you have time to test properly
Common Scenarios
Scenario 1: Function Too Large (>50 lines)
Fix:
- Extract helper functions (one responsibility each)
- Extract utilities for reusable logic
- Use descriptive names
- Test each extracted function
Scenario 2: Duplicated Code (3+ places)
Fix:
- Identify common pattern
- Extract to utility function/module
- Write comprehensive tests
- Replace one by one
- Commit after each replacement
Scenario 3: Hard to Test
Fix:
- Identify external dependencies (DB, API, filesystem)
- Extract dependencies to parameters
- Make functions pure (same input → same output)
- Write tests with mocks/stubs
Scenario 4: Unclear Naming
Fix:
- Rename ONE identifier at a time
- Use IDE refactor (F2 in VS Code, Rename in IntelliJ)
- Run tests
- Commit
- Repeat for next name
Emergency Rollback
If refactoring breaks something:
git revert HEAD
git checkout backup-before-refactor
git checkout -b refactor/v2
git stash
npm test
git stash pop
git reset --hard origin/main
Success Metrics
Refactoring succeeds when:
✅ All tests pass (no behavior changes)
✅ Code more readable (clear improvement)
✅ Complexity reduced (fewer lines, simpler logic, lower cyclomatic complexity)
✅ Duplication removed (DRY)
✅ Test coverage maintained/improved
✅ Performance unchanged/better
✅ No regressions (manual testing confirms)
Language-Specific Testing Commands
npm test
npm test -- --coverage
npm run type-check
npm run lint
python -m pytest
python -m pytest --cov
mypy .
pylint src/
cargo test
cargo test --all-features
cargo check
cargo clippy
go test ./...
go test -cover ./...
go vet ./...
golangci-lint run
dotnet test
dotnet test /p:CollectCoverage=true
dotnet build
bundle exec rspec
bundle exec rubocop
mvn test
./gradlew test
When to Stop
Stop refactoring when:
- Tests start failing frequently (too aggressive)
- Code is "good enough" (perfect is enemy of done)
- Deadline approaching (commit what you have)
- No measurable benefit (diminishing returns)
- You're just tweaking style (not improving structure)
Related Skills
This skill is framework-agnostic. Patterns apply to JavaScript, TypeScript, Python, Rust, Go, Java, C#, Ruby, or any language.