Review Python code for adherence to SOLID principles (SRP, OCP, LSP, ISP, DIP). Use when reviewing Python code for maintainability, testability, and design quality.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/example-1-srp-violation/expected.mdexamples/example-1-srp-violation/input.pyexamples/example-1-srp-violation/scenario.mdexamples/example-2-ocp-violation/expected.mdexamples/example-2-ocp-violation/input.pyexamples/example-2-ocp-violation/scenario.mdexamples/example-3-dip-violation/expected.mdexamples/example-3-dip-violation/input.pyexamples/example-3-dip-violation/scenario.mdexamples/example-4-complex-good/expected.mdexamples/example-4-complex-good/input.pyexamples/example-4-complex-good/scenario.mdThis skill evaluates Python code against the five SOLID principles of object-oriented design. SOLID principles guide the creation of maintainable, flexible, and testable code.
Scope: This skill focuses exclusively on SOLID principle violations and design patterns, not general code quality, performance, or formatting issues.
The Five Principles:
Focus only on SOLID principle violations.
Look for:
Red Flags:
# BAD: Multiple responsibilities
class Order:
def calculate_total(self): pass # Calculation
def save_to_database(self): pass # Persistence
def send_email(self): pass # Notification
Good Pattern:
# GOOD: Single responsibilities
class Order:
pass # Just data
class OrderCalculator:
def calculate_total(self, order): pass
class OrderRepository:
def save(self, order): pass
class OrderNotifier:
def send_confirmation(self, order): pass
Look for:
isinstance() or type()Red Flags:
# BAD: Must modify for new shapes
def calculate_area(shape):
if shape.type == "circle":
return 3.14 * shape.radius ** 2
elif shape.type == "rectangle":
return shape.width * shape.height
# Adding triangle requires modifying this function
Good Pattern:
# GOOD: Extend via inheritance or protocol
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self): pass
class Circle(Shape):
def calculate_area(self):
return 3.14 * self.radius ** 2
class Rectangle(Shape):
def calculate_area(self):
return self.width * self.height
Look for:
NotImplementedErrorRed Flags:
# BAD: Square can't substitute for Rectangle
class Rectangle:
def set_width(self, width):
self._width = width
def set_height(self, height):
self._height = height
class Square(Rectangle):
def set_width(self, width):
self._width = width
self._height = width # Unexpected side effect!
Good Pattern:
# GOOD: Proper abstraction hierarchy
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def get_area(self): pass
class Rectangle(Shape):
def get_area(self):
return self._width * self._height
class Square(Shape): # Not a Rectangle!
def get_area(self):
return self._side ** 2
Look for:
NotImplementedErrorRed Flags:
# BAD: Fat interface forces all implementations
from abc import ABC, abstractmethod
class MultiFunctionDevice(ABC):
@abstractmethod
def print_document(self, doc): pass
@abstractmethod
def scan_document(self): pass
@abstractmethod
def fax_document(self, doc): pass
class SimplePrinter(MultiFunctionDevice):
def print_document(self, doc):
print(doc)
def scan_document(self):
raise NotImplementedError # Forced to implement!
def fax_document(self, doc):
raise NotImplementedError # Forced to implement!
Good Pattern:
# GOOD: Small, focused interfaces
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print_document(self, doc): pass
class Scanner(ABC):
@abstractmethod
def scan_document(self): pass
class SimplePrinter(Printer):
def print_document(self, doc):
print(doc)
class MultiFunctionPrinter(Printer, Scanner):
def print_document(self, doc):
print(doc)
def scan_document(self):
return "scanned"
Look for:
__init__Red Flags:
# BAD: Direct dependency on concrete implementation
import sqlite3
class UserService:
def __init__(self):
# Hard-coded dependency
self.db = sqlite3.connect('users.db')
def register_user(self, name, email):
# Business logic coupled to SQLite
self.db.execute("INSERT INTO users VALUES (?, ?)", (name, email))
Good Pattern:
# GOOD: Depend on abstraction via injection
from abc import ABC, abstractmethod
class UserRepository(ABC):
@abstractmethod
def save(self, user_data): pass
class SQLiteUserRepository(UserRepository):
def __init__(self, db_path):
self.db = sqlite3.connect(db_path)
def save(self, user_data):
self.db.execute("INSERT INTO users VALUES (?, ?)",
(user_data['name'], user_data['email']))
class UserService:
def __init__(self, repository: UserRepository):
self.repository = repository # Injected dependency
def register_user(self, name, email):
self.repository.save({'name': name, 'email': email})
Prefer:
Protocol over ABC for structural subtyping (when runtime enforcement not needed)Example with Protocol:
from typing import Protocol
class Repository(Protocol):
def save(self, data: dict) -> None: ...
def find(self, id: str) -> dict | None: ...
# No inheritance needed - duck typing with type hints
class InMemoryRepository:
def save(self, data: dict) -> None: pass
def find(self, id: str) -> dict | None: pass
For each SOLID violation found:
Always flag:
NotImplementedError in production code (ISP/LSP violation)isinstance() checks for behavior switching (OCP violation)Flag if significant:
Don't flag:
if total > 100:) - these are logic, not type switchingSOLID principles are guidelines, not absolute rules. Consider:
Suggest refactoring when:
Defer refactoring when:
❌ Don't: Flag every conditional as an OCP violation
❌ Don't: Suggest SOLID refactoring for simple scripts
❌ Don't: Recommend ABC when Protocol would work better
❌ Don't: Focus only on violations without explaining impact
❌ Don't: Suggest over-engineering with excessive abstraction layers
Context: Order class doing too much
Code:
class Order:
def calculate_total(self):
return sum(item.price for item in self.items)
def save_to_database(self, db):
db.execute("INSERT INTO orders VALUES (?)", (self.total,))
def send_confirmation_email(self, email_service):
email_service.send("Order confirmed", self.customer_email)
Review Output:
---
**File**: order.py
**Lines**: 1-8
**Principle**: SRP
**Issue**: Order class has three responsibilities: calculation, persistence, and notification
**Impact**: Changes to email format require modifying Order class; difficult to test calculation logic without mocking database and email
**Suggestion**: Extract into separate classes: OrderCalculator, OrderRepository, OrderNotificationService
**Example**:
class Order:
pass # Data only
class OrderCalculator:
def calculate_total(self, order):
return sum(item.price for item in order.items)
class OrderRepository:
def save(self, order, db):
db.execute("INSERT INTO orders VALUES (?)", (order.total,))
class OrderNotifier:
def send_confirmation(self, order, email_service):
email_service.send("Order confirmed", order.customer_email)
---
Context: Service depends on concrete implementation
Code:
import sqlite3
class UserService:
def __init__(self):
self.db = sqlite3.connect('users.db')
def create_user(self, name, email):
self.db.execute("INSERT INTO users VALUES (?, ?)", (name, email))
Review Output:
---
**File**: user_service.py
**Lines**: 3-8
**Principle**: DIP
**Issue**: UserService directly depends on SQLite implementation; cannot test without real database
**Impact**: Impossible to write unit tests with mocks; cannot switch to different database without modifying UserService
**Suggestion**: Introduce repository abstraction and inject via constructor
**Example**:
from typing import Protocol
class UserRepository(Protocol):
def save(self, name: str, email: str) -> None: ...
class SQLiteUserRepository:
def __init__(self, db_path: str):
self.db = sqlite3.connect(db_path)
def save(self, name: str, email: str) -> None:
self.db.execute("INSERT INTO users VALUES (?, ?)", (name, email))
class UserService:
def __init__(self, repository: UserRepository):
self.repository = repository
def create_user(self, name: str, email: str) -> None:
self.repository.save(name, email)
# Easy to test with mock
mock_repo = Mock(spec=UserRepository)
service = UserService(mock_repo)
---
Test cases are located in skills/collaboration/solid-principles-review/examples/:
example-1-srp-violation: Order class with multiple responsibilities
example-2-ocp-violation: Type-based conditionals requiring modification
example-3-dip-violation: Hard-coded database dependency
example-4-complex-good: Well-designed code following SOLID
Run tests by launching subagents with:
Apply skills/collaboration/solid-principles-review/SKILL.md to review [test file path]