Refactoring
A comprehensive refactoring skill that helps improve code structure, readability, and maintainability while preserving existing behavior.
Quick Start
Basic refactoring workflow:
# Ensure tests exist (or create them first) # Identify code smells and improvement opportunities # Apply refactoring techniques incrementally # Run tests after each change # Commit frequently
Core Capabilities
1. Code Smell Detection & Resolution
Identify and fix common issues:
- •Long Method: Break into smaller methods
- •Large Class: Split responsibilities
- •Duplicate Code: Extract common functionality
- •Long Parameter List: Introduce parameter objects
- •Feature Envy: Move behavior to appropriate class
- •Data Clumps: Group related data
- •Primitive Obsession: Create value objects
- •Switch Statements: Replace with polymorphism
2. Refactoring Techniques
Apply proven refactoring patterns:
- •Extract Method: Pull code into separate method
- •Rename: Improve names for clarity
- •Move Method/Field: Relocate to appropriate class
- •Inline: Simplify unnecessary abstractions
- •Replace Temp with Query: Remove temporary variables
- •Introduce Parameter Object: Group related parameters
- •Replace Conditional with Polymorphism: Use inheritance
- •Extract Class: Split large classes
3. Design Pattern Implementation
Refactor to design patterns:
- •Strategy: Replace conditionals with strategies
- •Factory: Centralize object creation
- •Observer: Decouple event handling
- •Decorator: Add behavior without modification
- •Template Method: Define algorithm skeleton
4. Code Modernization
Update to modern practices:
- •Modern Language Features: Use latest syntax
- •Async/Await: Replace callback patterns
- •Type Safety: Add type hints/annotations
- •Functional Patterns: Pure functions, immutability
- •Dependency Injection: Improve testability
5. Performance Optimization
Improve efficiency while refactoring:
- •Algorithm Optimization: Better data structures
- •Caching: Avoid redundant computation
- •Lazy Loading: Defer expensive operations
- •Batch Operations: Reduce I/O overhead
- •Database Optimization: Query improvement
Refactoring Catalog
Extract Method
Before:
def print_invoice(invoice):
print("***********************")
print("**** Invoice ****")
print("***********************")
# Print details
print(f"Name: {invoice.customer_name}")
print(f"Address: {invoice.customer_address}")
print(f"Total: ${invoice.total}")
After:
def print_invoice(invoice):
print_banner()
print_details(invoice)
def print_banner():
print("***********************")
print("**** Invoice ****")
print("***********************")
def print_details(invoice):
print(f"Name: {invoice.customer_name}")
print(f"Address: {invoice.customer_address}")
print(f"Total: ${invoice.total}")
Replace Conditional with Polymorphism
Before:
class Bird:
def __init__(self, bird_type):
self.type = bird_type
def fly(self):
if self.type == "sparrow":
return "Flying short distances"
elif self.type == "eagle":
return "Soaring high"
elif self.type == "penguin":
return "Cannot fly"
After:
from abc import ABC, abstractmethod
class Bird(ABC):
@abstractmethod
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
return "Flying short distances"
class Eagle(Bird):
def fly(self):
return "Soaring high"
class Penguin(Bird):
def fly(self):
return "Cannot fly"
Introduce Parameter Object
Before:
def create_order(customer_name, customer_email, customer_address,
product_id, product_name, quantity, price):
# Create order with many parameters
pass
After:
from dataclasses import dataclass
@dataclass
class Customer:
name: str
email: str
address: str
@dataclass
class OrderItem:
product_id: int
product_name: str
quantity: int
price: float
def create_order(customer: Customer, item: OrderItem):
# Much cleaner interface
pass
Replace Temp with Query
Before:
def calculate_total(order):
base_price = order.quantity * order.item_price
discount_factor = 0.1 if base_price > 1000 else 0
return base_price * (1 - discount_factor)
After:
def calculate_total(order):
return base_price(order) * (1 - discount_factor(order))
def base_price(order):
return order.quantity * order.item_price
def discount_factor(order):
return 0.1 if base_price(order) > 1000 else 0
Extract Class
Before:
class Person:
def __init__(self, name, street, city, state, zip_code):
self.name = name
self.street = street
self.city = city
self.state = state
self.zip_code = zip_code
def get_full_address(self):
return f"{self.street}, {self.city}, {self.state} {self.zip_code}"
After:
class Address:
def __init__(self, street, city, state, zip_code):
self.street = street
self.city = city
self.state = state
self.zip_code = zip_code
def get_full_address(self):
return f"{self.street}, {self.city}, {self.state} {self.zip_code}"
class Person:
def __init__(self, name, address):
self.name = name
self.address = address
Refactoring Strategies
1. The Boy Scout Rule
Always leave code better than you found it.
# While fixing a bug, also: # - Improve variable names # - Extract complex expressions # - Add missing tests # - Update documentation
2. Incremental Refactoring
Make small, safe changes:
- •One change at a time: Single refactoring per commit
- •Run tests frequently: After each small change
- •Commit often: Create restore points
- •Stop if tests fail: Fix before continuing
3. Strangler Fig Pattern
Gradually replace legacy code:
Old System ──┐
├──▶ Facade ──▶ New System (grows)
New Code ────┘
- •Create abstraction layer
- •Route new features to new system
- •Migrate old features incrementally
- •Remove old system when complete
4. Branch by Abstraction
Refactor while keeping system working:
- •Create abstraction: Interface for current implementation
- •Implement new version: Behind same interface
- •Switch gradually: Use feature flags
- •Remove old implementation: When fully migrated
Language-Specific Refactoring
Python Modernization
Before (Python 2 style):
def process_data(data):
result = []
for item in data:
if item['value'] > 0:
result.append(item)
return result
After (Modern Python):
def process_data(data: list[dict]) -> list[dict]:
return [item for item in data if item['value'] > 0]
Advanced:
from typing import TypedDict
class DataItem(TypedDict):
value: int
name: str
def process_data(data: list[DataItem]) -> list[DataItem]:
"""Filter data items with positive values."""
return [item for item in data if item['value'] > 0]
JavaScript Modernization
Before (ES5):
var MyComponent = function(name) {
this.name = name;
};
MyComponent.prototype.greet = function() {
return 'Hello, ' + this.name;
};
After (Modern JavaScript):
class MyComponent {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
With TypeScript:
class MyComponent {
constructor(private name: string) {}
greet(): string {
return `Hello, ${this.name}`;
}
}
Callback to Async/Await
Before:
function fetchUserData(userId, callback) {
fetchUser(userId, function(err, user) {
if (err) return callback(err);
fetchPosts(user.id, function(err, posts) {
if (err) return callback(err);
fetchComments(posts, function(err, comments) {
if (err) return callback(err);
callback(null, { user, posts, comments });
});
});
});
}
After:
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts);
return { user, posts, comments };
} catch (error) {
throw new Error(`Failed to fetch user data: ${error.message}`);
}
}
Refactoring Workflow
Step 1: Understand the Code
- •Read the code thoroughly
- •Identify entry points and dependencies
- •Document current behavior
- •Review existing tests
Step 2: Ensure Test Coverage
If tests don't exist, create them first:
# Characterization tests - document current behavior
def test_current_behavior():
# Even if behavior is wrong, test it first
result = legacy_function(input_data)
assert result == expected_output # Document what it DOES
Step 3: Plan Refactoring
- •Identify code smells
- •Prioritize improvements
- •Choose refactoring techniques
- •Plan sequence of changes
Step 4: Refactor Incrementally
for each_refactoring in plan:
1. Make one small change
2. Run tests
3. If tests pass:
- Commit
- Continue
4. If tests fail:
- Revert
- Adjust approach
Step 5: Clean Up
- •Remove commented code
- •Update documentation
- •Verify all tests pass
- •Review overall improvements
Refactoring Patterns
Replace Magic Numbers
Before:
if order.total > 1000:
discount = order.total * 0.1
After:
BULK_ORDER_THRESHOLD = 1000
BULK_DISCOUNT_RATE = 0.1
if order.total > BULK_ORDER_THRESHOLD:
discount = order.total * BULK_DISCOUNT_RATE
Introduce Null Object
Before:
def get_customer_discount(customer):
if customer is None:
return 0
if customer.is_premium:
return 0.15
return 0.05
After:
class Customer:
def get_discount(self):
return 0.15 if self.is_premium else 0.05
class NullCustomer(Customer):
def get_discount(self):
return 0
def get_customer_discount(customer):
return customer.get_discount()
Replace Error Codes with Exceptions
Before:
def save_user(user):
if not user.email:
return -1 # Error: No email
if not user.name:
return -2 # Error: No name
# Save user
return 0 # Success
After:
class ValidationError(Exception):
pass
def save_user(user):
if not user.email:
raise ValidationError("Email is required")
if not user.name:
raise ValidationError("Name is required")
# Save user
Anti-Patterns to Avoid
1. Big Rewrite
❌ Don't: Rewrite everything from scratch
✅ Do: Refactor incrementally
2. Refactoring Without Tests
❌ Don't: Change code without safety net
✅ Do: Create tests first, then refactor
3. Premature Optimization
❌ Don't: Optimize before measuring
✅ Do: Profile first, optimize bottlenecks
4. Over-Engineering
❌ Don't: Add complexity "just in case"
✅ Do: Keep it simple, refactor when needed
Best Practices
- •Tests First: Ensure comprehensive test coverage
- •Small Steps: Make tiny, incremental changes
- •Commit Often: Create rollback points
- •Run Tests: After every change
- •One Thing at a Time: Single refactoring per commit
- •Preserve Behavior: Don't change functionality
- •Improve Names: Clear, descriptive naming
- •Remove Duplication: DRY principle
- •Simplify Logic: Reduce complexity
- •Document Decisions: Explain non-obvious changes
When to Refactor
✅ Good Times to Refactor:
- •Before adding new features
- •When fixing bugs
- •During code reviews
- •Scheduled refactoring sprints
- •When code is hard to understand
❌ Bad Times to Refactor:
- •Close to deadline
- •Without tests
- •Production is down
- •You don't understand the code
- •Scope is too large
When to Use This Skill
Use this skill when:
- •Improving code quality
- •Reducing technical debt
- •Modernizing legacy code
- •Preparing for new features
- •Eliminating code smells
- •Implementing design patterns
- •Optimizing performance
- •Improving testability
- •Making code more maintainable
- •Cleaning up after rapid development
Examples
See EXAMPLES.md for complete refactoring examples including:
- •Legacy code modernization
- •Design pattern implementation
- •Performance optimization
- •Test-driven refactoring
- •Large-scale refactoring strategies
For refactoring checklists, see CHECKLISTS.md.
For refactoring scripts, see scripts/refactor_helpers.py.