Refactorer
Overview
The Refactorer skill provides a disciplined, step-by-step approach to improving the internal structure of code while preserving its observable behavior. It covers the most impactful refactoring patterns — including Extract Function, DRY, SOLID principles, and replacing conditionals with polymorphism — along with a decision framework for knowing when to refactor versus when to rewrite. Every refactoring step is backed by tests to ensure nothing breaks.
When to Use
- Code is difficult to read, understand, or explain to a colleague
- The same logic appears in two or more places (DRY violation)
- A function or class has grown too large and handles too many concerns
- Adding a new feature requires touching many unrelated parts of the codebase
- Technical debt has accumulated to the point of slowing down delivery
- Code review consistently raises the same structural feedback
When NOT to Use
- The code is throwaway/prototype quality and will be replaced soon
- You don't have tests and can't add them before refactoring (too risky)
- The behavior itself is wrong — fix the bug first, then refactor
- You are under a tight deadline — schedule refactoring as deliberate work
- You're tempted to rewrite in a new language or framework (that's not refactoring)
Quick Reference
| Pattern |
When to Apply |
| Extract Function |
Function body > 20 lines; logic block has a clear name |
| Extract Class |
Class has > 1 responsibility; fields cluster into subgroups |
| Rename |
Name is misleading, abbreviated, or doesn't match intent |
| DRY / Extract Common |
Same logic copied in 2+ places |
| Replace Magic Number |
Unexplained literals; use named constants |
| Guard Clause |
Deeply nested if/else; flip condition to return early |
| Replace Conditional with Polymorphism |
Long if/switch on type; open/closed principle |
| Introduce Parameter Object |
Function takes 4+ related params; group into a struct/dataclass |
| Move Method |
Method uses another class's data more than its own |
| Decompose Conditional |
Complex boolean expression; extract to named predicate function |
Instructions
Ensure test coverage before touching code
- Run the existing test suite. It must be green before you start.
- If coverage is low, write characterization tests (tests that capture current behavior) before refactoring.
- Commit the tests separately so the refactoring diff is clean.
Identify the smell — Choose the most impactful problem to fix first:
- God Class: one class that does everything
- Long Method: function spans more than 20–30 lines
- Duplicated Code: same or nearly same logic in multiple places
- Feature Envy: a method that uses another class's data extensively
- Primitive Obsession: using raw strings/ints where a domain type should exist
- Shotgun Surgery: one change requires edits across many files
Apply refactoring patterns incrementally
- Make one refactoring at a time. Run tests after each step.
- Never mix refactoring with behavior changes in the same commit.
- Use your IDE's automated refactoring tools (rename, extract, move) when available — they are safer than manual edits.
Refactor vs. Rewrite decision
- Refactor when: the core logic is correct, tests exist, and the issues are structural.
- Rewrite when: the approach is fundamentally wrong, the code can't be tested, or it would take longer to refactor than to rewrite safely.
- The "strangler fig" pattern — incrementally replacing a module while both versions run — is often better than a big-bang rewrite.
Verify behavior is unchanged
- Run the full test suite after each change.
- If you introduced a regression,
git stash or revert to the last green state immediately.
- For critical paths, add integration or end-to-end tests before and after.
Commit with intent
- Each commit should do one logical refactoring: "Extract
calculateDiscount from processOrder"
- Never combine a refactoring commit with a feature commit.
Examples
Example 1: Refactor a God Class
Input:
class OrderProcessor:
def __init__(self, db, email_client, stripe_client):
self.db = db
self.email = email_client
self.stripe = stripe_client
def process(self, user_id, cart):
# Validate cart
if not cart or len(cart) == 0:
raise ValueError("Cart is empty")
for item in cart:
if item['quantity'] <= 0:
raise ValueError(f"Invalid quantity for {item['name']}")
# Calculate total
subtotal = sum(i['price'] * i['quantity'] for i in cart)
tax = subtotal * 0.08
total = subtotal + tax
# Charge payment
charge = self.stripe.charge(user_id, total)
if not charge['success']:
raise RuntimeError("Payment failed")
# Save order
order = {'user_id': user_id, 'total': total, 'items': cart}
order_id = self.db.insert('orders', order)
# Send confirmation
user = self.db.find('users', user_id)
self.email.send(user['email'], f"Order {order_id} confirmed!")
return order_id
Output (refactored):
# cart_validator.py
class CartValidator:
def validate(self, cart):
if not cart:
raise ValueError("Cart is empty")
for item in cart:
if item['quantity'] <= 0:
raise ValueError(f"Invalid quantity for {item['name']}")
# pricing.py
class PricingCalculator:
TAX_RATE = 0.08
def calculate_total(self, cart):
subtotal = sum(i['price'] * i['quantity'] for i in cart)
return subtotal * (1 + self.TAX_RATE)
# payment_service.py
class PaymentService:
def __init__(self, stripe_client):
self.stripe = stripe_client
def charge(self, user_id, amount):
charge = self.stripe.charge(user_id, amount)
if not charge['success']:
raise RuntimeError("Payment failed")
# order_repository.py
class OrderRepository:
def __init__(self, db):
self.db = db
def save(self, user_id, total, cart):
return self.db.insert('orders', {'user_id': user_id, 'total': total, 'items': cart})
# order_processor.py — now thin orchestrator
class OrderProcessor:
def __init__(self, validator, pricing, payment, repo, notifier):
self.validator = validator
self.pricing = pricing
self.payment = payment
self.repo = repo
self.notifier = notifier
def process(self, user_id, cart):
self.validator.validate(cart)
total = self.pricing.calculate_total(cart)
self.payment.charge(user_id, total)
order_id = self.repo.save(user_id, total, cart)
self.notifier.confirm(user_id, order_id)
return order_id
Each class now has a single responsibility and can be tested in isolation.
Example 2: Extract repeated logic and apply Guard Clauses
Input:
function getDisplayName(user) {
if (user !== null && user !== undefined) {
if (user.firstName !== null && user.firstName !== undefined) {
if (user.lastName !== null && user.lastName !== undefined) {
return user.firstName + ' ' + user.lastName;
} else {
return user.firstName;
}
} else {
if (user.email !== null && user.email !== undefined) {
return user.email;
} else {
return 'Anonymous';
}
}
} else {
return 'Anonymous';
}
}
Output (refactored):
// Step 1: Apply guard clauses to flatten nesting
// Step 2: Extract predicate helper
// Step 3: Use nullish coalescing / optional chaining (ES2020+)
function getDisplayName(user) {
if (!user) return 'Anonymous';
if (user.firstName && user.lastName) return `${user.firstName} ${user.lastName}`;
if (user.firstName) return user.firstName;
return user.email ?? 'Anonymous';
}
Reduced from 20 lines to 5. Logic is identical but immediately readable.
Best Practices
- The Golden Rule: never change behavior and structure in the same commit
- Always start from a green test suite — refactoring without tests is rewriting under the illusion of safety
- Prefer small, frequent refactoring to large, infrequent rewrites
- Use your IDE's rename/extract tools instead of manual search-and-replace
- Read Refactoring by Martin Fowler for the canonical catalog of patterns
- When in doubt, extract a function — small, named functions are almost always better
Common Mistakes
- Refactoring and adding features at the same time (mixing concerns in a commit)
- Refactoring without tests — impossible to verify behavior is preserved
- Extracting too early (premature abstraction) before patterns are clear
- Renaming things inconsistently (rename in one place, miss others)
- Treating "rewrite in a cleaner way" as refactoring when the logic changes
- Gold plating — refactoring to perfection when good enough is ready to ship
Tips & Tricks
- Run your test suite in watch mode while refactoring so failures are instant
git commit --amend or git rebase -i let you clean up refactoring commits before pushing
- The "rule of three": tolerate duplication once, extract on the third occurrence
- IDE shortcuts (IntelliJ
Ctrl+Alt+M, VS Code refactor menu) are faster and safer than manual edits
- Leave code cleaner than you found it — the "boy scout rule" applied per PR
Related Skills
1---2name: refactorer3description: Use this skill when improving the structure, clarity, or design of existing code without changing its behavior. Trigger phrases: 'clean up this code', 'this is messy', 'refactor this', 'apply SOLID principles'. Not for adding new features or fixing bugs.4license: MIT5---67# Refactorer89## Overview10The Refactorer skill provides a disciplined, step-by-step approach to improving the internal structure of code while preserving its observable behavior. It covers the most impactful refactoring patterns — including Extract Function, DRY, SOLID principles, and replacing conditionals with polymorphism — along with a decision framework for knowing when to refactor versus when to rewrite. Every refactoring step is backed by tests to ensure nothing breaks.1112## When to Use13- Code is difficult to read, understand, or explain to a colleague14- The same logic appears in two or more places (DRY violation)15- A function or class has grown too large and handles too many concerns16- Adding a new feature requires touching many unrelated parts of the codebase17- Technical debt has accumulated to the point of slowing down delivery18- Code review consistently raises the same structural feedback1920## When NOT to Use21- The code is throwaway/prototype quality and will be replaced soon22- You don't have tests and can't add them before refactoring (too risky)23- The behavior itself is wrong — fix the bug first, then refactor24- You are under a tight deadline — schedule refactoring as deliberate work25- You're tempted to rewrite in a new language or framework (that's not refactoring)2627## Quick Reference28| Pattern | When to Apply |29|---------|--------------|30| Extract Function | Function body > 20 lines; logic block has a clear name |31| Extract Class | Class has > 1 responsibility; fields cluster into subgroups |32| Rename | Name is misleading, abbreviated, or doesn't match intent |33| DRY / Extract Common | Same logic copied in 2+ places |34| Replace Magic Number | Unexplained literals; use named constants |35| Guard Clause | Deeply nested if/else; flip condition to return early |36| Replace Conditional with Polymorphism | Long if/switch on type; open/closed principle |37| Introduce Parameter Object | Function takes 4+ related params; group into a struct/dataclass |38| Move Method | Method uses another class's data more than its own |39| Decompose Conditional | Complex boolean expression; extract to named predicate function |4041## Instructions42431. **Ensure test coverage before touching code**44 - Run the existing test suite. It must be green before you start.45 - If coverage is low, write characterization tests (tests that capture current behavior) before refactoring.46 - Commit the tests separately so the refactoring diff is clean.47482. **Identify the smell** — Choose the most impactful problem to fix first:49 - **God Class**: one class that does everything50 - **Long Method**: function spans more than 20–30 lines51 - **Duplicated Code**: same or nearly same logic in multiple places52 - **Feature Envy**: a method that uses another class's data extensively53 - **Primitive Obsession**: using raw strings/ints where a domain type should exist54 - **Shotgun Surgery**: one change requires edits across many files55563. **Apply refactoring patterns incrementally**57 - Make one refactoring at a time. Run tests after each step.58 - Never mix refactoring with behavior changes in the same commit.59 - Use your IDE's automated refactoring tools (rename, extract, move) when available — they are safer than manual edits.60614. **Refactor vs. Rewrite decision**62 - **Refactor** when: the core logic is correct, tests exist, and the issues are structural.63 - **Rewrite** when: the approach is fundamentally wrong, the code can't be tested, or it would take longer to refactor than to rewrite safely.64 - The "strangler fig" pattern — incrementally replacing a module while both versions run — is often better than a big-bang rewrite.65665. **Verify behavior is unchanged**67 - Run the full test suite after each change.68 - If you introduced a regression, `git stash` or revert to the last green state immediately.69 - For critical paths, add integration or end-to-end tests before and after.70716. **Commit with intent**72 - Each commit should do one logical refactoring: "Extract `calculateDiscount` from `processOrder`"73 - Never combine a refactoring commit with a feature commit.7475## Examples7677### Example 1: Refactor a God Class7879**Input:**80```python81class OrderProcessor:82 def __init__(self, db, email_client, stripe_client):83 self.db = db84 self.email = email_client85 self.stripe = stripe_client8687 def process(self, user_id, cart):88 # Validate cart89 if not cart or len(cart) == 0:90 raise ValueError("Cart is empty")91 for item in cart:92 if item['quantity'] <= 0:93 raise ValueError(f"Invalid quantity for {item['name']}")9495 # Calculate total96 subtotal = sum(i['price'] * i['quantity'] for i in cart)97 tax = subtotal * 0.0898 total = subtotal + tax99100 # Charge payment101 charge = self.stripe.charge(user_id, total)102 if not charge['success']:103 raise RuntimeError("Payment failed")104105 # Save order106 order = {'user_id': user_id, 'total': total, 'items': cart}107 order_id = self.db.insert('orders', order)108109 # Send confirmation110 user = self.db.find('users', user_id)111 self.email.send(user['email'], f"Order {order_id} confirmed!")112113 return order_id114```115116**Output (refactored):**117```python118# cart_validator.py119class CartValidator:120 def validate(self, cart):121 if not cart:122 raise ValueError("Cart is empty")123 for item in cart:124 if item['quantity'] <= 0:125 raise ValueError(f"Invalid quantity for {item['name']}")126127# pricing.py128class PricingCalculator:129 TAX_RATE = 0.08130131 def calculate_total(self, cart):132 subtotal = sum(i['price'] * i['quantity'] for i in cart)133 return subtotal * (1 + self.TAX_RATE)134135# payment_service.py136class PaymentService:137 def __init__(self, stripe_client):138 self.stripe = stripe_client139140 def charge(self, user_id, amount):141 charge = self.stripe.charge(user_id, amount)142 if not charge['success']:143 raise RuntimeError("Payment failed")144145# order_repository.py146class OrderRepository:147 def __init__(self, db):148 self.db = db149150 def save(self, user_id, total, cart):151 return self.db.insert('orders', {'user_id': user_id, 'total': total, 'items': cart})152153# order_processor.py — now thin orchestrator154class OrderProcessor:155 def __init__(self, validator, pricing, payment, repo, notifier):156 self.validator = validator157 self.pricing = pricing158 self.payment = payment159 self.repo = repo160 self.notifier = notifier161162 def process(self, user_id, cart):163 self.validator.validate(cart)164 total = self.pricing.calculate_total(cart)165 self.payment.charge(user_id, total)166 order_id = self.repo.save(user_id, total, cart)167 self.notifier.confirm(user_id, order_id)168 return order_id169```170171Each class now has a single responsibility and can be tested in isolation.172173---174175### Example 2: Extract repeated logic and apply Guard Clauses176177**Input:**178```javascript179function getDisplayName(user) {180 if (user !== null && user !== undefined) {181 if (user.firstName !== null && user.firstName !== undefined) {182 if (user.lastName !== null && user.lastName !== undefined) {183 return user.firstName + ' ' + user.lastName;184 } else {185 return user.firstName;186 }187 } else {188 if (user.email !== null && user.email !== undefined) {189 return user.email;190 } else {191 return 'Anonymous';192 }193 }194 } else {195 return 'Anonymous';196 }197}198```199200**Output (refactored):**201```javascript202// Step 1: Apply guard clauses to flatten nesting203// Step 2: Extract predicate helper204// Step 3: Use nullish coalescing / optional chaining (ES2020+)205206function getDisplayName(user) {207 if (!user) return 'Anonymous';208 if (user.firstName && user.lastName) return `${user.firstName} ${user.lastName}`;209 if (user.firstName) return user.firstName;210 return user.email ?? 'Anonymous';211}212```213214Reduced from 20 lines to 5. Logic is identical but immediately readable.215216## Best Practices217- The Golden Rule: **never change behavior and structure in the same commit**218- Always start from a green test suite — refactoring without tests is rewriting under the illusion of safety219- Prefer small, frequent refactoring to large, infrequent rewrites220- Use your IDE's rename/extract tools instead of manual search-and-replace221- Read *Refactoring* by Martin Fowler for the canonical catalog of patterns222- When in doubt, extract a function — small, named functions are almost always better223224## Common Mistakes225- Refactoring and adding features at the same time (mixing concerns in a commit)226- Refactoring without tests — impossible to verify behavior is preserved227- Extracting too early (premature abstraction) before patterns are clear228- Renaming things inconsistently (rename in one place, miss others)229- Treating "rewrite in a cleaner way" as refactoring when the logic changes230- Gold plating — refactoring to perfection when good enough is ready to ship231232## Tips & Tricks233- Run your test suite in watch mode while refactoring so failures are instant234- `git commit --amend` or `git rebase -i` let you clean up refactoring commits before pushing235- The "rule of three": tolerate duplication once, extract on the third occurrence236- IDE shortcuts (IntelliJ `Ctrl+Alt+M`, VS Code refactor menu) are faster and safer than manual edits237- Leave code cleaner than you found it — the "boy scout rule" applied per PR238239## Related Skills240- [code-reviewer](../code-reviewer/SKILL.md)241- [test-writer](../test-writer/SKILL.md)242- [architecture-designer](../architecture-designer/SKILL.md)243- [debugger](../debugger/SKILL.md)