Skip to content

SOLID Code Smells

When to Use This Guide

During code reviews, use these smells to detect SOLID violations early.

Code Smell: Large Class (SRP Violation)

Indicators: - Class > 300 lines - > 10 public methods - Many unrelated private methods - Requires scrolling to understand

Detection:

# Find large classes
grep -c "public function" *.php | awk -F: '$2 > 10'

Questions to Ask: - Can I describe this class without "and" or "or"? - Do all methods relate to the same responsibility? - If I change feature X, does this class change?

Refactor: Extract Class, Extract Module.

Code Smell: Switch/Case on Type (OCP Violation)

Indicators:

switch ($type) {
    case 'typeA': /* ... */ break;
    case 'typeB': /* ... */ break;
    case 'typeC': /* ... */ break;
}

Or:

if (obj instanceof ClassA) { /* ... */ }
else if (obj instanceof ClassB) { /* ... */ }
else if (obj instanceof ClassC) { /* ... */ }

Why it smells: - Adding new type requires modifying this code - Same switch pattern repeated in multiple places - Violates OCP (closed for modification)

Refactor: Replace Conditional with Polymorphism.

Code Smell: Empty Override (LSP Violation)

Indicators:

class Base:
    def method(self): pass

class Derived(Base):
    def method(self):
        pass  # Empty: does nothing

Or:

class Derived extends Base {
  method() {
    throw new Error("Not supported");
  }
}

Why it smells: - Subtype refuses inherited behavior - Runtime errors instead of compile-time safety - Violates substitutability

Refactor: Separate hierarchies, don't inherit what you can't fulfill.

Code Smell: Feature Envy (SRP/ISP Violation)

Indicators:

class OrderReport {
    public function generate(Order $order) {
        $customer = $order->getCustomer();
        $email = $customer->getEmail();
        $name = $customer->getName();
        $address = $customer->getAddress();
        $city = $customer->getCity();
        // Uses Customer data more than Order data
    }
}

Why it smells: - Class is more interested in another class's data - Indicates misplaced responsibility

Refactor: Move method to the envied class.

Code Smell: Data Clumps (ISP Violation)

Indicators: - Same group of parameters passed together repeatedly

function createUser(name: string, email: string, address: string, city: string, zip: string) {}
function updateUser(id: string, name: string, email: string, address: string, city: string, zip: string) {}
function validateUser(name: string, email: string, address: string, city: string, zip: string) {}

Why it smells: - Missing abstraction - Indicates a concept that deserves its own type

Refactor: Introduce Parameter Object.

interface UserData {
  name: string;
  email: string;
  address: string;
  city: string;
  zip: string;
}

function createUser(userData: UserData) {}

Code Smell: Shotgun Surgery (SRP/OCP Violation)

Indicators: - One conceptual change requires editing many classes - Example: Adding a new field requires changing 10+ files

Why it smells: - Responsibility is spread across many classes - High coupling

Refactor: Move related code together (Move Method, Move Field).

Code Smell: Inappropriate Intimacy (DIP Violation)

Indicators:

class OrderProcessor:
    def process(self, order):
        # Directly accesses database internals
        connection = MySQLConnection()
        connection.host = "localhost"
        connection.port = 3306
        connection.connect()
        connection.execute("INSERT INTO orders ...")

Why it smells: - High-level code knows low-level details - Tight coupling to concrete implementation

Refactor: Depend on abstraction, inject dependency.

Code Smell: Primitive Obsession (SRP Violation)

Indicators: - Using primitives instead of domain objects

function processPayment(amount: number, currency: string, cardNumber: string, cvv: string, expiry: string) {}

Why it smells: - Validation logic scattered - No encapsulation of related data

Refactor: Introduce Value Object.

class Money {
  constructor(readonly amount: number, readonly currency: string) {}
}

class CreditCard {
  constructor(readonly number: string, readonly cvv: string, readonly expiry: string) {}
}

function processPayment(amount: Money, card: CreditCard) {}

Code Smell: Long Parameter List (ISP Violation)

Indicators: - > 4 parameters

public function createInvoice($customer, $items, $taxRate, $discount, $currency, $dueDate, $notes) {}

Why it smells: - Hard to remember parameter order - Often indicates missing abstraction

Refactor: Introduce Parameter Object, Preserve Whole Object.

Code Smell: Comments Explaining "What" (SRP Violation)

Indicators:

# Calculate discount for premium customers
if customer.type == 'premium':
    discount = 0.1
else:
    discount = 0

Why it smells: - Code isn't self-explanatory - Comment necessary because logic is misplaced

Refactor: Extract Method.

def calculate_discount(customer):
    return 0.1 if customer.is_premium() else 0

discount = calculate_discount(customer)

Detection Checklist

During code review, ask: - Can I describe each class's purpose in one sentence? - Are there switch/if-else chains on type? - Do subtypes throw "not supported" errors? - Are dependencies injected or created internally? - Are interfaces small and focused? - Is behavior co-located with data? - Are abstractions generic, not tied to one implementation?