God Object (God Class) is a class that knows too much or does too much - centralizes control and violates SRP by having multiple responsibilities. Detection: Class >500 lines, >20 methods, imports from 5+ different domains (database, email, logging, caching, validation, business logic), high change frequency (modified for unrelated features). Example: class UserManager {createUser(), deleteUser(), sendEmail(), validateEmail(), hashPassword(), queryDatabase(), cacheUser(), logActivity(), exportToCSV(), generateReport()} - handles user management, email, validation, database, caching, logging, reporting. SRP violation: 7+ reasons to change. Refactoring: Extract services by domain: UserService (create, delete), EmailService (send, validate), AuthService (hash password), UserRepository (database), CacheService, Logger, ReportGenerator. Benefits: Each service single responsibility, easier testing, parallel development, reduced merge conflicts. God Object risks: Hard to test (many dependencies), tight coupling (changes ripple), merge conflicts (everyone modifies same file), difficult onboarding (10K line class). Detection tools: SonarQube flags >500 line classes, high cyclomatic complexity (>50). Modern: Microservices architecture prevents God Objects at service level.
Solid Antipatterns FAQ & Answers
40 expert Solid Antipatterns answers researched from official documentation. Every answer cites authoritative sources you can verify.
unknown
40 questionsShotgun Surgery: Making single conceptual change requires modifying many classes - indicates scattered responsibility violating SRP. Detection: Adding feature touches 5+ files, single bug fix modifies 10+ locations, grep for feature name returns 20+ files. Example: Adding payment method requires changes in OrderController, OrderService, PaymentValidator, EmailService, InvoiceGenerator, AnalyticsTracker, DatabaseSchema, ConfigFile - payment logic scattered. SRP violation: Payment responsibility spread across system instead of centralized. Refactoring: Gather related changes together. Extract PaymentService encapsulating all payment logic. Use Observer pattern for cross-cutting concerns (analytics, email) instead of direct calls. Benefits: Single place to modify for payment changes, easier testing (mock PaymentService), clearer ownership. Opposite of God Object but equally bad: God Object = everything in one place (too cohesive), Shotgun Surgery = one concept scattered everywhere (too decoupled). Causes: Premature optimization, copy-paste programming, lack of abstraction. Prevention: Ask 'Where does this responsibility belong?' before adding code. Use feature folders/modules grouping related code. Modern tools: CodeScene detects change coupling (files changed together), suggests refactoring candidates.
Feature Envy: Method more interested in other class's data than its own - indicates misplaced responsibility violating SRP. Detection: Method calls other object's getters 5+ times, method uses other class's data more than own class data, method would make more sense in other class. Example: class Order {getTotal(customer: Customer): number {let total = this.basePrice; if(customer.isPremium()) total *= 0.9; if(customer.getLoyaltyPoints() > 100) total *= 0.95; if(customer.getRegion() === 'EU') total += total * 0.2; return total;}} - Order.getTotal() obsessed with Customer data. SRP violation: Pricing logic uses customer's responsibility, should be in Customer or separate PricingService. Refactoring: Move method to Customer: customer.calculatePrice(order). Or extract service: pricingService.calculate(order, customer). Benefits: Methods near data they use, clearer responsibility boundaries, easier testing. Law of Demeter: Talk to friends, not strangers. order.getTotal() shouldn't know about customer.getRegion().getTaxRate(). Exception: DTOs/Value Objects intentionally accessed by services - acceptable when clear separation between data and behavior. Modern: Domain-Driven Design puts behavior with data in Aggregates.
Blob (Lava Flow): Monolithic class with conditional logic for every variation - violates OCP by requiring modification for each new variant. Detection: Class with 10+ if/switch statements, conditionals on type/state scattered throughout, adding variant requires touching 20+ lines. Example: class PaymentProcessor {process(payment: Payment) {if(payment.type === 'credit') {validateCredit(); chargeCredit();} else if(payment.type === 'paypal') {validatePayPal(); chargePayPal();} else if(payment.type === 'crypto') {validateCrypto(); chargeCrypto();}} validate(payment) {if... else if...} refund(payment) {if... else if...}} - every method has conditionals. OCP violation: Adding Bitcoin payment requires modifying process(), validate(), refund(), getStatus(), generateReceipt(). Refactoring: Strategy pattern - interface PaymentStrategy {process(), validate(), refund()}. Implementations: CreditCardStrategy, PayPalStrategy, CryptoStrategy. Context: class PaymentProcessor {constructor(private strategy: PaymentStrategy) {} process(payment) {this.strategy.process(payment);}}. Benefits: Add variant without modifying existing code, each strategy independently testable, strategies composable. Detection: Cyclomatic complexity >20, too many paths through code. Modern: TypeScript discriminated unions help but don't prevent violation - still need polymorphism for extensibility.
Refused Bequest: Subclass inherits methods it doesn't want/need, refuses to use them - violates LSP by not honoring parent contract. Detection: Subclass throws NotImplementedException, empty method overrides, methods returning null/default where parent returns real values. Example: class Bird {fly() {/* flying logic */}} class Ostrich extends Bird {fly() {throw new Error('Cannot fly');}} - Ostrich refuses Bird.fly(). LSP violation: Client expecting Bird gets exception. Refactoring: Interface segregation - split Bird into Flyable and NonFlyable. Use composition: class Bird {flyBehavior?: FlyBehavior}. Or separate hierarchies: FlyingBird, FlightlessBird. Why harmful: Violates polymorphism (can't substitute), forces type checking (if bird instanceof Ostrich), brittle inheritance (adding capability to parent forces on all children). Root cause: Misusing inheritance - 'is-a' relationship conceptually but not behaviorally. Stack extends ArrayList refuses get(index) - should use composition. ReadOnlyList extends List refuses add() - should implement separate interface. Prevention: Favor composition over inheritance, use interfaces for capabilities, apply LSP test (can subclass replace parent without breaking?). Modern: TypeScript interfaces enable multiple small contracts without inheritance.
Interface Pollution (Fat Interface): Interface with too many methods forcing clients to depend on methods they don't use - violates ISP. Detection: Interface >10 methods, implementations throw NotImplementedException for unused methods, clients only use subset of interface, interface serves multiple unrelated roles. Example: interface Worker {work(), eat(), sleep(), commute(), getPaid(), attendMeeting(), submitTimesheet(), useTools(), cleanWorkspace(), train()} - 10 methods. Robot implements Worker but throws errors for eat(), sleep(), commute(). ISP violation: Robot forced to implement biological/human methods. Refactoring: Split into role interfaces: Workable {work()}, Biological {eat(), sleep()}, Commutable {commute()}, Payable {getPaid()}, Trainable {train()}. Human implements all, Robot implements Workable + Trainable. Benefits: Clients depend on minimal interface, easier testing (mock only needed methods), clear contracts. Similar violations: Printer {print(), scan(), fax(), email()} forces BasicPrinter to stub scan/fax/email. DocumentManager {create(), read(), update(), delete(), export(), import(), share(), version(), archive()} mixed CRUD with features. Modern: GraphQL naturally follows ISP - clients query only needed fields. Microservices avoid fat interfaces via focused service APIs. Testing benefit: Mock 2-method interface vs 10-method interface - huge difference.
Service Locator: Global registry for retrieving dependencies - hides dependencies and violates DIP by introducing static coupling. Pattern: class OrderService {process() {const db = ServiceLocator.get('Database'); const logger = ServiceLocator.get('Logger'); db.save(); logger.log();}}. Problems: (1) Hidden dependencies - constructor doesn't show what OrderService needs, can't see dependencies without reading implementation. (2) Global state - ServiceLocator is static singleton, makes testing harder (must configure global state). (3) Runtime errors - missing dependency fails at call site, not object creation (too late). (4) Tight coupling - every class depends on ServiceLocator (framework lock-in). (5) Unclear ownership - who registers dependencies? when? DIP violation: High-level OrderService depends on low-level ServiceLocator framework, not abstractions. Refactoring: Dependency Injection - class OrderService {constructor(private db: Database, private logger: Logger)}. Benefits: Explicit dependencies (visible in constructor), compile-time safety (DI container fails at startup), no global state, easy testing (inject mocks). Exception: Legacy migration - ServiceLocator acceptable as intermediate step when migrating to DI. Modern frameworks: NestJS, Spring use DI, not Service Locator. Martin Fowler: 'Service Locator is fine, but DI more explicit.'
Primitive Obsession: Using primitives (string, number) instead of small value objects - spreads validation/logic across codebase violating SRP. Detection: Email validation scattered in 10+ places, ZIP code regex duplicated, currency calculations with bare numbers (no Currency type). Example: function createUser(email: string, phone: string, zip: string) {if(!/\S+@\S+/.test(email)) throw Error(); if(!/\d{10}/.test(phone)) throw Error(); if(!/\d{5}/.test(zip)) throw Error();} - validation logic in function. Repeated everywhere email/phone/zip used. SRP violation: createUser responsible for user creation AND email/phone/zip validation. Refactoring: Value objects - class Email {constructor(private value: string) {if(!/\S+@\S+/.test(value)) throw Error('Invalid email');} toString() {return this.value;}}. Usage: function createUser(email: Email, phone: Phone, zip: ZipCode) - validation in value object constructor. Benefits: Single place for validation (DRY), type safety (can't pass string where Email expected), rich behavior (email.getDomain()), self-documenting code. SOLID impact: Enables SRP (validation in value object), supports OCP (extend Email with methods), helps LSP (value objects immutable, no inheritance issues). Modern: TypeScript branded types: type Email = string & {__brand: 'Email'}. Domain-Driven Design: Value Objects core pattern for domain modeling.
Anemic Domain Model: Domain objects are just data containers (getters/setters), all logic in separate service classes - violates OOP encapsulation and spreads responsibility. Pattern: class User {constructor(public name: string, public email: string, public points: number) {}}. class UserService {addPoints(user: User, points: number) {user.points += points;} canRedeem(user: User): boolean {return user.points >= 100;}}. Problem: User is passive data holder, UserService knows User's business rules. SRP violation: User and UserService share User responsibility. Encapsulation violation: User's invariants (points >= 0) not enforced, any code can set user.points = -1000. Refactoring: Rich domain model - class User {private points: number = 0; addPoints(points: number) {if(points < 0) throw Error(); this.points += points;} canRedeem(): boolean {return this.points >= 100;}}. Benefits: Data + behavior together, invariants enforced (points always valid), tell don't ask (user.addPoints() vs service.addPoints(user)). When acceptable: DTOs for data transfer, View Models for UI, Read Models in CQRS (query side). Not acceptable: Domain entities with business rules. Martin Fowler: 'Anemic models are procedural programming in OOP disguise.' Domain-Driven Design: Rich entities with behavior core pattern. Modern: TypeScript classes enable rich models, avoid plain objects for domain logic.
Circular Dependency: A depends on B, B depends on A - creates tight coupling violating DIP and preventing proper layering. Detection: Import cycle errors, DI container fails to resolve, classes can't be tested independently. Example: class UserService {constructor(private orders: OrderService)} class OrderService {constructor(private users: UserService)} - circular dependency. DIP violation: High-level modules depend on each other directly instead of abstractions, prevents dependency inversion (can't make arrows point inward). Refactoring strategies: (1) Extract interface - interface IUserLookup {getUser(id)}. OrderService depends on IUserLookup, UserService implements it. Breaks cycle: OrderService → IUserLookup ← UserService. (2) Event-driven - OrderService emits OrderPlaced event, UserService subscribes. No direct dependency. (3) Introduce mediator - OrderUserCoordinator depends on both services, services don't depend on each other. (4) Redesign - circular dependency indicates wrong boundaries, extract shared concern to separate module. Problems: Can't determine initialization order, testing requires both classes, tight coupling (changing A requires changing B), violates acyclic dependencies principle. Prevention: Depend on abstractions (DIP), use events for cross-cutting concerns, respect layered architecture (upper layers depend on lower, never reverse). Tools: Madge, dependency-cruiser detect cycles in JavaScript/TypeScript.
Magic Numbers/Strings: Hardcoded values with no explanation scattered throughout code - violates DRY and makes changes risky. Detection: Unexplained constants (if(status === 3), amount * 0.07), duplicated values across codebase, unclear meaning (setTimeout(fn, 86400000)). Example: if(user.role === 'ADMIN') {...} else if(user.role === 'MODERATOR') {...}. Problem: String 'ADMIN' duplicated 50 times, typo creates bug ('ADNIM'), changing role name requires 50 edits. Refactoring: Named constants - const UserRole = {ADMIN: 'ADMIN', MODERATOR: 'MODERATOR', USER: 'USER'} as const. Usage: if(user.role === UserRole.ADMIN). Better: Enums - enum UserRole {ADMIN = 'ADMIN', MODERATOR = 'MODERATOR'}. TypeScript advantage: Type safety, autocomplete, refactoring support. Benefits: Single source of truth (change once), self-documenting (TAX_RATE vs 0.07), prevents typos, easier testing. SOLID connection: Enables SRP (configuration in one place), supports OCP (change constant without modifying logic). Special cases: 0, 1, -1, null, empty string often acceptable without extraction. Rule: If number/string appears 3+ times or meaning unclear, extract constant. Modern: Configuration files for environment-specific values (tax rates, API URLs), feature flags for toggles.
Copy-Paste Programming: Duplicating code instead of abstracting - violates DRY (Don't Repeat Yourself) and spreads bugs. Detection: SonarQube flags >5 line duplications, similar code blocks with slight variations, fixing bug requires updating 10 places. Example: function validateEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} function validateUserEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} function validateAdminEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} - same regex duplicated. Problem: Bug in regex requires fixing 3 places, email validation logic scattered. Refactoring: Extract function - function validateEmail(email: string) {if(!/\S+@\S+/.test(email)) throw Error();}. Or value object: class Email {constructor(value) {if(!/\S+@\S+/.test(value)) throw Error();}}. Benefits: Single source of truth, fix bugs once, consistent behavior. When duplication acceptable: Unrelated coincidences (different domains happen to look similar), premature abstraction worse than duplication (Rule of Three: wait for 3rd instance). SOLID impact: Violates SRP (validation responsibility duplicated), prevents OCP (can't extend duplicated code easily). Modern tools: PMD Copy/Paste Detector, SonarQube duplication detection, IDE 'extract method' refactoring. Sandi Metz: 'Duplication is far cheaper than wrong abstraction.' Balance: Don't abstract first instance, do abstract third instance.
Golden Hammer: Overusing familiar solution regardless of problem fit - 'If all you have is a hammer, everything looks like a nail.' Limits applying SOLID appropriately. Examples: Using inheritance for everything (should use composition), forcing all problems into single design pattern (Observer for state that should use simple callbacks), using microservices for 10-user app (overkill), applying OOP to functional problem domain. Detection: Same solution pattern in every module, refusing to learn new approaches, dismissing alternatives without consideration, saying 'We always do it this way'. Example: Team loves Singleton pattern, uses it for everything - ConfigSingleton, LoggerSingleton, DatabaseSingleton, CacheSingleton. Problem: Global state everywhere, hard to test (shared mutable state), tight coupling. SOLID violation: Violates DIP (depend on singletons not abstractions), prevents proper dependency injection. Refactoring: Evaluate each problem independently. Config/Logger: Dependency injection, not singleton. Database: Connection pool, not singleton. Cache: Injected cache service. Benefits: Right tool for job, easier testing, flexible architecture. Prevention: Learn multiple patterns/paradigms, evaluate trade-offs, ask 'Is this the simplest solution?' Modern: Polyglot programming, multi-paradigm languages (TypeScript supports OOP + functional + procedural). Principle: No Silver Bullet - every solution has trade-offs.
Spaghetti Code: Tangled control flow with no clear structure - violates all SOLID principles simultaneously. Characteristics: No clear separation of concerns, goto-style flow (callbacks calling callbacks), global variables everywhere, functions >500 lines, nested conditionals 10+ levels deep. Example: function processOrder(order) {if(order.items) {for(let item of order.items) {if(item.quantity > 0) {let price = item.basePrice; if(GLOBAL_DISCOUNT) price *= 0.9; if(item.type === 'book') {if(item.isbn) {DATABASE.query('SELECT...');}} else if(item.type === 'electronics') {...}}}}}. SOLID violations: SRP - processOrder does validation, pricing, database, business logic. OCP - adding item type requires modifying deeply nested conditional. LSP - not applicable (no inheritance, just chaos). ISP - no interfaces, everything tightly coupled. DIP - depends on global DATABASE, GLOBAL_DISCOUNT directly. Refactoring: Extract services (OrderService, PricingService, InventoryService), introduce interfaces (IDatabase, IPricingStrategy), use dependency injection, separate concerns into cohesive modules. Benefits: Testable code (can mock dependencies), maintainable (clear responsibilities), extensible (OCP compliance). Prevention: Code reviews, linting (complexity limits), TDD (forces small testable units), SOLID training. Modern: Frameworks enforce structure (NestJS modules, React components), functional programming reduces spaghetti via pure functions.
Leaky Abstraction: Abstraction exposes implementation details forcing clients to know internals - violates encapsulation and prevents substitutability (LSP). Detection: Clients must know which database implementation to handle edge cases, abstraction's API reveals technology choice, error messages expose internal stack traces, performance depends on knowing implementation. Example: interface FileStorage {save(file: File): void}. Implementation: class S3Storage implements FileStorage {save(file: File) {if(file.size > 5000000) throw new Error('S3 limit 5MB');}}. Leak: Client must know S3's 5MB limit to use abstraction. Better: FileStorage defines limit in contract or handles chunking internally. More leaks: Repository.query() returns Prisma QueryBuilder (leaks ORM), API wrapper throws AxiosError (leaks HTTP library), cache.get() returns Redis-specific data structure. Problems: Can't substitute implementations (S3Storage for LocalStorage breaks size assumptions), tight coupling (client code knows implementation), violates LSP (subclasses have different constraints). Refactoring: Hide implementation - repository.find() returns domain entities (not ORM objects), storage.save() handles chunking internally, API throws domain errors (not library errors). Benefits: True abstraction (can swap implementations), loose coupling, LSP compliance. Joel Spolsky: 'All non-trivial abstractions are leaky' - minimize leakage, document unavoidable leaks.
Inappropriate Intimacy: Classes too dependent on each other's internals - violates encapsulation and tight coupling violating DIP. Detection: Friend classes accessing each other's private fields (via public getters), classes in same file because tightly coupled, classes knowing too much about each other's implementation. Example: class Order {getItems() {return this.items;}} class Invoice {calculate(order: Order) {let total = 0; for(let item of order.getItems()) {total += item.price * item.quantity; if(item.discount) total -= item.discount;} return total;}}. Problem: Invoice knows Order's internal structure (items array, discount field), changes to Order.items structure breaks Invoice. Refactoring: Tell Don't Ask - move calculation to Order: class Order {getTotal(): number {return this.items.reduce((sum, item) => sum + item.getPrice(), 0);}}. Invoice: calculate(order: Order) {return order.getTotal();}. Benefits: Order encapsulates calculation logic, Invoice doesn't need to know Order internals, changes to Order.items don't affect Invoice. Law of Demeter: Only talk to immediate friends. invoice.calculate() should call order.getTotal(), not order.getItems()[0].price. SOLID: Violates DIP (Invoice depends on Order implementation), prevents OCP (can't extend Order without breaking Invoice). Modern: Domain-Driven Design - Aggregates enforce boundaries, information hiding principle.
Yo-Yo Problem: Deep inheritance hierarchy forcing developers to jump between files to understand code - indicates over-use of inheritance violating LSP and ISP. Detection: Inheritance depth >4 levels, method implementation in grandparent, template methods with 10 hook points, understanding class requires reading 6 parent classes. Example: Animal → Mammal → Carnivore → Feline → BigCat → Lion. Lion.hunt() calls Carnivore.hunt() which calls Mammal.move() which calls Animal.breathe(). Reading Lion requires understanding entire chain. Problems: Fragile base class (changing Animal breaks Lion), hard to test (need entire hierarchy), difficult onboarding (where is method defined?), LSP violations accumulate (each level adds constraints). Refactoring: Composition over inheritance - interface Huntable {hunt()}; interface Movable {move()}; class Lion implements Huntable, Movable {constructor(private movement: Movement, private hunting: Hunting) {}}. Benefits: Flat structure (understand Lion without parents), flexible composition (swap hunting strategy), easier testing (mock interfaces). Gang of Four: 'Favor composition over inheritance.' When inheritance acceptable: 1-2 levels max, pure behavioral substitution (LSP compliant), shared implementation valuable. Modern: React moved from class inheritance (Component) to composition (hooks). TypeScript enables mixins as alternative to deep inheritance. SOLID: Prefer interfaces (ISP) and composition (DIP) over inheritance hierarchies.
Poltergeist: Short-lived objects with no clear purpose that just pass data around - indicates missing abstraction and poor responsibility assignment. Detection: Classes doing nothing but forwarding calls, objects created just to call one method then discarded, unnecessary intermediaries adding no value. Example: class DataFetcher {fetch(url: string) {return new HTTPClient().get(url);}} class HTTPClient {get(url: string) {return new RequestSender().send(url);}} class RequestSender {send(url: string) {return axios.get(url);}}. Problem: DataFetcher, HTTPClient add no value, just forward to axios. SRP violation: Responsibilities scattered across unnecessary classes. Refactoring: Eliminate middlemen - class DataFetcher {fetch(url: string) {return axios.get(url);}}. Or if wrapping axios valuable: class HTTPClient {get(url: string) {return axios.get(url);}}. Use directly. Benefits: Simpler code, fewer files, clearer flow. When intermediary acceptable: Adapter pattern (wrapping third-party library), Facade (simplifying complex subsystem), Proxy (adding cross-cutting concern like caching). Must add value. Related: Big Ball of Mud - system with no architecture, random structure. Poltergeists symptom of larger problem (no clear design). Prevention: Ask 'What responsibility does this class have?' If answer is 'forwards calls', eliminate it. SOLID: Violates SRP (fake responsibilities), prevents DIP (can't inject ghost objects meaningfully).
Singleton Overuse: Making everything a singleton for convenience - creates global state violating DIP and preventing proper testing. Pattern: class Database {private static instance: Database; private constructor() {} static getInstance() {if(!Database.instance) Database.instance = new Database(); return Database.instance;}}. Problems: (1) Global state - shared mutable state across application, race conditions in concurrent environments. (2) Hidden dependencies - class doesn't declare Database dependency, can't see in constructor. (3) Hard to test - can't inject mock, singleton persists across tests (test pollution). (4) Violates DIP - code depends directly on concrete Database singleton, not abstraction. (5) Lifecycle complexity - when to initialize? reset? Overuse example: ConfigSingleton, LoggerSingleton, DatabaseSingleton, CacheSingleton, AnalyticsSingleton, EmailSingleton - everything global. Refactoring: Dependency injection - class UserService {constructor(private db: Database, private logger: Logger) {}}. Container manages single instance if needed. Benefits: Explicit dependencies, testable (inject mocks), no global state, respects DIP (depend on interfaces). When Singleton acceptable: Truly single resource (hardware device driver), thread-safe immutable configuration. Modern: React Context, NestJS providers replace singletons with proper DI. Testing: singleton.resetInstance() hacks in tests indicate overuse. Scoped singletons: One instance per DI container scope (request, session) better than global.
Premature Optimization: Optimizing before knowing performance bottlenecks - sacrifices maintainability (SOLID) for unmeasured performance gains. Donald Knuth: 'Premature optimization is root of all evil.' Detection: Complex code for theoretical performance (no measurements), micro-optimizations everywhere (using bitwise ops in business logic), choosing complex architecture for scalability that's not needed (microservices for 10 users). Example: class UserService {private cache = new Map(); findById(id: string) {if(this.cache.has(id)) return this.cache.get(id); const user = this.db.query(SELECT * FROM users WHERE id = ${id}); this.cache.set(id, user); return user;}}. Problem: Added caching complexity without measuring if database is bottleneck. Cache invalidation adds bugs. SQL injection vulnerability from optimization rush. SOLID violations: Violates SRP (UserService now manages cache + users), violates OCP (cache strategy hardcoded, can't extend), poor separation of concerns. Refactoring: Measure first - use profiler, find actual bottleneck. If database is issue: Extract caching to decorator/middleware. class CachedRepository implements UserRepository {constructor(private repo: UserRepository, private cache: Cache) {}}. Benefits: Clean code first, optimize when needed, SOLID maintained, optimization isolated. Modern: Profile with Chrome DevTools, New Relic, DataDog. Optimize hot paths only (20% code uses 80% resources). Rule: Make it work, make it right, make it fast (in that order).
God Object (God Class) is a class that knows too much or does too much - centralizes control and violates SRP by having multiple responsibilities. Detection: Class >500 lines, >20 methods, imports from 5+ different domains (database, email, logging, caching, validation, business logic), high change frequency (modified for unrelated features). Example: class UserManager {createUser(), deleteUser(), sendEmail(), validateEmail(), hashPassword(), queryDatabase(), cacheUser(), logActivity(), exportToCSV(), generateReport()} - handles user management, email, validation, database, caching, logging, reporting. SRP violation: 7+ reasons to change. Refactoring: Extract services by domain: UserService (create, delete), EmailService (send, validate), AuthService (hash password), UserRepository (database), CacheService, Logger, ReportGenerator. Benefits: Each service single responsibility, easier testing, parallel development, reduced merge conflicts. God Object risks: Hard to test (many dependencies), tight coupling (changes ripple), merge conflicts (everyone modifies same file), difficult onboarding (10K line class). Detection tools: SonarQube flags >500 line classes, high cyclomatic complexity (>50). Modern: Microservices architecture prevents God Objects at service level.
Shotgun Surgery: Making single conceptual change requires modifying many classes - indicates scattered responsibility violating SRP. Detection: Adding feature touches 5+ files, single bug fix modifies 10+ locations, grep for feature name returns 20+ files. Example: Adding payment method requires changes in OrderController, OrderService, PaymentValidator, EmailService, InvoiceGenerator, AnalyticsTracker, DatabaseSchema, ConfigFile - payment logic scattered. SRP violation: Payment responsibility spread across system instead of centralized. Refactoring: Gather related changes together. Extract PaymentService encapsulating all payment logic. Use Observer pattern for cross-cutting concerns (analytics, email) instead of direct calls. Benefits: Single place to modify for payment changes, easier testing (mock PaymentService), clearer ownership. Opposite of God Object but equally bad: God Object = everything in one place (too cohesive), Shotgun Surgery = one concept scattered everywhere (too decoupled). Causes: Premature optimization, copy-paste programming, lack of abstraction. Prevention: Ask 'Where does this responsibility belong?' before adding code. Use feature folders/modules grouping related code. Modern tools: CodeScene detects change coupling (files changed together), suggests refactoring candidates.
Feature Envy: Method more interested in other class's data than its own - indicates misplaced responsibility violating SRP. Detection: Method calls other object's getters 5+ times, method uses other class's data more than own class data, method would make more sense in other class. Example: class Order {getTotal(customer: Customer): number {let total = this.basePrice; if(customer.isPremium()) total *= 0.9; if(customer.getLoyaltyPoints() > 100) total *= 0.95; if(customer.getRegion() === 'EU') total += total * 0.2; return total;}} - Order.getTotal() obsessed with Customer data. SRP violation: Pricing logic uses customer's responsibility, should be in Customer or separate PricingService. Refactoring: Move method to Customer: customer.calculatePrice(order). Or extract service: pricingService.calculate(order, customer). Benefits: Methods near data they use, clearer responsibility boundaries, easier testing. Law of Demeter: Talk to friends, not strangers. order.getTotal() shouldn't know about customer.getRegion().getTaxRate(). Exception: DTOs/Value Objects intentionally accessed by services - acceptable when clear separation between data and behavior. Modern: Domain-Driven Design puts behavior with data in Aggregates.
Blob (Lava Flow): Monolithic class with conditional logic for every variation - violates OCP by requiring modification for each new variant. Detection: Class with 10+ if/switch statements, conditionals on type/state scattered throughout, adding variant requires touching 20+ lines. Example: class PaymentProcessor {process(payment: Payment) {if(payment.type === 'credit') {validateCredit(); chargeCredit();} else if(payment.type === 'paypal') {validatePayPal(); chargePayPal();} else if(payment.type === 'crypto') {validateCrypto(); chargeCrypto();}} validate(payment) {if... else if...} refund(payment) {if... else if...}} - every method has conditionals. OCP violation: Adding Bitcoin payment requires modifying process(), validate(), refund(), getStatus(), generateReceipt(). Refactoring: Strategy pattern - interface PaymentStrategy {process(), validate(), refund()}. Implementations: CreditCardStrategy, PayPalStrategy, CryptoStrategy. Context: class PaymentProcessor {constructor(private strategy: PaymentStrategy) {} process(payment) {this.strategy.process(payment);}}. Benefits: Add variant without modifying existing code, each strategy independently testable, strategies composable. Detection: Cyclomatic complexity >20, too many paths through code. Modern: TypeScript discriminated unions help but don't prevent violation - still need polymorphism for extensibility.
Refused Bequest: Subclass inherits methods it doesn't want/need, refuses to use them - violates LSP by not honoring parent contract. Detection: Subclass throws NotImplementedException, empty method overrides, methods returning null/default where parent returns real values. Example: class Bird {fly() {/* flying logic */}} class Ostrich extends Bird {fly() {throw new Error('Cannot fly');}} - Ostrich refuses Bird.fly(). LSP violation: Client expecting Bird gets exception. Refactoring: Interface segregation - split Bird into Flyable and NonFlyable. Use composition: class Bird {flyBehavior?: FlyBehavior}. Or separate hierarchies: FlyingBird, FlightlessBird. Why harmful: Violates polymorphism (can't substitute), forces type checking (if bird instanceof Ostrich), brittle inheritance (adding capability to parent forces on all children). Root cause: Misusing inheritance - 'is-a' relationship conceptually but not behaviorally. Stack extends ArrayList refuses get(index) - should use composition. ReadOnlyList extends List refuses add() - should implement separate interface. Prevention: Favor composition over inheritance, use interfaces for capabilities, apply LSP test (can subclass replace parent without breaking?). Modern: TypeScript interfaces enable multiple small contracts without inheritance.
Interface Pollution (Fat Interface): Interface with too many methods forcing clients to depend on methods they don't use - violates ISP. Detection: Interface >10 methods, implementations throw NotImplementedException for unused methods, clients only use subset of interface, interface serves multiple unrelated roles. Example: interface Worker {work(), eat(), sleep(), commute(), getPaid(), attendMeeting(), submitTimesheet(), useTools(), cleanWorkspace(), train()} - 10 methods. Robot implements Worker but throws errors for eat(), sleep(), commute(). ISP violation: Robot forced to implement biological/human methods. Refactoring: Split into role interfaces: Workable {work()}, Biological {eat(), sleep()}, Commutable {commute()}, Payable {getPaid()}, Trainable {train()}. Human implements all, Robot implements Workable + Trainable. Benefits: Clients depend on minimal interface, easier testing (mock only needed methods), clear contracts. Similar violations: Printer {print(), scan(), fax(), email()} forces BasicPrinter to stub scan/fax/email. DocumentManager {create(), read(), update(), delete(), export(), import(), share(), version(), archive()} mixed CRUD with features. Modern: GraphQL naturally follows ISP - clients query only needed fields. Microservices avoid fat interfaces via focused service APIs. Testing benefit: Mock 2-method interface vs 10-method interface - huge difference.
Service Locator: Global registry for retrieving dependencies - hides dependencies and violates DIP by introducing static coupling. Pattern: class OrderService {process() {const db = ServiceLocator.get('Database'); const logger = ServiceLocator.get('Logger'); db.save(); logger.log();}}. Problems: (1) Hidden dependencies - constructor doesn't show what OrderService needs, can't see dependencies without reading implementation. (2) Global state - ServiceLocator is static singleton, makes testing harder (must configure global state). (3) Runtime errors - missing dependency fails at call site, not object creation (too late). (4) Tight coupling - every class depends on ServiceLocator (framework lock-in). (5) Unclear ownership - who registers dependencies? when? DIP violation: High-level OrderService depends on low-level ServiceLocator framework, not abstractions. Refactoring: Dependency Injection - class OrderService {constructor(private db: Database, private logger: Logger)}. Benefits: Explicit dependencies (visible in constructor), compile-time safety (DI container fails at startup), no global state, easy testing (inject mocks). Exception: Legacy migration - ServiceLocator acceptable as intermediate step when migrating to DI. Modern frameworks: NestJS, Spring use DI, not Service Locator. Martin Fowler: 'Service Locator is fine, but DI more explicit.'
Primitive Obsession: Using primitives (string, number) instead of small value objects - spreads validation/logic across codebase violating SRP. Detection: Email validation scattered in 10+ places, ZIP code regex duplicated, currency calculations with bare numbers (no Currency type). Example: function createUser(email: string, phone: string, zip: string) {if(!/\S+@\S+/.test(email)) throw Error(); if(!/\d{10}/.test(phone)) throw Error(); if(!/\d{5}/.test(zip)) throw Error();} - validation logic in function. Repeated everywhere email/phone/zip used. SRP violation: createUser responsible for user creation AND email/phone/zip validation. Refactoring: Value objects - class Email {constructor(private value: string) {if(!/\S+@\S+/.test(value)) throw Error('Invalid email');} toString() {return this.value;}}. Usage: function createUser(email: Email, phone: Phone, zip: ZipCode) - validation in value object constructor. Benefits: Single place for validation (DRY), type safety (can't pass string where Email expected), rich behavior (email.getDomain()), self-documenting code. SOLID impact: Enables SRP (validation in value object), supports OCP (extend Email with methods), helps LSP (value objects immutable, no inheritance issues). Modern: TypeScript branded types: type Email = string & {__brand: 'Email'}. Domain-Driven Design: Value Objects core pattern for domain modeling.
Anemic Domain Model: Domain objects are just data containers (getters/setters), all logic in separate service classes - violates OOP encapsulation and spreads responsibility. Pattern: class User {constructor(public name: string, public email: string, public points: number) {}}. class UserService {addPoints(user: User, points: number) {user.points += points;} canRedeem(user: User): boolean {return user.points >= 100;}}. Problem: User is passive data holder, UserService knows User's business rules. SRP violation: User and UserService share User responsibility. Encapsulation violation: User's invariants (points >= 0) not enforced, any code can set user.points = -1000. Refactoring: Rich domain model - class User {private points: number = 0; addPoints(points: number) {if(points < 0) throw Error(); this.points += points;} canRedeem(): boolean {return this.points >= 100;}}. Benefits: Data + behavior together, invariants enforced (points always valid), tell don't ask (user.addPoints() vs service.addPoints(user)). When acceptable: DTOs for data transfer, View Models for UI, Read Models in CQRS (query side). Not acceptable: Domain entities with business rules. Martin Fowler: 'Anemic models are procedural programming in OOP disguise.' Domain-Driven Design: Rich entities with behavior core pattern. Modern: TypeScript classes enable rich models, avoid plain objects for domain logic.
Circular Dependency: A depends on B, B depends on A - creates tight coupling violating DIP and preventing proper layering. Detection: Import cycle errors, DI container fails to resolve, classes can't be tested independently. Example: class UserService {constructor(private orders: OrderService)} class OrderService {constructor(private users: UserService)} - circular dependency. DIP violation: High-level modules depend on each other directly instead of abstractions, prevents dependency inversion (can't make arrows point inward). Refactoring strategies: (1) Extract interface - interface IUserLookup {getUser(id)}. OrderService depends on IUserLookup, UserService implements it. Breaks cycle: OrderService → IUserLookup ← UserService. (2) Event-driven - OrderService emits OrderPlaced event, UserService subscribes. No direct dependency. (3) Introduce mediator - OrderUserCoordinator depends on both services, services don't depend on each other. (4) Redesign - circular dependency indicates wrong boundaries, extract shared concern to separate module. Problems: Can't determine initialization order, testing requires both classes, tight coupling (changing A requires changing B), violates acyclic dependencies principle. Prevention: Depend on abstractions (DIP), use events for cross-cutting concerns, respect layered architecture (upper layers depend on lower, never reverse). Tools: Madge, dependency-cruiser detect cycles in JavaScript/TypeScript.
Magic Numbers/Strings: Hardcoded values with no explanation scattered throughout code - violates DRY and makes changes risky. Detection: Unexplained constants (if(status === 3), amount * 0.07), duplicated values across codebase, unclear meaning (setTimeout(fn, 86400000)). Example: if(user.role === 'ADMIN') {...} else if(user.role === 'MODERATOR') {...}. Problem: String 'ADMIN' duplicated 50 times, typo creates bug ('ADNIM'), changing role name requires 50 edits. Refactoring: Named constants - const UserRole = {ADMIN: 'ADMIN', MODERATOR: 'MODERATOR', USER: 'USER'} as const. Usage: if(user.role === UserRole.ADMIN). Better: Enums - enum UserRole {ADMIN = 'ADMIN', MODERATOR = 'MODERATOR'}. TypeScript advantage: Type safety, autocomplete, refactoring support. Benefits: Single source of truth (change once), self-documenting (TAX_RATE vs 0.07), prevents typos, easier testing. SOLID connection: Enables SRP (configuration in one place), supports OCP (change constant without modifying logic). Special cases: 0, 1, -1, null, empty string often acceptable without extraction. Rule: If number/string appears 3+ times or meaning unclear, extract constant. Modern: Configuration files for environment-specific values (tax rates, API URLs), feature flags for toggles.
Copy-Paste Programming: Duplicating code instead of abstracting - violates DRY (Don't Repeat Yourself) and spreads bugs. Detection: SonarQube flags >5 line duplications, similar code blocks with slight variations, fixing bug requires updating 10 places. Example: function validateEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} function validateUserEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} function validateAdminEmail(email) {if(!/\S+@\S+/.test(email)) throw Error();} - same regex duplicated. Problem: Bug in regex requires fixing 3 places, email validation logic scattered. Refactoring: Extract function - function validateEmail(email: string) {if(!/\S+@\S+/.test(email)) throw Error();}. Or value object: class Email {constructor(value) {if(!/\S+@\S+/.test(value)) throw Error();}}. Benefits: Single source of truth, fix bugs once, consistent behavior. When duplication acceptable: Unrelated coincidences (different domains happen to look similar), premature abstraction worse than duplication (Rule of Three: wait for 3rd instance). SOLID impact: Violates SRP (validation responsibility duplicated), prevents OCP (can't extend duplicated code easily). Modern tools: PMD Copy/Paste Detector, SonarQube duplication detection, IDE 'extract method' refactoring. Sandi Metz: 'Duplication is far cheaper than wrong abstraction.' Balance: Don't abstract first instance, do abstract third instance.
Golden Hammer: Overusing familiar solution regardless of problem fit - 'If all you have is a hammer, everything looks like a nail.' Limits applying SOLID appropriately. Examples: Using inheritance for everything (should use composition), forcing all problems into single design pattern (Observer for state that should use simple callbacks), using microservices for 10-user app (overkill), applying OOP to functional problem domain. Detection: Same solution pattern in every module, refusing to learn new approaches, dismissing alternatives without consideration, saying 'We always do it this way'. Example: Team loves Singleton pattern, uses it for everything - ConfigSingleton, LoggerSingleton, DatabaseSingleton, CacheSingleton. Problem: Global state everywhere, hard to test (shared mutable state), tight coupling. SOLID violation: Violates DIP (depend on singletons not abstractions), prevents proper dependency injection. Refactoring: Evaluate each problem independently. Config/Logger: Dependency injection, not singleton. Database: Connection pool, not singleton. Cache: Injected cache service. Benefits: Right tool for job, easier testing, flexible architecture. Prevention: Learn multiple patterns/paradigms, evaluate trade-offs, ask 'Is this the simplest solution?' Modern: Polyglot programming, multi-paradigm languages (TypeScript supports OOP + functional + procedural). Principle: No Silver Bullet - every solution has trade-offs.
Spaghetti Code: Tangled control flow with no clear structure - violates all SOLID principles simultaneously. Characteristics: No clear separation of concerns, goto-style flow (callbacks calling callbacks), global variables everywhere, functions >500 lines, nested conditionals 10+ levels deep. Example: function processOrder(order) {if(order.items) {for(let item of order.items) {if(item.quantity > 0) {let price = item.basePrice; if(GLOBAL_DISCOUNT) price *= 0.9; if(item.type === 'book') {if(item.isbn) {DATABASE.query('SELECT...');}} else if(item.type === 'electronics') {...}}}}}. SOLID violations: SRP - processOrder does validation, pricing, database, business logic. OCP - adding item type requires modifying deeply nested conditional. LSP - not applicable (no inheritance, just chaos). ISP - no interfaces, everything tightly coupled. DIP - depends on global DATABASE, GLOBAL_DISCOUNT directly. Refactoring: Extract services (OrderService, PricingService, InventoryService), introduce interfaces (IDatabase, IPricingStrategy), use dependency injection, separate concerns into cohesive modules. Benefits: Testable code (can mock dependencies), maintainable (clear responsibilities), extensible (OCP compliance). Prevention: Code reviews, linting (complexity limits), TDD (forces small testable units), SOLID training. Modern: Frameworks enforce structure (NestJS modules, React components), functional programming reduces spaghetti via pure functions.
Leaky Abstraction: Abstraction exposes implementation details forcing clients to know internals - violates encapsulation and prevents substitutability (LSP). Detection: Clients must know which database implementation to handle edge cases, abstraction's API reveals technology choice, error messages expose internal stack traces, performance depends on knowing implementation. Example: interface FileStorage {save(file: File): void}. Implementation: class S3Storage implements FileStorage {save(file: File) {if(file.size > 5000000) throw new Error('S3 limit 5MB');}}. Leak: Client must know S3's 5MB limit to use abstraction. Better: FileStorage defines limit in contract or handles chunking internally. More leaks: Repository.query() returns Prisma QueryBuilder (leaks ORM), API wrapper throws AxiosError (leaks HTTP library), cache.get() returns Redis-specific data structure. Problems: Can't substitute implementations (S3Storage for LocalStorage breaks size assumptions), tight coupling (client code knows implementation), violates LSP (subclasses have different constraints). Refactoring: Hide implementation - repository.find() returns domain entities (not ORM objects), storage.save() handles chunking internally, API throws domain errors (not library errors). Benefits: True abstraction (can swap implementations), loose coupling, LSP compliance. Joel Spolsky: 'All non-trivial abstractions are leaky' - minimize leakage, document unavoidable leaks.
Inappropriate Intimacy: Classes too dependent on each other's internals - violates encapsulation and tight coupling violating DIP. Detection: Friend classes accessing each other's private fields (via public getters), classes in same file because tightly coupled, classes knowing too much about each other's implementation. Example: class Order {getItems() {return this.items;}} class Invoice {calculate(order: Order) {let total = 0; for(let item of order.getItems()) {total += item.price * item.quantity; if(item.discount) total -= item.discount;} return total;}}. Problem: Invoice knows Order's internal structure (items array, discount field), changes to Order.items structure breaks Invoice. Refactoring: Tell Don't Ask - move calculation to Order: class Order {getTotal(): number {return this.items.reduce((sum, item) => sum + item.getPrice(), 0);}}. Invoice: calculate(order: Order) {return order.getTotal();}. Benefits: Order encapsulates calculation logic, Invoice doesn't need to know Order internals, changes to Order.items don't affect Invoice. Law of Demeter: Only talk to immediate friends. invoice.calculate() should call order.getTotal(), not order.getItems()[0].price. SOLID: Violates DIP (Invoice depends on Order implementation), prevents OCP (can't extend Order without breaking Invoice). Modern: Domain-Driven Design - Aggregates enforce boundaries, information hiding principle.
Yo-Yo Problem: Deep inheritance hierarchy forcing developers to jump between files to understand code - indicates over-use of inheritance violating LSP and ISP. Detection: Inheritance depth >4 levels, method implementation in grandparent, template methods with 10 hook points, understanding class requires reading 6 parent classes. Example: Animal → Mammal → Carnivore → Feline → BigCat → Lion. Lion.hunt() calls Carnivore.hunt() which calls Mammal.move() which calls Animal.breathe(). Reading Lion requires understanding entire chain. Problems: Fragile base class (changing Animal breaks Lion), hard to test (need entire hierarchy), difficult onboarding (where is method defined?), LSP violations accumulate (each level adds constraints). Refactoring: Composition over inheritance - interface Huntable {hunt()}; interface Movable {move()}; class Lion implements Huntable, Movable {constructor(private movement: Movement, private hunting: Hunting) {}}. Benefits: Flat structure (understand Lion without parents), flexible composition (swap hunting strategy), easier testing (mock interfaces). Gang of Four: 'Favor composition over inheritance.' When inheritance acceptable: 1-2 levels max, pure behavioral substitution (LSP compliant), shared implementation valuable. Modern: React moved from class inheritance (Component) to composition (hooks). TypeScript enables mixins as alternative to deep inheritance. SOLID: Prefer interfaces (ISP) and composition (DIP) over inheritance hierarchies.
Poltergeist: Short-lived objects with no clear purpose that just pass data around - indicates missing abstraction and poor responsibility assignment. Detection: Classes doing nothing but forwarding calls, objects created just to call one method then discarded, unnecessary intermediaries adding no value. Example: class DataFetcher {fetch(url: string) {return new HTTPClient().get(url);}} class HTTPClient {get(url: string) {return new RequestSender().send(url);}} class RequestSender {send(url: string) {return axios.get(url);}}. Problem: DataFetcher, HTTPClient add no value, just forward to axios. SRP violation: Responsibilities scattered across unnecessary classes. Refactoring: Eliminate middlemen - class DataFetcher {fetch(url: string) {return axios.get(url);}}. Or if wrapping axios valuable: class HTTPClient {get(url: string) {return axios.get(url);}}. Use directly. Benefits: Simpler code, fewer files, clearer flow. When intermediary acceptable: Adapter pattern (wrapping third-party library), Facade (simplifying complex subsystem), Proxy (adding cross-cutting concern like caching). Must add value. Related: Big Ball of Mud - system with no architecture, random structure. Poltergeists symptom of larger problem (no clear design). Prevention: Ask 'What responsibility does this class have?' If answer is 'forwards calls', eliminate it. SOLID: Violates SRP (fake responsibilities), prevents DIP (can't inject ghost objects meaningfully).
Singleton Overuse: Making everything a singleton for convenience - creates global state violating DIP and preventing proper testing. Pattern: class Database {private static instance: Database; private constructor() {} static getInstance() {if(!Database.instance) Database.instance = new Database(); return Database.instance;}}. Problems: (1) Global state - shared mutable state across application, race conditions in concurrent environments. (2) Hidden dependencies - class doesn't declare Database dependency, can't see in constructor. (3) Hard to test - can't inject mock, singleton persists across tests (test pollution). (4) Violates DIP - code depends directly on concrete Database singleton, not abstraction. (5) Lifecycle complexity - when to initialize? reset? Overuse example: ConfigSingleton, LoggerSingleton, DatabaseSingleton, CacheSingleton, AnalyticsSingleton, EmailSingleton - everything global. Refactoring: Dependency injection - class UserService {constructor(private db: Database, private logger: Logger) {}}. Container manages single instance if needed. Benefits: Explicit dependencies, testable (inject mocks), no global state, respects DIP (depend on interfaces). When Singleton acceptable: Truly single resource (hardware device driver), thread-safe immutable configuration. Modern: React Context, NestJS providers replace singletons with proper DI. Testing: singleton.resetInstance() hacks in tests indicate overuse. Scoped singletons: One instance per DI container scope (request, session) better than global.
Premature Optimization: Optimizing before knowing performance bottlenecks - sacrifices maintainability (SOLID) for unmeasured performance gains. Donald Knuth: 'Premature optimization is root of all evil.' Detection: Complex code for theoretical performance (no measurements), micro-optimizations everywhere (using bitwise ops in business logic), choosing complex architecture for scalability that's not needed (microservices for 10 users). Example: class UserService {private cache = new Map(); findById(id: string) {if(this.cache.has(id)) return this.cache.get(id); const user = this.db.query(SELECT * FROM users WHERE id = ${id}); this.cache.set(id, user); return user;}}. Problem: Added caching complexity without measuring if database is bottleneck. Cache invalidation adds bugs. SQL injection vulnerability from optimization rush. SOLID violations: Violates SRP (UserService now manages cache + users), violates OCP (cache strategy hardcoded, can't extend), poor separation of concerns. Refactoring: Measure first - use profiler, find actual bottleneck. If database is issue: Extract caching to decorator/middleware. class CachedRepository implements UserRepository {constructor(private repo: UserRepository, private cache: Cache) {}}. Benefits: Clean code first, optimize when needed, SOLID maintained, optimization isolated. Modern: Profile with Chrome DevTools, New Relic, DataDog. Optimize hot paths only (20% code uses 80% resources). Rule: Make it work, make it right, make it fast (in that order).