| name | refactoring-assistant |
| description | Helps improve code quality through systematic refactoring, applying SOLID principles, DRY patterns, and design patterns. Use when cleaning up legacy code, reducing complexity, eliminating code smells, improving maintainability, or restructuring codebases for better architecture. |
Refactoring Assistant Skill
This skill guides systematic code improvement through proven refactoring techniques. Use this whenever you need to clean up code, reduce technical debt, improve structure, or apply better design patterns.
Core Refactoring Principles
1. SOLID Principles
| Principle | Description | Violation Signs |
|---|
| Single Responsibility | One class = one reason to change | Large classes, mixed concerns |
| Open/Closed | Open for extension, closed for modification | Frequent edits to existing code |
| Liskov Substitution | Subtypes must be substitutable | Broken inheritance, type checks |
| Interface Segregation | Many specific interfaces > one general | Fat interfaces, unused methods |
| Dependency Inversion | Depend on abstractions, not concretions | Hard-coded dependencies |
2. DRY (Don't Repeat Yourself)
- Eliminate duplicate code
- Extract common logic into reusable components
- Use configuration over hardcoding
- Single source of truth for data
3. KISS (Keep It Simple, Stupid)
- Prefer simple solutions over clever ones
- Avoid premature optimization
- Reduce unnecessary complexity
- Write readable, self-documenting code
4. YAGNI (You Ain't Gonna Need It)
- Don't add functionality until needed
- Remove unused code
- Avoid speculative generality
- Build for today's requirements
Code Smells Catalog
Bloaters
Code that has grown excessively large:
| Smell | Description | Refactoring |
|---|
| Long Method | Method > 20-30 lines | Extract Method |
| Large Class | Class doing too much | Extract Class |
| Primitive Obsession | Overuse of primitives | Replace with Value Object |
| Long Parameter List | > 3-4 parameters | Parameter Object, Builder |
| Data Clumps | Groups of data appearing together | Extract Class |
Object-Orientation Abusers
Misuse of OOP principles:
| Smell | Description | Refactoring |
|---|
| Switch Statements | Complex conditionals | Replace with Polymorphism |
| Temporary Field | Fields only sometimes used | Extract Class |
| Refused Bequest | Subclass not using parent methods | Replace Inheritance with Delegation |
| Alternative Classes | Similar classes, different interfaces | Rename, Merge Classes |
Change Preventers
Code that makes changes difficult:
| Smell | Description | Refactoring |
|---|
| Divergent Change | One class changed for many reasons | Extract Class |
| Shotgun Surgery | One change affects many classes | Move Method, Inline Class |
| Parallel Inheritance | Subclass requires parallel subclass | Move Method, Move Field |
Dispensables
Code that could be removed:
| Smell | Description | Refactoring |
|---|
| Comments | Excessive comments hiding bad code | Extract Method, Rename |
| Duplicate Code | Same code in multiple places | Extract Method/Class |
| Dead Code | Unreachable/unused code | Delete it |
| Speculative Generality | Unused abstractions | Collapse Hierarchy |
| Lazy Class | Class doing too little | Inline Class |
Couplers
Excessive coupling between classes:
| Smell | Description | Refactoring |
|---|
| Feature Envy | Method uses another class more | Move Method |
| Inappropriate Intimacy | Classes too dependent | Move Method, Extract Class |
| Message Chains | a.b().c().d() | Hide Delegate |
| Middle Man | Class only delegates | Remove Middle Man |
Refactoring Patterns
Extract Method
Transform long methods into smaller, focused ones.
Before:
def process_order(order):
if not order.items:
raise ValueError("Order must have items")
if order.total < 0:
raise ValueError("Invalid total")
discount = 0
if order.customer.is_premium:
discount = order.total * 0.1
if order.total > 100:
discount += order.total * 0.05
subtotal = order.total - discount
tax = subtotal * 0.08
final_total = subtotal + tax
email = f"Order confirmed. Total: ${final_total}"
send_email(order.customer.email, "Order Confirmation", email)
return final_total
After:
def process_order(order):
validate_order(order)
discount = calculate_discount(order)
final_total = apply_tax(order.total - discount)
send_order_confirmation(order.customer, final_total)
return final_total
def validate_order(order):
if not order.items:
raise ValueError("Order must have items")
if order.total < 0:
raise ValueError("Invalid total")
def calculate_discount(order):
discount = 0
if order.customer.is_premium:
discount = order.total * 0.1
if order.total > 100:
discount += order.total * 0.05
return discount
def apply_tax(subtotal, tax_rate=0.08):
return subtotal * (1 + tax_rate)
def send_order_confirmation(customer, total):
email = f"Order confirmed. Total: ${total}"
send_email(customer.email, "Order Confirmation", email)
Extract Class
Split a class with multiple responsibilities.
Before:
class User:
def __init__(self, name, email, street, city, zip_code, country):
self.name = name
self.email = email
self.street = street
self.city = city
self.zip_code = zip_code
self.country = country
def get_full_address(self):
return f"{self.street}, {self.city}, {self.zip_code}, {self.country}"
def validate_address(self):
return bool(self.street and self.city and self.zip_code)
def format_mailing_label(self):
return f"{self.name}\n{self.get_full_address()}"
After:
class Address:
def __init__(self, street, city, zip_code, country):
self.street = street
self.city = city
self.zip_code = zip_code
self.country = country
def get_full_address(self):
return f"{self.street}, {self.city}, {self.zip_code}, {self.country}"
def is_valid(self):
return bool(self.street and self.city and self.zip_code)
class User:
def __init__(self, name, email, address: Address):
self.name = name
self.email = email
self.address = address
def format_mailing_label(self):
return f"{self.name}\n{self.address.get_full_address()}"
Replace Conditional with Polymorphism
Replace complex conditionals with inheritance.
Before:
class PaymentProcessor:
def process_payment(self, payment_type, amount, details):
if payment_type == "credit_card":
if not self._validate_card(details["card_number"]):
raise ValueError("Invalid card")
response = self._charge_card(details["card_number"], amount)
return response
elif payment_type == "paypal":
token = self._get_paypal_token(details["email"])
response = self._paypal_charge(token, amount)
return response
elif payment_type == "bank_transfer":
if not self._validate_iban(details["iban"]):
raise ValueError("Invalid IBAN")
response = self._initiate_transfer(details["iban"], amount)
return response
else:
raise ValueError(f"Unknown payment type: {payment_type}")
After:
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def validate(self, details: dict) -> bool:
pass
@abstractmethod
def process(self, amount: float, details: dict) -> dict:
pass
class CreditCardPayment(PaymentMethod):
def validate(self, details):
return self._validate_card(details["card_number"])
def process(self, amount, details):
if not self.validate(details):
raise ValueError("Invalid card")
return self._charge_card(details["card_number"], amount)
class PayPalPayment(PaymentMethod):
def validate(self, details):
return bool(details.get("email"))
def process(self, amount, details):
token = self._get_paypal_token(details["email"])
return self._paypal_charge(token, amount)
class BankTransferPayment(PaymentMethod):
def validate(self, details):
return self._validate_iban(details["iban"])
def process(self, amount, details):
if not self.validate(details):
raise ValueError("Invalid IBAN")
return self._initiate_transfer(details["iban"], amount)
class PaymentProcessor:
def __init__(self):
self._methods = {
"credit_card": CreditCardPayment(),
"paypal": PayPalPayment(),
"bank_transfer": BankTransferPayment(),
}
def process_payment(self, payment_type, amount, details):
method = self._methods.get(payment_type)
if not method:
raise ValueError(f"Unknown payment type: {payment_type}")
return method.process(amount, details)
Introduce Parameter Object
Replace long parameter lists with an object.
Before:
def create_report(
title,
author,
start_date,
end_date,
include_charts,
include_summary,
page_size,
orientation,
department,
category
):
pass
def email_report(
title,
author,
start_date,
end_date,
include_charts,
include_summary,
recipients
):
pass
After:
from dataclasses import dataclass
from datetime import date
from typing import Optional
@dataclass
class ReportConfig:
title: str
author: str
start_date: date
end_date: date
include_charts: bool = True
include_summary: bool = True
@dataclass
class PrintConfig:
page_size: str = "A4"
orientation: str = "portrait"
@dataclass
class ReportFilter:
department: Optional[str] = None
category: Optional[str] = None
def create_report(
config: ReportConfig,
print_config: PrintConfig,
filter: ReportFilter
):
pass
def email_report(
config: ReportConfig,
recipients: list[str]
):
pass
Replace Magic Numbers/Strings with Constants
Before:
def calculate_shipping(weight, distance):
if weight < 1:
base_cost = 5.99
elif weight < 5:
base_cost = 9.99
else:
base_cost = 14.99
if distance > 500:
base_cost *= 1.5
if distance > 1000:
base_cost *= 2.0
return base_cost
def get_status_message(status):
if status == 1:
return "Pending"
elif status == 2:
return "Processing"
elif status == 3:
return "Shipped"
elif status == 4:
return "Delivered"
After:
from enum import Enum, auto
from dataclasses import dataclass
class ShippingTier:
LIGHT_WEIGHT_LIMIT = 1
MEDIUM_WEIGHT_LIMIT = 5
LIGHT_COST = 5.99
MEDIUM_COST = 9.99
HEAVY_COST = 14.99
class DistanceMultiplier:
LONG_DISTANCE_THRESHOLD = 500
VERY_LONG_DISTANCE_THRESHOLD = 1000
LONG_DISTANCE_MULTIPLIER = 1.5
VERY_LONG_DISTANCE_MULTIPLIER = 2.0
class OrderStatus(Enum):
PENDING = auto()
PROCESSING = auto()
SHIPPED = auto()
DELIVERED = auto()
@property
def display_name(self):
return self.name.capitalize()
def calculate_shipping(weight: float, distance: float) -> float:
if weight < ShippingTier.LIGHT_WEIGHT_LIMIT:
base_cost = ShippingTier.LIGHT_COST
elif weight < ShippingTier.MEDIUM_WEIGHT_LIMIT:
base_cost = ShippingTier.MEDIUM_COST
else:
base_cost = ShippingTier.HEAVY_COST
if distance > DistanceMultiplier.VERY_LONG_DISTANCE_THRESHOLD:
base_cost *= DistanceMultiplier.VERY_LONG_DISTANCE_MULTIPLIER
elif distance > DistanceMultiplier.LONG_DISTANCE_THRESHOLD:
base_cost *= DistanceMultiplier.LONG_DISTANCE_MULTIPLIER
return base_cost
def get_status_message(status: OrderStatus) -> str:
return status.display_name
Dependency Injection
Replace hard-coded dependencies with injected ones.
Before:
class UserService:
def __init__(self):
self.db = PostgresDatabase("localhost", "users_db")
self.cache = RedisCache("localhost:6379")
self.mailer = SmtpMailer("smtp.gmail.com")
def create_user(self, data):
user = self.db.insert("users", data)
self.cache.set(f"user:{user.id}", user)
self.mailer.send(user.email, "Welcome!")
return user
After:
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def insert(self, table: str, data: dict) -> dict:
pass
class Cache(ABC):
@abstractmethod
def set(self, key: str, value: any) -> None:
pass
class Mailer(ABC):
@abstractmethod
def send(self, to: str, message: str) -> None:
pass
class PostgresDatabase(Database):
def __init__(self, host: str, db_name: str):
self.host = host
self.db_name = db_name
def insert(self, table, data):
pass
class RedisCache(Cache):
def __init__(self, url: str):
self.url = url
def set(self, key, value):
pass
class UserService:
def __init__(self, db: Database, cache: Cache, mailer: Mailer):
self.db = db
self.cache = cache
self.mailer = mailer
def create_user(self, data):
user = self.db.insert("users", data)
self.cache.set(f"user:{user.id}", user)
self.mailer.send(user.email, "Welcome!")
return user
class MockDatabase(Database):
def insert(self, table, data):
return {"id": 1, **data}
service = UserService(
db=PostgresDatabase("localhost", "users_db"),
cache=RedisCache("localhost:6379"),
mailer=SmtpMailer("smtp.gmail.com")
)
test_service = UserService(
db=MockDatabase(),
cache=MockCache(),
mailer=MockMailer()
)
Strategy Pattern
Encapsulate algorithms that can be swapped.
Before:
class PriceCalculator:
def calculate(self, items, customer_type, promo_code):
total = sum(item.price * item.quantity for item in items)
if customer_type == "premium":
total *= 0.9
elif customer_type == "vip":
total *= 0.8
if promo_code == "SUMMER20":
total *= 0.8
elif promo_code == "FLASH50":
total *= 0.5
elif promo_code == "FREESHIP":
pass
return total
After:
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, total: float) -> float:
pass
class NoDiscount(DiscountStrategy):
def apply(self, total):
return total
class PercentageDiscount(DiscountStrategy):
def __init__(self, percentage: float):
self.percentage = percentage
def apply(self, total):
return total * (1 - self.percentage / 100)
class FixedDiscount(DiscountStrategy):
def __init__(self, amount: float):
self.amount = amount
def apply(self, total):
return max(0, total - self.amount)
class CompositeDiscount(DiscountStrategy):
def __init__(self, *strategies: DiscountStrategy):
self.strategies = strategies
def apply(self, total):
for strategy in self.strategies:
total = strategy.apply(total)
return total
class DiscountFactory:
CUSTOMER_DISCOUNTS = {
"regular": NoDiscount(),
"premium": PercentageDiscount(10),
"vip": PercentageDiscount(20),
}
PROMO_DISCOUNTS = {
"SUMMER20": PercentageDiscount(20),
"FLASH50": PercentageDiscount(50),
"SAVE10": FixedDiscount(10),
}
@classmethod
def get_discount(cls, customer_type: str, promo_code: str = None) -> DiscountStrategy:
strategies = []
customer_discount = cls.CUSTOMER_DISCOUNTS.get(customer_type, NoDiscount())
strategies.append(customer_discount)
if promo_code:
promo_discount = cls.PROMO_DISCOUNTS.get(promo_code)
if promo_discount:
strategies.append(promo_discount)
if len(strategies) == 1:
return strategies[0]
return CompositeDiscount(*strategies)
class PriceCalculator:
def calculate(self, items, customer_type, promo_code=None):
total = sum(item.price * item.quantity for item in items)
discount = DiscountFactory.get_discount(customer_type, promo_code)
return discount.apply(total)
Null Object Pattern
Replace null checks with a null object.
Before:
class UserService:
def get_user(self, user_id):
user = self.db.find(user_id)
return user
def display_welcome(self, user_id):
user = self.get_user(user_id)
if user is not None:
if user.name is not None:
print(f"Welcome, {user.name}!")
else:
print("Welcome!")
if user.preferences is not None:
if user.preferences.theme is not None:
apply_theme(user.preferences.theme)
else:
apply_theme("default")
else:
apply_theme("default")
else:
print("Welcome, Guest!")
apply_theme("default")
After:
from dataclasses import dataclass, field
@dataclass
class Preferences:
theme: str = "default"
language: str = "en"
notifications: bool = True
@dataclass
class User:
id: str
name: str
email: str
preferences: Preferences = field(default_factory=Preferences)
class NullUser(User):
"""Null Object representing a guest/anonymous user."""
def __init__(self):
super().__init__(
id="guest",
name="Guest",
email="",
preferences=Preferences()
)
def is_authenticated(self):
return False
class UserService:
NULL_USER = NullUser()
def get_user(self, user_id) -> User:
user = self.db.find(user_id)
return user if user else self.NULL_USER
def display_welcome(self, user_id):
user = self.get_user(user_id)
print(f"Welcome, {user.name}!")
apply_theme(user.preferences.theme)
Refactoring Process
Step 1: Identify the Smell
- Review code for patterns listed above
- Check complexity metrics (cyclomatic complexity, LOC)
- Look for repeated patterns
Step 2: Ensure Test Coverage
- Write tests before refactoring (if missing)
- Tests should pass before AND after
- Use tests as a safety net
Step 3: Make Small Changes
- One refactoring at a time
- Commit after each successful change
- Keep changes reversible
Step 4: Verify Behavior
- Run all tests after each change
- Check for regressions
- Validate performance if relevant
Step 5: Clean Up
- Remove dead code
- Update documentation
- Review for additional improvements
Complexity Metrics
When to Refactor
| Metric | Threshold | Action |
|---|
| Cyclomatic Complexity | > 10 | Extract methods |
| Lines per Method | > 30 | Extract methods |
| Lines per Class | > 300 | Extract classes |
| Parameters | > 4 | Parameter object |
| Nesting Depth | > 3 | Early returns, extract |
| Duplicate Code | > 3 occurrences | Extract to shared |
Measuring Complexity
Refactoring Checklist
Before refactoring:
During refactoring:
After refactoring:
Output Format
When suggesting refactorings, provide:
## Refactoring Analysis
### Code Smell Identified
**Type:** [Smell name]
**Severity:** [High/Medium/Low]
**Location:** `file.py:42-78`
### Current Code Issues
1. [Issue 1]
2. [Issue 2]
3. [Issue 3]
### Recommended Refactoring
**Pattern:** [Refactoring pattern name]
**Current Code:**
```python
[Code before refactoring]
Refactored Code:
[Code after refactoring]
Benefits
- [Benefit 1]
- [Benefit 2]
- [Benefit 3]
Migration Steps
- [Step 1]
- [Step 2]
- [Step 3]
Testing Considerations
- [What tests to add/modify]
## Notes
- Always refactor with tests as a safety net
- Prefer many small refactorings over big rewrites
- Don't mix refactoring with adding new features
- Refactor incrementally, not all at once
- Sometimes "good enough" code is acceptable
- Consider team readability over clever solutions
- Document significant design decisions