code-refactoring-solid
Guide for refactoring Python code to follow SOLID principles and improve maintainability
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
🏗️ Code Refactoring with SOLID Principles
When to use this skill
Use this skill when:
- Refactoring existing code to improve maintainability
- Code has grown complex and difficult to modify
- Adding new features requires changing multiple unrelated parts
- Unit testing is difficult due to tight coupling
- Code violates SOLID principles
- Planning architectural improvements
How to use it
🎯 SOLID Principles Overview
S - Single Responsibility Principle (SRP)
Definition: A class should have only one reason to change.
Violations in current codebase:
# ❌ BAD: DataManager does too much
class DataManager:
def load_data(self, data_type):
# Loading logic
def save_data(self, data_type, data):
# Saving logic
def delete_data(self, data_type):
# Deletion logic
# Also handles Firebase connection, validation, etc.
Refactored:
# ✅ GOOD: Separate responsibilities
class FirebaseConnection:
"""Handles Firebase connection only"""
def connect(self): pass
def is_available(self): pass
class DataRepository:
"""Handles data CRUD operations only"""
def __init__(self, connection: FirebaseConnection):
self.connection = connection
def load(self, data_type): pass
def save(self, data_type, data): pass
def delete(self, data_type): pass
class DataValidator:
"""Handles data validation only"""
def validate_group_ids(self, ids): pass
def validate_schedule(self, schedule): pass
O - Open/Closed Principle (OCP)
Definition: Software entities should be open for extension but closed for modification.
Violations:
# ❌ BAD: Must modify function to add new command
def handle_message(event):
if event.text.startswith("@time"):
# handle time
elif event.text.startswith("@day"):
# handle day
elif event.text.startswith("@week"):
# handle week
# Adding new command requires modifying this function
Refactored:
# ✅ GOOD: Command pattern - add new commands without modifying handler
class Command(ABC):
@abstractmethod
def can_handle(self, text: str) -> bool:
pass
@abstractmethod
def execute(self, event) -> str:
pass
class TimeCommand(Command):
def can_handle(self, text: str) -> bool:
return text.startswith("@time")
def execute(self, event) -> str:
# Handle time command
pass
class CommandHandler:
def __init__(self):
self.commands: List[Command] = []
def register(self, command: Command):
self.commands.append(command)
def handle(self, event):
for command in self.commands:
if command.can_handle(event.text):
return command.execute(event)
L - Liskov Substitution Principle (LSP)
Definition: Objects of a superclass should be replaceable with objects of its subclasses.
Application:
# ✅ GOOD: Storage abstraction
class Storage(ABC):
@abstractmethod
def save(self, key: str, value: Any) -> bool:
pass
@abstractmethod
def load(self, key: str) -> Any:
pass
class FirebaseStorage(Storage):
def save(self, key: str, value: Any) -> bool:
# Firebase implementation
pass
def load(self, key: str) -> Any:
# Firebase implementation
pass
class LocalFileStorage(Storage):
def save(self, key: str, value: Any) -> bool:
# File implementation
pass
def load(self, key: str) -> Any:
# File implementation
pass
# Can swap implementations without changing client code
def save_schedule(storage: Storage, schedule):
storage.save("schedule", schedule)
I - Interface Segregation Principle (ISP)
Definition: Clients should not be forced to depend on interfaces they don't use.
Violations:
# ❌ BAD: Fat interface
class BotService:
def handle_message(self): pass
def handle_join(self): pass
def handle_leave(self): pass
def send_broadcast(self): pass
def schedule_task(self): pass
def validate_input(self): pass
# Too many responsibilities
Refactored:
# ✅ GOOD: Segregated interfaces
class MessageHandler(ABC):
@abstractmethod
def handle_message(self, event): pass
class GroupEventHandler(ABC):
@abstractmethod
def handle_join(self, event): pass
@abstractmethod
def handle_leave(self, event): pass
class BroadcastService(ABC):
@abstractmethod
def send_broadcast(self, group_id: str, message: str): pass
class ScheduleService(ABC):
@abstractmethod
def schedule_task(self, task): pass
D - Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.
Violations:
# ❌ BAD: Direct dependency on concrete class
class ScheduleManager:
def __init__(self):
self.firebase = firebase_service.firebase_service_instance # Tight coupling
def save_schedule(self, schedule):
self.firebase.save_group_schedules(schedule)
Refactored:
# ✅ GOOD: Depend on abstraction
class ScheduleRepository(ABC):
@abstractmethod
def save(self, schedule): pass
@abstractmethod
def load(self): pass
class ScheduleManager:
def __init__(self, repository: ScheduleRepository):
self.repository = repository # Depends on abstraction
def save_schedule(self, schedule):
self.repository.save(schedule)
# Concrete implementation
class FirebaseScheduleRepository(ScheduleRepository):
def __init__(self, firebase_service):
self.firebase = firebase_service
def save(self, schedule):
return self.firebase.save_group_schedules(schedule)
def load(self):
return self.firebase.load_group_schedules()
🔧 Refactoring Patterns
1. Extract Class
When: A class is doing too much
# Before
class BotHandler:
def parse_time(self, text): pass
def parse_members(self, text): pass
def validate_time(self, hour, minute): pass
def validate_members(self, members): pass
def format_message(self, action, details): pass
def handle_command(self, event): pass
# After
class InputParser:
def parse_time(self, text): pass
def parse_members(self, text): pass
class InputValidator:
def validate_time(self, hour, minute): pass
def validate_members(self, members): pass
class MessageFormatter:
def format_success(self, action, details): pass
def format_error(self, error): pass
class BotHandler:
def __init__(self, parser, validator, formatter):
self.parser = parser
self.validator = validator
self.formatter = formatter
def handle_command(self, event): pass
2. Extract Method
When: A method is too long or does multiple things
# Before
def handle_time_command(event):
parts = event.text.split()
if len(parts) < 2:
return "Error"
time_str = parts[1]
patterns = [r'^(\d{1,2}):(\d{2})$', r'^(\d{2})(\d{2})$']
hour, minute = None, None
for pattern in patterns:
match = re.match(pattern, time_str)
if match:
hour = int(match.group(1))
minute = int(match.group(2))
break
if not (0 <= hour <= 23):
return "Hour error"
# ... more logic
# After
def handle_time_command(event):
time_str = extract_time_parameter(event.text)
if not time_str:
return format_missing_parameter_error()
hour, minute, error = parse_time_flexible(time_str)
if error:
return error
return update_schedule_time(event.group_id, hour, minute)
def extract_time_parameter(text: str) -> Optional[str]:
parts = text.split(maxsplit=1)
return parts[1] if len(parts) >= 2 else None
3. Introduce Parameter Object
When: Functions have too many parameters
# Before
def update_schedule(group_id, days, hour, minute, timezone, enabled):
pass
# After
@dataclass
class ScheduleConfig:
group_id: str
days: str
hour: int
minute: int
timezone: str = "Asia/Taipei"
enabled: bool = True
def update_schedule(config: ScheduleConfig):
pass
4. Replace Conditional with Polymorphism
When: Complex if/elif chains for different types
# Before
def format_message(message_type, data):
if message_type == "success":
return f"✅ {data['action']}\n{data['details']}"
elif message_type == "error":
return f"❌ {data['error']}\n{data['suggestion']}"
elif message_type == "warning":
return f"⚠️ {data['warning']}"
# After
class Message(ABC):
@abstractmethod
def format(self) -> str:
pass
class SuccessMessage(Message):
def __init__(self, action: str, details: dict):
self.action = action
self.details = details
def format(self) -> str:
return f"✅ {self.action}\n{self.details}"
class ErrorMessage(Message):
def __init__(self, error: str, suggestion: str):
self.error = error
self.suggestion = suggestion
def format(self) -> str:
return f"❌ {self.error}\n{self.suggestion}"
📋 Refactoring Checklist
Before Refactoring
- Write tests for existing functionality
- Identify code smells (long methods, large classes, duplicate code)
- Document current behavior
- Create backup/branch
During Refactoring
- Make small, incremental changes
- Run tests after each change
- Keep commits atomic and descriptive
- Maintain backward compatibility if needed
After Refactoring
- Verify all tests pass
- Check performance hasn't degraded
- Update documentation
- Code review
🚨 Code Smells to Watch For
- Long Method (>20 lines) → Extract Method
- Large Class (>200 lines) → Extract Class
- Long Parameter List (>3 params) → Introduce Parameter Object
- Duplicate Code → Extract Method/Class
- Feature Envy (method uses another class more than its own) → Move Method
- Data Clumps (same group of data together) → Extract Class
- Primitive Obsession (using primitives instead of objects) → Introduce Value Object
- Switch Statements (type checking) → Replace with Polymorphism
🎯 Refactoring Priority for Current Codebase
High Priority
-
Extract Command Handlers - Massive
handle_message()function - Separate Data Access - DataManager does too much
- Introduce Storage Abstraction - Tight coupling to Firebase
Medium Priority
- Extract Parsing Logic - Scattered parsing code
- Introduce Message Formatters - Duplicate formatting code
- Extract Validation - Validation mixed with business logic
Low Priority
- Introduce Value Objects - For ScheduleConfig, MemberGroup
- Extract Helper Functions - Utility functions in main file
📚 References
- Clean Code by Robert C. Martin
- Refactoring by Martin Fowler
- Design Patterns by Gang of Four
- Python SOLID Principles
- Refactoring Guru
Last Updated: 2026-01-16
Maintainer: Code Quality Agent
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!