Identify and report code smells indicating poor design or maintainability issues in Python code, including duplicate code, magic numbers, hardcoded values, God classes, feature envy, inappropriate intimacy, data clumps, primitive obsession, and long parameter lists. Use when conducting code quality audits, preparing for refactoring, improving codebase maintainability, or performing design reviews. Produces markdown reports with severity ratings, locations, descriptions, and specific refactoring recommendations with before/after examples. Triggers when users ask to find code smells, identify design issues, suggest refactorings, improve code quality, or detect maintainability problems.
90
88%
Does it follow best practices?
Impact
93%
1.43xAverage score across 3 eval scenarios
Passed
No known issues
Identify code quality and design smells in Python codebases, then provide specific refactoring recommendations to improve maintainability and design.
Define what to analyze:
Questions to ask:
Determine scope:
# Check project structure
ls -la
# Count Python files
find . -name "*.py" | wc -l
# Identify large files (potential smells)
find . -name "*.py" -exec wc -l {} + | sort -rn | head -10Use multiple detection strategies.
Use the bundled script for AST-based analysis:
# Scan entire project
python scripts/detect_smells.py /path/to/project
# Exclude specific directories
python scripts/detect_smells.py /path/to/project venv,tests,docsWhat it detects:
Read the code to identify design smells. See smell-patterns.md for comprehensive catalog.
Look for:
Code Quality Smells:
Design Smells:
Search patterns:
# Find long files (potential large classes)
find . -name "*.py" -exec wc -l {} + | awk '$1 > 300'
# Find magic numbers (basic pattern)
grep -r "[^0-9]\d\{3,\}" --include="*.py" .
# Find hardcoded paths
grep -r '"/.*/"' --include="*.py" .
# Find commented code
grep -r "^[ ]*#.*def \|^[ ]*#.*class " --include="*.py" .radon - Complexity metrics:
# Install
pip install radon
# Check cyclomatic complexity
radon cc /path/to/project -a
# Maintainability index
radon mi /path/to/project
# Show only complex functions
radon cc /path/to/project -ncpylint - Code quality:
pip install pylint
# Check for code smells
pylint /path/to/project --disable=C0111 # Disable docstring warningsOrganize findings by severity and type.
See smell-patterns.md for detailed patterns.
Immediate attention needed:
Should refactor soon:
Nice to improve:
For each smell, determine appropriate refactoring.
See refactoring-patterns.md for detailed examples.
Common mappings:
| Smell | Refactoring |
|---|---|
| God class | Extract Class, Extract Service |
| Feature envy | Move Method |
| Duplicate code | Extract Method, Pull Up Method |
| Data clumps | Introduce Parameter Object |
| Magic numbers | Replace with Symbolic Constant |
| Long parameter list | Introduce Parameter Object |
| Primitive obsession | Replace Data Value with Object |
| Inappropriate intimacy | Move Method, Hide Delegate |
| Shotgun surgery | Move Method, Inline Class |
| Lazy class | Inline Class, Collapse Hierarchy |
Create a structured markdown report with specific refactoring recommendations.
Project: [Project Name] Analyzed: [Date] Scope: [Directories analyzed] Excluded: [Excluded directories]
Total: N code smells detected
Location: src/services/user_manager.py:15
Class: UserManager
Description: Class has 28 methods handling multiple unrelated responsibilities (user CRUD, authentication, email, logging, analytics).
Impact:
Refactoring: Extract Class
Recommendation:
Split into focused classes by responsibility:
# Before: God class with 28 methods
class UserManager:
def create_user(self, data): pass
def update_user(self, user_id, data): pass
def delete_user(self, user_id): pass
def authenticate(self, username, password): pass
def hash_password(self, password): pass
def send_welcome_email(self, user): pass
def send_password_reset(self, user): pass
def log_activity(self, user, action): pass
def get_statistics(self, user): pass
# ... 19 more methods
# After: Split by responsibility
class UserRepository:
"""Handles user persistence."""
def create(self, data): pass
def update(self, user_id, data): pass
def delete(self, user_id): pass
def find_by_id(self, user_id): pass
class UserAuthService:
"""Handles authentication."""
def authenticate(self, username, password): pass
def hash_password(self, password): pass
def validate_password_strength(self, password): pass
class UserNotificationService:
"""Handles user notifications."""
def send_welcome_email(self, user): pass
def send_password_reset(self, user): pass
class UserAnalyticsService:
"""Handles user analytics."""
def log_activity(self, user, action): pass
def get_statistics(self, user): passPriority: High - Refactor within 1-2 sprints
Location: src/models/order.py:45
Method: Order.calculate_total()
Description: Method uses customer features heavily (discount_rate, is_premium, shipping_address) rather than its own class features.
Impact:
Refactoring: Move Method
Recommendation:
Move calculation logic to Customer class:
# Before: Feature envy
class Order:
def calculate_total(self):
discount = self.customer.get_discount_rate()
is_premium = self.customer.is_premium_member()
address = self.customer.get_shipping_address()
total = sum(item.price for item in self.items)
if is_premium:
total *= (1 - discount)
if address.country != 'US':
total += 50
return total
# After: Move to appropriate class
class Customer:
def calculate_order_total(self, items):
total = sum(item.price for item in items)
if self.is_premium_member():
total *= (1 - self.get_discount_rate())
if self.shipping_address.country != 'US':
total += 50
return total
class Order:
def calculate_total(self):
return self.customer.calculate_order_total(self.items)Priority: High - Refactor within 2 weeks
Locations:
src/validators/user_validator.py:23-35src/validators/profile_validator.py:45-57Description: Identical email and name validation logic appears in two validators.
Impact:
Refactoring: Extract Method
Recommendation:
Extract common validation into shared utility:
# Before: Duplicate code
class UserValidator:
def validate(self, data):
if not data.get('email') or '@' not in data['email']:
raise ValueError("Invalid email")
if not data.get('name') or len(data['name']) < 2:
raise ValueError("Invalid name")
# ... more validation
class ProfileValidator:
def validate(self, data):
if not data.get('email') or '@' not in data['email']:
raise ValueError("Invalid email")
if not data.get('name') or len(data['name']) < 2:
raise ValueError("Invalid name")
# ... more validation
# After: Extract common validation
class ValidationHelpers:
@staticmethod
def validate_email(email):
if not email or '@' not in email:
raise ValueError("Invalid email")
@staticmethod
def validate_name(name):
if not name or len(name) < 2:
raise ValueError("Invalid name")
class UserValidator:
def validate(self, data):
ValidationHelpers.validate_email(data.get('email'))
ValidationHelpers.validate_name(data.get('name'))
# ... more validation
class ProfileValidator:
def validate(self, data):
ValidationHelpers.validate_email(data.get('email'))
ValidationHelpers.validate_name(data.get('name'))
# ... more validationPriority: Medium - Refactor within month
Locations:
src/services/email_service.py:12 (6 parameters)src/services/sms_service.py:23 (6 parameters)src/services/notification_service.py:34 (6 parameters)Description: Same parameter group (name, email, phone, address_street, address_city, address_zip) appears in multiple methods.
Impact:
Refactoring: Introduce Parameter Object
Recommendation:
Create ContactInfo value object:
# Before: Data clumps
def send_email(name, email, phone, street, city, zip_code):
pass
def send_sms(name, email, phone, street, city, zip_code):
pass
def send_notification(name, email, phone, street, city, zip_code):
pass
# After: Parameter object
from dataclasses import dataclass
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class ContactInfo:
name: str
email: str
phone: str
address: Address
def send_email(contact: ContactInfo):
pass
def send_sms(contact: ContactInfo):
pass
def send_notification(contact: ContactInfo):
passPriority: Medium - Refactor within month
Location: src/billing/calculator.py:67-78
Description: Multiple unexplained numeric literals (1000, 0.15, 500, 0.10, 0.05).
Impact:
Refactoring: Replace Magic Number with Symbolic Constant
Recommendation:
Use named constants:
# Before: Magic numbers
def calculate_discount(price):
if price > 1000:
return price * 0.15
elif price > 500:
return price * 0.10
else:
return price * 0.05
# After: Named constants
BULK_ORDER_THRESHOLD = 1000
LARGE_ORDER_THRESHOLD = 500
BULK_DISCOUNT_RATE = 0.15
LARGE_DISCOUNT_RATE = 0.10
STANDARD_DISCOUNT_RATE = 0.05
def calculate_discount(price):
if price > BULK_ORDER_THRESHOLD:
return price * BULK_DISCOUNT_RATE
elif price > LARGE_ORDER_THRESHOLD:
return price * LARGE_DISCOUNT_RATE
else:
return price * STANDARD_DISCOUNT_RATEPriority: Low - Refactor when touching this code
Add to CI/CD:
# Complexity checks
radon cc src/ -nc # Fail on complex functions
# Code quality
pylint src/ --fail-under=8.0Code review checklist:
Regular audits:
Share report with team and discuss refactoring priorities.
Presentation tips:
Get team input:
Start with obvious smells:
Consider context:
Prioritize by impact:
Refactor incrementally:
Use tools as guides:
Not every pattern is a smell:
Long methods may be acceptable:
Large classes may be justified:
Magic numbers may be OK:
Consider trade-offs:
For comprehensive smell patterns and refactoring techniques:
0f00a4f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.