Safely refactor code while maintaining behavior. Use when improving code structure, reducing duplication, extracting functions, or modernizing legacy code.
Install
npx skillscat add saajunaid/caddis-plugin/refactoring Install via the SkillsCat registry.
SKILL.md
Refactoring Skill
Systematically refactor code to improve quality while preserving functionality.
Trigger
Activate when:
- User asks to "refactor" or "clean up" code
- Code review identifies structural issues
- User wants to improve maintainability
- Technical debt needs to be addressed
Refactoring Principles
- Never change behavior during refactoring
- Small steps - one change at a time
- Tests first - ensure coverage before changing
- Commit often - after each successful refactoring
- One thing at a time - don't mix refactoring with feature changes
When NOT to Refactor
- Code that works and won't change again (if it ain't broke...)
- Critical production code without tests (add tests first)
- When under a tight deadline (schedule it for later)
- "Just because" - always have a clear purpose
Phase 1: Assessment
Objectives
- Understand current code structure
- Identify refactoring opportunities
- Assess test coverage
Actions
Read and understand the code
- What does this code do?
- What are the inputs/outputs?
- Where are the dependencies?
Identify code smells
Smell Signs Refactoring Long Method >20 lines Extract Method Large Class >200 lines, multiple concerns Extract Class Duplicated Code Same logic in multiple places Extract Method/Class Long Parameter List >3-4 parameters Parameter Object Deep Nesting >3 levels of indentation Guard Clauses, Extract Method Magic Numbers Hardcoded values Named Constants Dead Code Unused functions/variables Delete Feature Envy Method uses another class's data more Move Method Check test coverage
pytest --cov=module_name --cov-report=term-missing
Output
## Refactoring Assessment
### Current State
- File: [filename]
- Lines: [count]
- Test Coverage: [percentage]
### Code Smells Identified
1. [Smell]: [Location] - [Description]
2. [Smell]: [Location] - [Description]
### Recommended Refactorings
1. [Refactoring]: [Expected improvement]
2. [Refactoring]: [Expected improvement]
### Risk Assessment
- Test coverage: [adequate/needs improvement]
- Complexity: [low/medium/high]
- Dependencies: [list affected areas]Phase 2: Preparation
Objectives
- Ensure adequate test coverage
- Set up for safe refactoring
Actions
Write missing tests (if coverage < 80%)
def test_existing_behavior(): """Capture current behavior before refactoring.""" result = function_to_refactor(input_data) assert result == expected_outputCreate characterization tests (for legacy code)
def test_characterization(): """Document current behavior, even if unexpected.""" # Run function and record actual output result = legacy_function(test_input) # Assert against actual output (not expected) assert result == actual_observed_outputRun all tests and ensure they pass
pytest -vCommit current state
git add . git commit -m "chore: add tests before refactoring"
Phase 3: Execute Refactoring
Common Refactoring Patterns
Extract Method
# Before: Long method with mixed concerns
def process_complaint(complaint):
# Validation (10 lines)
if not complaint.customer_id:
raise ValueError("Customer ID required")
if not complaint.description:
raise ValueError("Description required")
# ... more validation
# Processing (15 lines)
complaint.status = "processing"
# ... processing logic
# Notification (10 lines)
send_email(complaint.customer_email, "...")
# ... notification logic
# After: Extracted methods
def process_complaint(complaint):
validate_complaint(complaint)
process_complaint_logic(complaint)
send_complaint_notification(complaint)
def validate_complaint(complaint):
if not complaint.customer_id:
raise ValueError("Customer ID required")
if not complaint.description:
raise ValueError("Description required")
def process_complaint_logic(complaint):
complaint.status = "processing"
# ... processing logic
def send_complaint_notification(complaint):
send_email(complaint.customer_email, "...")Extract Class
# Before: Class doing too much
class ComplaintHandler:
def __init__(self):
self.db_connection = create_connection()
def create_complaint(self, data):
# 20 lines of complaint logic
pass
def send_email(self, to, subject, body):
# 15 lines of email logic
pass
def generate_report(self, date_range):
# 25 lines of reporting logic
pass
# After: Separated concerns
class ComplaintHandler:
def __init__(self, notifier, reporter):
self.notifier = notifier
self.reporter = reporter
def create_complaint(self, data):
# Complaint logic only
pass
class EmailNotifier:
def send_email(self, to, subject, body):
pass
class ComplaintReporter:
def generate_report(self, date_range):
passReplace Conditionals with Polymorphism
# Before: Switch-like conditionals
def calculate_priority_score(complaint):
if complaint.type == "billing":
return complaint.amount * 0.1
elif complaint.type == "technical":
return 50 + complaint.severity * 10
elif complaint.type == "service":
return 30 + complaint.wait_time * 2
else:
return 10
# After: Strategy pattern
class PriorityCalculator(Protocol):
def calculate(self, complaint) -> float: ...
class BillingPriorityCalculator:
def calculate(self, complaint) -> float:
return complaint.amount * 0.1
class TechnicalPriorityCalculator:
def calculate(self, complaint) -> float:
return 50 + complaint.severity * 10
CALCULATORS = {
"billing": BillingPriorityCalculator(),
"technical": TechnicalPriorityCalculator(),
"service": ServicePriorityCalculator(),
}
def calculate_priority_score(complaint):
calculator = CALCULATORS.get(complaint.type, DefaultCalculator())
return calculator.calculate(complaint)Guard Clauses
# Before: Deep nesting
def process(data):
if data is not None:
if data.is_valid:
if data.has_permission:
# Actual logic here
result = do_something(data)
return result
else:
raise PermissionError()
else:
raise ValueError("Invalid data")
else:
raise ValueError("Data required")
# After: Guard clauses
def process(data):
if data is None:
raise ValueError("Data required")
if not data.is_valid:
raise ValueError("Invalid data")
if not data.has_permission:
raise PermissionError()
# Actual logic at main level
return do_something(data)Duplicated Code
# Before: Same logic in multiple places
def get_user_discount(user):
if user.membership == "gold": return user.total * 0.2
if user.membership == "silver": return user.total * 0.1
return 0
def get_order_discount(order):
if order.user.membership == "gold": return order.total * 0.2
if order.user.membership == "silver": return order.total * 0.1
return 0
# After: Extract common logic
DISCOUNT_RATES = {"gold": 0.2, "silver": 0.1}
def get_membership_rate(membership: str) -> float:
return DISCOUNT_RATES.get(membership, 0)Long Parameter List → Parameter Object
# Before: Too many parameters
def create_user(email, password, name, age, address, city, country, phone):
...
# After: Group into a dataclass
@dataclass
class UserData:
email: str
password: str
name: str
age: int | None = None
address: str | None = None
phone: str | None = None
def create_user(data: UserData):
...Feature Envy → Move Method
# Before: Method uses another object's data more than its own
class Order:
def calculate_discount(self, user):
if user.membership_level == "gold":
return self.total * 0.2
if user.account_age > 365:
return self.total * 0.1
return 0
# After: Move logic to the object that owns the data
class User:
def get_discount_rate(self) -> float:
if self.membership_level == "gold": return 0.2
if self.account_age > 365: return 0.1
return 0
class Order:
def calculate_discount(self, user):
return self.total * user.get_discount_rate()Primitive Obsession → Value Objects
# Before: Using raw strings for domain concepts
def send_email(to: str, subject: str, body: str): ...
send_email("user@example.com", "Hello", "...")
# After: Domain types with validation
class Email:
def __init__(self, value: str):
if "@" not in value:
raise ValueError(f"Invalid email: {value}")
self.value = value
def send_email(to: Email, subject: str, body: str): ...Dead Code → Delete It
# Before: Unused code lingers
def old_implementation(): ... # Nobody calls this
DEPRECATED_VALUE = 5 # Not referenced
# def commented_out_code(): ... # Noise
# After: Remove it — git has history if you need it backAfter Each Refactoring
Run tests
pytest -vIf tests pass, commit
git commit -m "refactor: extract validation to separate method"If tests fail, revert
git checkout -- .
Phase 4: Validation
Objectives
- Verify behavior unchanged
- Confirm quality improved
- Document changes
Actions
Run full test suite
pytest --cov=module_nameCompare before/after metrics
- Lines of code
- Cyclomatic complexity
- Test coverage
- Method/class sizes
Manual smoke test
- Test key user flows
- Verify edge cases
Phase 5: Documentation
Output
## Refactoring Summary
### Changes Made
1. **[Refactoring 1]**: [What was changed and why]
2. **[Refactoring 2]**: [What was changed and why]
### Files Modified
- `path/to/file.py`: [Summary of changes]
### Metrics Comparison
| Metric | Before | After |
|--------|--------|-------|
| Lines of Code | [x] | [y] |
| Methods >20 lines | [x] | [y] |
| Test Coverage | [x]% | [y]% |
### Behavioral Changes
None (refactoring only)
### Tests Added
- `test_function_name`: [What it tests]
### Follow-up Recommendations
- [ ] [Future improvement 1]
- [ ] [Future improvement 2]Common Refactoring Operations Reference
| Operation | Description |
|---|---|
| Extract Method | Turn code fragment into a method |
| Extract Class | Move behavior to a new class |
| Inline Method | Move method body back to caller |
| Introduce Parameter Object | Group related parameters |
| Replace Conditional with Polymorphism | Use strategy/polymorphism instead of switch/if |
| Replace Magic Number with Constant | Named constants for clarity |
| Decompose Conditional | Break complex conditions into named functions |
| Replace Nested Conditional with Guard Clauses | Early returns |
| Introduce Null Object | Eliminate null checks |
| Replace Type Code with Enum | Strong typing for categories |
| Replace Inheritance with Delegation | Composition over inheritance |
Safety Checklist
- Tests exist for code being refactored
- All tests pass before starting
- Refactoring in small, atomic steps
- Tests run after each change
- Commits after each successful refactoring
- No behavior changes introduced
- All tests pass after completion
- Changes documented