Code Smell Patterns Reference
Comprehensive catalog of code quality and design smells in Python.
Code Quality Smells
Pattern 1: Duplicate Code
Description: Same or very similar code appears in multiple places.
Impact: Changes must be made in multiple locations, increasing maintenance burden and bug risk.
Detection:
- Identical or nearly identical code blocks
- Copy-pasted functions with minor variations
- Similar logic in multiple methods
Example:
# ❌ Duplicate code
class UserService:
def create_user(self, data):
if not data.get('email'):
raise ValueError("Email required")
if not data.get('name'):
raise ValueError("Name required")
# Create user...
def update_user(self, data):
if not data.get('email'):
raise ValueError("Email required")
if not data.get('name'):
raise ValueError("Name required")
# Update user...
# ✅ Extract common validation
class UserService:
def _validate_user_data(self, data):
if not data.get('email'):
raise ValueError("Email required")
if not data.get('name'):
raise ValueError("Name required")
def create_user(self, data):
self._validate_user_data(data)
# Create user...
def update_user(self, data):
self._validate_user_data(data)
# Update user...
Refactoring: Extract Method, Pull Up Method, Form Template Method
Pattern 2: Magic Numbers
Description: Numeric literals with unclear meaning appearing directly in code.
Impact: Hard to understand intent, difficult to change, easy to make mistakes.
Example:
# ❌ 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
# ✅ 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_RATE
Refactoring: Replace Magic Number with Symbolic Constant
Pattern 3: Hardcoded Values
Description: Configuration values, paths, or strings embedded directly in code.
Impact: Difficult to change for different environments, violates DRY principle.
Example:
# ❌ Hardcoded values
def save_file(data):
with open('/var/app/data/output.txt', 'w') as f:
f.write(data)
def send_email(user):
send_to('admin@example.com', f'User {user.name} registered')
# ✅ Configuration-based
import os
class Config:
OUTPUT_PATH = os.getenv('OUTPUT_PATH', '/var/app/data/output.txt')
ADMIN_EMAIL = os.getenv('ADMIN_EMAIL', 'admin@example.com')
def save_file(data):
with open(Config.OUTPUT_PATH, 'w') as f:
f.write(data)
def send_email(user):
send_to(Config.ADMIN_EMAIL, f'User {user.name} registered')
Refactoring: Introduce Parameter Object, Use Configuration
Pattern 4: Commented-Out Code
Description: Code that has been commented out but not removed.
Impact: Clutters codebase, creates confusion about what's actually used.
Example:
# ❌ Commented code
def process_order(order):
# Old implementation
# validate_order(order)
# calculate_total(order)
# charge_payment(order)
# New implementation
process_payment_v2(order)
send_confirmation(order)
# ✅ Clean code
def process_order(order):
process_payment(order)
send_confirmation(order)
Refactoring: Delete the code (version control preserves history)
Pattern 5: Long Identifier Names
Description: Excessively long variable or function names.
Impact: Reduces readability, makes code harder to scan.
Example:
# ❌ Too long
def calculate_total_price_including_tax_and_shipping_for_customer_order(order):
pass
# ✅ Appropriately named
def calculate_order_total(order):
"""Calculate total including tax and shipping."""
pass
Refactoring: Rename, use docstrings for detailed explanations
Pattern 6: Inconsistent Naming
Description: Similar concepts use different naming conventions.
Impact: Confusing, harder to learn codebase.
Example:
# ❌ Inconsistent
def getUserData(user_id):
pass
def fetch_product_info(product_id):
pass
def RetrieveOrderDetails(order_id):
pass
# ✅ Consistent
def get_user(user_id):
pass
def get_product(product_id):
pass
def get_order(order_id):
pass
Refactoring: Standardize naming conventions
Design Smells
Pattern 7: God Class
Description: Class that knows too much or does too much.
Impact: Hard to understand, test, and maintain. Violates Single Responsibility Principle.
Indicators:
- More than 20 methods
- Touches many different concerns
- Large number of fields
- Many dependencies
Example:
# ❌ God class
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_user(self, username, password): pass
def send_welcome_email(self, user): pass
def send_password_reset_email(self, user): pass
def generate_password_reset_token(self, user): pass
def validate_password_strength(self, password): pass
def hash_password(self, password): pass
def log_user_activity(self, user, action): pass
def export_user_data(self, user): pass
def import_user_data(self, data): pass
def calculate_user_statistics(self, user): pass
def get_user_recommendations(self, user): pass
# ... 20+ more methods
# ✅ Split by responsibility
class UserRepository:
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 UserAuthenticationService:
def authenticate(self, username, password): pass
def hash_password(self, password): pass
def validate_password_strength(self, password): pass
def generate_reset_token(self, user): pass
class UserNotificationService:
def send_welcome_email(self, user): pass
def send_password_reset_email(self, user): pass
class UserAnalyticsService:
def log_activity(self, user, action): pass
def calculate_statistics(self, user): pass
def get_recommendations(self, user): pass
Refactoring: Extract Class, Extract Service
Pattern 8: Feature Envy
Description: Method uses more features from another class than its own.
Impact: Poor cohesion, method is in the wrong place.
Example:
# ❌ Feature envy
class Order:
def __init__(self, items, customer):
self.items = items
self.customer = customer
def calculate_total(self):
# Uses customer features heavily
discount_rate = self.customer.get_discount_rate()
is_premium = self.customer.is_premium_member()
shipping_address = self.customer.get_shipping_address()
total = sum(item.price for item in self.items)
if is_premium:
total *= (1 - discount_rate)
if shipping_address.country != 'US':
total += 50 # International shipping
return total
# ✅ Move method 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 __init__(self, items, customer):
self.items = items
self.customer = customer
def calculate_total(self):
return self.customer.calculate_order_total(self.items)
Refactoring: Move Method, Extract Method
Pattern 9: Inappropriate Intimacy
Description: Classes that access each other's internal details too much.
Impact: Tight coupling, changes ripple across classes.
Example:
# ❌ Inappropriate intimacy
class BankAccount:
def __init__(self):
self.balance = 0
self._transaction_log = []
class AccountManager:
def transfer_funds(self, from_account, to_account, amount):
# Directly accessing internals
if from_account.balance >= amount:
from_account.balance -= amount
to_account.balance += amount
from_account._transaction_log.append(f"Transferred {amount}")
to_account._transaction_log.append(f"Received {amount}")
# ✅ Use proper encapsulation
class BankAccount:
def __init__(self):
self._balance = 0
self._transaction_log = []
def withdraw(self, amount):
if self._balance >= amount:
self._balance -= amount
self._log_transaction(f"Withdrew {amount}")
return True
return False
def deposit(self, amount):
self._balance += amount
self._log_transaction(f"Deposited {amount}")
def _log_transaction(self, message):
self._transaction_log.append(message)
class AccountManager:
def transfer_funds(self, from_account, to_account, amount):
if from_account.withdraw(amount):
to_account.deposit(amount)
return True
return False
Refactoring: Move Method, Hide Delegate, Encapsulate Field
Pattern 10: Data Clumps
Description: Same group of variables appears together in multiple places.
Impact: Suggests missing abstraction, code duplication.
Example:
# ❌ Data clumps
def create_user(name, email, phone, address_street, address_city, address_zip):
pass
def update_user(user_id, name, email, phone, address_street, address_city, address_zip):
pass
def send_mail(name, email, phone, address_street, address_city, address_zip):
pass
# ✅ Introduce 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 create_user(contact_info: ContactInfo):
pass
def update_user(user_id: int, contact_info: ContactInfo):
pass
def send_mail(contact_info: ContactInfo):
pass
Refactoring: Extract Class, Introduce Parameter Object
Pattern 11: Primitive Obsession
Description: Using primitive types instead of small objects for simple tasks.
Impact: Missing domain concepts, validation logic scattered.
Example:
# ❌ Primitive obsession
def process_order(order_id: str, customer_email: str, total_cents: int):
# Validation scattered
if '@' not in customer_email:
raise ValueError("Invalid email")
if total_cents < 0:
raise ValueError("Invalid amount")
# Process...
# ✅ Use value objects
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if '@' not in self.address:
raise ValueError("Invalid email")
@dataclass
class Money:
cents: int
def __post_init__(self):
if self.cents < 0:
raise ValueError("Amount cannot be negative")
@property
def dollars(self):
return self.cents / 100
@dataclass
class OrderId:
value: str
def process_order(order_id: OrderId, email: Email, total: Money):
# Validation is built into the types
# Process...
Refactoring: Replace Data Value with Object, Introduce Value Object
Pattern 12: Long Parameter List
Description: Functions with too many parameters.
Impact: Hard to understand, remember, and use correctly.
Example:
# ❌ Long parameter list
def create_invoice(
customer_id,
customer_name,
customer_email,
items,
subtotal,
tax_rate,
tax_amount,
discount_code,
discount_amount,
shipping_cost,
total,
payment_method,
billing_address,
shipping_address
):
pass
# ✅ Use parameter objects
from dataclasses import dataclass
from typing import List
@dataclass
class Customer:
id: str
name: str
email: str
@dataclass
class InvoiceDetails:
items: List[object]
subtotal: float
tax_rate: float
tax_amount: float
discount_code: str
discount_amount: float
shipping_cost: float
total: float
@dataclass
class PaymentInfo:
method: str
billing_address: str
shipping_address: str
def create_invoice(customer: Customer, details: InvoiceDetails, payment: PaymentInfo):
pass
Refactoring: Introduce Parameter Object, Preserve Whole Object
Pattern 13: Shotgun Surgery
Description: Single change requires modifications across many classes.
Impact: Easy to miss changes, high risk of bugs.
Example:
# ❌ Shotgun surgery - adding a new payment method requires changes everywhere
class OrderProcessor:
def process(self, order):
if order.payment_method == 'credit_card':
# handle credit card
elif order.payment_method == 'paypal':
# handle paypal
# Need to add cash here
class InvoiceGenerator:
def generate(self, order):
if order.payment_method == 'credit_card':
# credit card invoice
elif order.payment_method == 'paypal':
# paypal invoice
# Need to add cash here
class Receipt:
def create(self, order):
if order.payment_method == 'credit_card':
# credit card receipt
elif order.payment_method == 'paypal':
# paypal receipt
# Need to add cash here
# ✅ Use polymorphism
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount): pass
@abstractmethod
def generate_invoice(self): pass
@abstractmethod
def create_receipt(self): pass
class CreditCardPayment(PaymentMethod):
def process(self, amount): pass
def generate_invoice(self): pass
def create_receipt(self): pass
class PayPalPayment(PaymentMethod):
def process(self, amount): pass
def generate_invoice(self): pass
def create_receipt(self): pass
class CashPayment(PaymentMethod): # Just add one new class
def process(self, amount): pass
def generate_invoice(self): pass
def create_receipt(self): pass
class OrderProcessor:
def process(self, order):
order.payment_method.process(order.total)
Refactoring: Move Method, Inline Class, Replace Conditional with Polymorphism
Pattern 14: Lazy Class
Description: Class that doesn't do enough to justify its existence.
Impact: Unnecessary complexity, maintenance overhead.
Example:
# ❌ Lazy class
class UserNameFormatter:
def format(self, name):
return name.strip().title()
class User:
def __init__(self, name):
formatter = UserNameFormatter()
self.name = formatter.format(name)
# ✅ Inline the class
class User:
def __init__(self, name):
self.name = name.strip().title()
Refactoring: Inline Class, Collapse Hierarchy
Detection Strategies
Metrics-Based Detection
Function/Method Complexity:
- Lines of code > 50
- Cyclomatic complexity > 10
- Nesting depth > 4
- Parameters > 5
Class Complexity:
- Methods > 15 (large class)
- Methods > 20 (god class)
- Fields > 10
- Dependencies > 7
Code Duplication:
- Identical code blocks > 5 lines
- Similar code with minor variations
- Token-based similarity > 80%
Pattern-Based Detection
Look for:
- Multiple methods accessing same external data (Feature Envy)
- Same parameter groups (Data Clumps)
- Primitive types for domain concepts (Primitive Obsession)
- Direct field access between classes (Inappropriate Intimacy)
- Long if-else chains on type (Switch Statements smell)
Tool-Based Detection
Python tools:
radon- Cyclomatic complexity, maintainability indexpylint- General code qualityflake8- Style and qualitymccabe- Complexity checkervulture- Dead codebandit- Security issues
Prioritization
High Priority (Fix Soon):
- God classes
- Shotgun surgery
- Feature envy
- Long parameter lists
Medium Priority (Plan Refactoring):
- Large classes
- Duplicate code
- Data clumps
- Primitive obsession
Low Priority (Nice to Have):
- Magic numbers
- Lazy classes
- Long method names
- Inconsistent naming