diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-27 22:51:51 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-27 22:51:51 +0200 |
| commit | d501cff2c6c521d4d8ff0d535164ed70ac790c84 (patch) | |
| tree | 962103bbdd03304cf23e54cc19288576014fb250 | |
| parent | 86c0aa6c11eabe10ac7feedacce2e8f879920a9d (diff) | |
add solid principles
| -rw-r--r-- | prompts/skills/solid-principles/SKILL.md | 123 | ||||
| -rw-r--r-- | prompts/skills/solid-principles/references/dip.md | 127 | ||||
| -rw-r--r-- | prompts/skills/solid-principles/references/isp.md | 110 | ||||
| -rw-r--r-- | prompts/skills/solid-principles/references/lsp.md | 114 | ||||
| -rw-r--r-- | prompts/skills/solid-principles/references/ocp.md | 88 | ||||
| -rw-r--r-- | prompts/skills/solid-principles/references/srp.md | 92 |
6 files changed, 654 insertions, 0 deletions
diff --git a/prompts/skills/solid-principles/SKILL.md b/prompts/skills/solid-principles/SKILL.md new file mode 100644 index 0000000..8293150 --- /dev/null +++ b/prompts/skills/solid-principles/SKILL.md @@ -0,0 +1,123 @@ +--- +name: solid-principles +version: 1.0.0 +description: > + This skill should be used when the user asks to "check SOLID violations", + "audit class design", "review code quality", "find design smells", or + "improve object-oriented architecture". Also triggers when the user + mentions a principle by name (e.g., "check SRP", "is this violating LSP?", + "dependency inversion"), or asks about coupling, cohesion, or class + responsibilities. Supports checking all five principles at once or + focusing on a single principle. +--- + +# SOLID Principles + +Analyze source code for violations of Robert C. Martin's SOLID principles of +object-oriented design. Produce actionable findings with severity ratings, +code locations, and concrete refactoring suggestions. + +## Subcommands + +Request a full audit or focus on a single principle: + +| Command Pattern | Principle | Reference | +|----------------|-----------|-----------| +| `solid all` / `solid check` / `solid` | All five principles | All references | +| `solid srp` | Single Responsibility | `references/srp.md` | +| `solid ocp` | Open/Closed | `references/ocp.md` | +| `solid lsp` | Liskov Substitution | `references/lsp.md` | +| `solid isp` | Interface Segregation | `references/isp.md` | +| `solid dip` | Dependency Inversion | `references/dip.md` | + +When no subcommand is specified, default to checking all five principles. +When a principle is mentioned by name (even without saying "solid"), match it to +the appropriate subcommand. + +## Workflow + +### 1. Identify Target Code + +Determine what code to analyze: +- When files or a directory are provided, use those. +- When a class/module is referenced by name, locate it. +- When ambiguous, ask which files or directories to scan. + +Supported languages: any OO language (Python, Java, TypeScript, C#, C++, Kotlin, +Go with struct methods, Rust with impl blocks, etc.). Adapt the principle checks +to the idioms of the target language — not every principle manifests identically +across languages. + +### 2. Load Principle References + +Before analyzing, read the reference file(s) for the requested principle(s): + +- [`references/srp.md`](references/srp.md) for SRP checks +- [`references/ocp.md`](references/ocp.md) for OCP checks +- [`references/lsp.md`](references/lsp.md) for LSP checks +- [`references/isp.md`](references/isp.md) for ISP checks +- [`references/dip.md`](references/dip.md) for DIP checks + +For a full audit (`solid all`), read all five. + +### 3. Analyze + +For each target file/class, apply the violation patterns from the loaded references. +Think carefully about each pattern — not every heuristic match is a true violation. +Consider context, project size, and pragmatism. A 50-line script doesn't need the +same architectural rigor as a large production system. + +### 4. Report Findings + +Present findings using this structure: + +#### Per Violation + +``` +**[PRINCIPLE] Violation — Severity: HIGH | MEDIUM | LOW** +Location: `filename.py`, class `ClassName`, lines ~XX-YY +Issue: Clear description of what violates the principle and why it matters. +Suggestion: Concrete refactoring approach with brief code sketch if helpful. +``` + +Severity guidelines: +- **HIGH**: Active maintenance pain, bug risk, or blocks extensibility. +- **MEDIUM**: Code smell that will cause problems as the codebase grows. +- **LOW**: Minor design impurity, worth noting but fine to defer. + +#### Summary + +After all findings, provide: +- A count table: `| Principle | HIGH | MEDIUM | LOW |` +- Top 3 priorities: which violations to fix first and why. +- Overall assessment: one paragraph on the code's structural health. + +### 5. Refactor Mode (Optional) + +When a fix or refactoring is requested (e.g., "fix this", "refactor it", +"show me the clean version"), produce refactored code that resolves the identified +violations. Explain each change briefly. + +## Pragmatism Guidelines + +These are guidelines, not laws. Apply judgment: + +- Small utility scripts and prototypes get a lighter touch. Don't flag a 30-line + script for lacking dependency injection. +- Some "violations" are conscious trade-offs. When a rationale is provided for + a trade-off, acknowledge it rather than insisting on purity. +- Language idioms matter. A Python module with top-level functions isn't violating + SRP just because it's not a class. Go interfaces are implicitly satisfied, so + ISP looks different there. +- Prefer actionable findings over exhaustive catalogs. Five important findings + beat twenty trivial ones. + +## Example Interaction + +**User**: `solid srp` (with a file attached) + +**Claude**: +1. Reads `references/srp.md` +2. Analyzes the attached file for SRP violations +3. Reports findings with locations, severity, and suggestions +4. Provides a summary with priorities diff --git a/prompts/skills/solid-principles/references/dip.md b/prompts/skills/solid-principles/references/dip.md new file mode 100644 index 0000000..8597f1c --- /dev/null +++ b/prompts/skills/solid-principles/references/dip.md @@ -0,0 +1,127 @@ +# Dependency Inversion Principle (DIP) + +> "High-level modules should not depend on low-level modules. Both should depend +> on abstractions. Abstractions should not depend on details. Details should +> depend on abstractions." +> — Robert C. Martin + +## Core Idea + +The flow of control and the flow of source code dependency should point in +opposite directions at architectural boundaries. Business logic (high-level +policy) should define the interfaces it needs, and infrastructure (low-level +detail) should implement those interfaces. This makes the core of your +application independent of databases, frameworks, and external services. + +DIP is NOT simply "use interfaces everywhere." It specifically addresses the +direction of dependency at boundaries between layers. + +## Violation Patterns + +### 1. Direct Infrastructure Coupling +**Heuristic**: Business logic classes directly import and instantiate +infrastructure classes (database clients, HTTP libraries, file system APIs). + +**Look for**: +- Business logic that imports `psycopg2`, `requests`, `boto3`, `smtplib`, etc. +- Classes that create their own database connections or HTTP clients. +- Domain logic files with imports from infrastructure/framework packages. + +**Refactoring**: Define an interface/protocol in the domain layer for the +capability needed (e.g., `UserRepository`, `EmailSender`). Implement it in +the infrastructure layer. Inject the implementation at composition time. + +### 2. Concrete Class Injection +**Heuristic**: Dependency injection is used, but concrete classes are injected +instead of interfaces/protocols. + +**Look for**: +- Constructor type hints referencing concrete classes rather than abstractions: + `def __init__(self, db: PostgresDatabase)` instead of `def __init__(self, db: Database)`. +- Factory methods that return concrete types. +- DI container bindings that map concrete to concrete. + +**Refactoring**: Introduce an abstraction and depend on it. The concrete class +implements the abstraction and is wired in at the composition root. + +### 3. Framework Entanglement +**Heuristic**: Domain/business classes inherit from or directly use framework +base classes, coupling the domain to the framework. + +**Look for**: +- Domain entities extending ORM base classes (e.g., `class User(db.Model)`). +- Business logic decorated with framework decorators that can't run without the framework. +- Domain objects that require framework initialization to instantiate. + +**Refactoring**: Keep domain objects plain (POJOs/dataclasses/structs). Create +adapter/mapper layers between the domain and the framework. The domain defines +what it needs; the framework adapts to it, not the other way around. + +### 4. Upward Dependencies +**Heuristic**: A low-level module imports from a high-level module, creating +a circular or inverted dependency. + +**Look for**: +- A utility/infrastructure module importing from a service/domain module. +- Circular import errors or lazy imports used to work around circular dependencies. +- A "shared" module that depends on specific feature modules. + +**Refactoring**: Extract the shared contract into an interface module that both +layers can depend on. The dependency arrows should point inward (toward +abstractions), not upward or circularly. + +### 5. Service Locator Anti-Pattern +**Heuristic**: Instead of injecting dependencies, code reaches into a global +registry or container to pull them out at runtime. + +**Look for**: +- Calls like `Container.get(UserService)` or `ServiceLocator.resolve("db")` + scattered through business logic. +- Global singleton access patterns in domain code. +- `import settings` / `from config import db` in domain logic. + +**Refactoring**: Use constructor injection. Dependencies are declared explicitly +in the constructor signature and wired at the composition root (main/entry point). + +### 6. Missing Abstraction Boundary +**Heuristic**: There's no interface at all between layers — high-level code +directly calls low-level code with no seam for substitution. + +**Look for**: +- No interfaces/protocols/ABCs in the codebase at architectural boundaries. +- Inability to run business logic tests without a real database/API. +- Integration tests where unit tests should suffice. + +**Refactoring**: Introduce interface boundaries at the points where +high-level policy meets low-level detail. You don't need interfaces everywhere — +only at architectural boundaries. + +## Language-Specific Notes + +- **Python**: Use `Protocol` (structural subtyping) or `ABC` for abstractions. + Python's duck typing can mask DIP violations — things work until you try to + test or swap implementations. Type hints with concrete classes are a smell. +- **Java/C#**: Natural territory for DIP with strong interface support and + mature DI frameworks (Spring, .NET DI). Watch for `new` inside business + logic as the primary violation signal. +- **TypeScript**: Use interfaces or abstract classes. Watch for direct imports + of concrete implementations across module boundaries. +- **Go**: Interfaces are implicitly satisfied, which makes DIP natural. Define + interfaces in the consumer package (where they're needed), not the provider + package. This is idiomatic Go and inherently supports DIP. +- **Rust**: Traits serve as the abstraction boundary. Watch for concrete type + dependencies where `dyn Trait` or generic `T: Trait` would be more appropriate + at module boundaries. + +## False Positives to Avoid + +- **Leaf nodes**: Code at the edges of the system (entry points, scripts, CLI + tools) naturally depends on concretes — that's the composition root. Don't + flag `main()` for instantiating concrete classes. +- **Value objects and DTOs**: These are details, but they're typically stable + and shared across layers. Depending on a `User` dataclass is fine. +- **Standard library**: Depending on `datetime`, `pathlib`, `collections`, etc. + is not a DIP violation. These are stable abstractions. +- **Small projects**: A 500-line application probably doesn't need interface + boundaries. DIP shines in systems with multiple developers, long lifespans, + and complex infrastructure dependencies. diff --git a/prompts/skills/solid-principles/references/isp.md b/prompts/skills/solid-principles/references/isp.md new file mode 100644 index 0000000..150084b --- /dev/null +++ b/prompts/skills/solid-principles/references/isp.md @@ -0,0 +1,110 @@ +# Interface Segregation Principle (ISP) + +> "Clients should not be forced to depend on interfaces they do not use." +> — Robert C. Martin + +## Core Idea + +Keep interfaces small and focused. When a class is forced to implement methods +it doesn't need, those empty/stub methods are dead weight that couples the class +to concerns it shouldn't know about. ISP says: split large interfaces into +smaller, role-specific ones so that implementing classes only depend on the +methods they actually use. + +ISP works hand-in-hand with SRP at the interface level — where SRP is about +implementation cohesion, ISP is about contract cohesion. + +## Violation Patterns + +### 1. Fat Interface +**Heuristic**: An interface/abstract class with 7+ methods where most +implementations only meaningfully use a subset. + +**Look for**: +- Implementations with multiple `pass` / `return None` / `throw NotImplementedError` + stub methods. +- An interface that mixes read and write operations when some consumers are read-only. +- A "God interface" that defines operations for multiple unrelated capabilities. + +**Refactoring**: Split into role-based interfaces. For example, +`IRepository` → `IReader` + `IWriter`. Implementing classes can implement +one or both. + +### 2. Marker Methods / Stub Implementations +**Heuristic**: A class implements an interface but leaves some methods as +no-ops or throws exceptions. + +**Look for**: +- Methods with body `pass`, `return null`, `throw new UnsupportedOperationException()`. +- Comments like "// not needed for this implementation". +- Methods that always return a default/empty value because the class doesn't + actually support that operation. + +**Refactoring**: The class shouldn't be forced to implement those methods. +Extract the unsupported methods into a separate interface. + +### 3. Parameter Interfaces / Bag-of-Methods +**Heuristic**: A function or constructor accepts a large interface but only +calls 1-2 methods on it. + +**Look for**: +- A function that takes `IUserService` but only calls `getUserById()`. +- Constructor injection of a broad service where only a narrow slice is used. +- Test mocks that must implement 10 methods but the test only exercises 2. + +**Refactoring**: Depend on a smaller interface that exposes only what's needed. +This also makes testing dramatically easier. + +### 4. Leaking Infrastructure into Domain Interfaces +**Heuristic**: A domain-level interface includes infrastructure concerns like +serialization, caching, or transport. + +**Look for**: +- Domain interfaces with methods like `toJson()`, `serialize()`, `cache()`. +- An interface mixing business operations (`calculateTotal()`) with + infrastructure (`save()`, `publish()`). + +**Refactoring**: Separate domain interfaces from infrastructure interfaces. +A class can implement both, but consumers depend only on the interface relevant +to their layer. + +### 5. Callback / Event Listener Bloat +**Heuristic**: An event listener or callback interface requires implementing +many event handlers when most consumers care about only one or two. + +**Look for**: +- Adapter base classes that exist solely to provide empty default implementations + of a fat listener interface (e.g., Java's `MouseAdapter`). +- Event handlers with mostly empty method bodies. + +**Refactoring**: Use single-method interfaces (functional interfaces) per +event type, or an event bus pattern where consumers subscribe to specific events. + +## Language-Specific Notes + +- **Python**: Python uses Protocols and ABCs. ISP violations show up as Protocol + classes with too many required methods. Also applies to function signatures — + if a function accepts a complex object but only reads `.name`, consider accepting + just the string. +- **Java/C#**: Classic ISP territory. Watch for interfaces with `default` methods + (Java 8+) used to paper over the fact that the interface is too broad. +- **TypeScript**: `Pick<T, K>` and mapped types allow narrowing interfaces at the + call site, but the underlying interface might still be too broad. Also check for + `Partial<T>` used to avoid implementing all properties. +- **Go**: Interfaces are implicitly satisfied and typically small (often 1-2 methods), + which naturally encourages ISP. Violations happen when someone defines a large + explicit interface mimicking Java-style patterns. +- **Rust**: Traits can become fat. Look for traits with many methods where + implementors use `todo!()` or `unimplemented!()` for some. + +## False Positives to Avoid + +- **Genuinely cohesive interfaces**: A `Stream` interface with `read()`, `write()`, + `seek()`, `close()` is cohesive if all stream types support all operations. + Only flag it if some streams meaningfully can't support some operations. +- **Standard library interfaces**: Don't flag language-standard interfaces + (e.g., Java's `Iterable`, Python's `Sequence`) as too broad — they're + established contracts. +- **Small total interface count**: If an interface has 3-4 methods and all + implementors use all of them, it's fine. ISP isn't about making every + interface have exactly one method. diff --git a/prompts/skills/solid-principles/references/lsp.md b/prompts/skills/solid-principles/references/lsp.md new file mode 100644 index 0000000..bfe74b7 --- /dev/null +++ b/prompts/skills/solid-principles/references/lsp.md @@ -0,0 +1,114 @@ +# Liskov Substitution Principle (LSP) + +> "Objects of a supertype shall be replaceable with objects of a subtype without +> altering the correctness of the program." +> — Barbara Liskov + +## Core Idea + +If class `B` extends class `A`, you should be able to use `B` anywhere `A` is +expected without surprises. Subtypes must honor the behavioral contract of their +parent — not just the type signature, but the semantic expectations: preconditions, +postconditions, invariants, and side effects. + +LSP is the formal foundation of polymorphism. When it's violated, inheritance +becomes a liability rather than a tool. + +## Violation Patterns + +### 1. Refusing Inherited Behavior +**Heuristic**: A subclass overrides a method to throw `NotImplementedError`, +return `None`, or do nothing — effectively saying "I don't support this." + +**Look for**: +- `raise NotImplementedError` in an override. +- Override that returns a hardcoded empty/null value. +- Override with `pass` as the body. +- Comments like "not applicable for this subtype." + +**Refactoring**: The inheritance hierarchy is wrong. Either extract the +unsupported method into a separate interface/mixin, or restructure the hierarchy +so the subclass isn't forced to inherit behavior it can't fulfill. + +### 2. Strengthened Preconditions +**Heuristic**: A subclass method requires MORE from its inputs than the parent's +contract promises. + +**Look for**: +- Added `if` guards at the start of an override that reject inputs the parent + would accept (e.g., parent accepts any int, subclass rejects negatives). +- Type narrowing in overrides (e.g., parent accepts `Animal`, subclass demands `Dog`). +- Additional validation not present in the parent. + +**Refactoring**: Subtypes should accept at least everything the parent accepts. +Widen preconditions or rethink the hierarchy. + +### 3. Weakened Postconditions +**Heuristic**: A subclass method provides LESS in its return value than the +parent guarantees. + +**Look for**: +- Parent returns a fully populated object; subclass returns a partial/null result. +- Parent guarantees sorted output; subclass doesn't maintain the order. +- Parent guarantees non-null; subclass may return null. + +**Refactoring**: Subtypes must deliver at least what the parent promises. +Strengthen the subclass implementation or adjust the contract. + +### 4. Violated Invariants +**Heuristic**: A subclass breaks a property that the parent always maintains. + +**Look for**: +- Parent maintains `balance >= 0`; subclass allows negative balance. +- Parent ensures a collection is always sorted; subclass inserts without sorting. +- State machine invariants broken (e.g., skipping required transitions). + +**Refactoring**: Either enforce the invariant in the subclass or don't inherit. + +### 5. The Classic Rectangle-Square Problem +**Heuristic**: A subclass constrains independent properties of the parent, creating +contradictory behavior under mutation. + +**Look for**: +- Subclass overrides setters to enforce constraints that break parent behavior + (e.g., `Square.set_width()` also sets height). +- Tests that pass for the parent but fail for the subclass. + +**Refactoring**: Use composition or a common interface instead of inheritance. +Make `Square` and `Rectangle` siblings, not parent-child. + +### 6. Exception Surprises +**Heuristic**: A subclass throws exceptions that callers of the parent type +wouldn't expect. + +**Look for**: +- Override that throws new checked/unchecked exception types not in the parent's + contract. +- Override that can fail in scenarios where the parent is documented as safe. + +**Refactoring**: Subtypes should only throw exceptions that are subtypes of +the parent's declared exceptions, or handle errors internally. + +## Language-Specific Notes + +- **Python**: Duck typing makes LSP violations subtle — they manifest at runtime + as `AttributeError` or unexpected `None`. Watch for Protocol/ABC mismatches. +- **Java/C#**: The compiler enforces type signatures but NOT behavioral contracts. + Look for `@Override` methods that change semantics. +- **TypeScript**: Structural typing means you can create implicit subtypes that + violate LSP. Watch for interface implementations that throw on "unsupported" + methods. +- **Go**: No inheritance, but interface satisfaction can be violated if a concrete + type's method has different semantics than the interface implies. + +## False Positives to Avoid + +- **Template Method pattern**: Abstract methods that are *designed* to be overridden + with different behavior are not LSP violations — the parent's contract explicitly + delegates to subclasses. +- **Intentional restriction**: If a subclass is documented as a restricted variant + and callers are aware, this may be acceptable (though it suggests composition + over inheritance). +- **Different behavior, same contract**: A `LinkedList` and `ArrayList` behave + differently in performance characteristics but both honor the `List` contract. + That's fine. diff --git a/prompts/skills/solid-principles/references/ocp.md b/prompts/skills/solid-principles/references/ocp.md new file mode 100644 index 0000000..476b7d2 --- /dev/null +++ b/prompts/skills/solid-principles/references/ocp.md @@ -0,0 +1,88 @@ +# Open/Closed Principle (OCP) + +> "Software entities should be open for extension, but closed for modification." +> — Bertrand Meyer (popularized by Robert C. Martin) + +## Core Idea + +You should be able to add new behavior to a system without modifying existing, +working code. This is achieved through abstractions: interfaces, abstract classes, +strategy patterns, plugins, or higher-order functions. When new requirements arrive, +you extend (add new implementations) rather than edit existing classes. + +OCP doesn't mean "never touch existing code" — it means design so that the *common* +axis of change can be handled by extension rather than modification. + +## Violation Patterns + +### 1. Type-Switching / Conditional Dispatch +**Heuristic**: `if/elif/else` or `switch/case` chains that branch on a type, +status, enum, or string tag to decide behavior. + +**Look for**: +- `if isinstance(x, Foo)` / `if type == "bar"` chains. +- The same switch structure repeated across multiple methods/locations. +- Adding a new type requires editing every switch statement. + +**Refactoring**: Replace conditionals with polymorphism. Define an interface, +implement per type, and dispatch via method calls instead of branches. + +### 2. Hardcoded Strategies +**Heuristic**: An algorithm or behavior is baked directly into a class with no +way to swap it out without modifying the class itself. + +**Look for**: +- A class that internally instantiates its collaborators (e.g., `self.sorter = QuickSort()`). +- Formatting/encoding logic written inline rather than delegated. +- Configuration that requires code changes (magic strings, hardcoded URLs). + +**Refactoring**: Extract the strategy into an interface/protocol and inject it. +The class becomes open to new strategies without modification. + +### 3. Modification Magnets +**Heuristic**: A single file or class that must be edited for every new feature, +even when the features are independent. + +**Look for**: +- A router/registry where every new handler requires adding a line. +- A factory with a growing `if/elif` chain. +- A config file that's really just a list of hardcoded mappings. + +**Refactoring**: Use registration patterns (decorators, plugin discovery, +convention-based loading) so new features register themselves. + +### 4. Rigid Data Pipelines +**Heuristic**: A processing pipeline where adding a new step requires modifying +the pipeline class rather than plugging in a new processor. + +**Look for**: +- Sequential method calls in a `process()` method with no way to add/remove steps. +- ETL code where each new transformation is added by editing the main function. + +**Refactoring**: Use a pipeline/chain pattern where processors implement a common +interface and can be composed dynamically. + +## Language-Specific Notes + +- **Python**: Protocols and ABCs enable OCP. Decorators and first-class functions + are natural extension points. `functools.singledispatch` is an idiomatic + alternative to type-switching. +- **Java/C#**: Interfaces and abstract classes are the primary mechanism. Look + for `switch` on enums as a common violation. +- **TypeScript**: Union types with exhaustive switches are sometimes intentional + (discriminated unions). This is a valid pattern when the set of types is truly + closed. Flag it only when the set is expected to grow. +- **Go**: Interface satisfaction is implicit, which makes OCP natural. Watch for + type-switch statements (`switch v := x.(type)`) as potential violations. + +## False Positives to Avoid + +- **Discriminated unions in functional-style code**: TypeScript `type Shape = Circle | Square` + with exhaustive pattern matching is a deliberate design choice, not a violation — + the compiler enforces handling all cases. +- **Simple mappings**: A dictionary/map from string to handler is often fine and + doesn't need a full plugin system. +- **Early-stage code**: Adding abstractions before you know the axis of change + is premature. OCP is most valuable when applied to known extension points. +- **Configuration**: Not every hardcoded value is an OCP violation. Constants + that genuinely don't change are fine. diff --git a/prompts/skills/solid-principles/references/srp.md b/prompts/skills/solid-principles/references/srp.md new file mode 100644 index 0000000..a6fd5d5 --- /dev/null +++ b/prompts/skills/solid-principles/references/srp.md @@ -0,0 +1,92 @@ +# Single Responsibility Principle (SRP) + +> "A class should have one, and only one, reason to change." +> — Robert C. Martin + +## Core Idea + +SRP is about **cohesion**: every module, class, or function should be responsible +to exactly one actor (stakeholder). If two different actors depend on the same class +for different reasons, changes for one actor risk breaking the other. + +This is NOT simply "a class should do one thing." A class can have multiple methods +and still satisfy SRP if they all serve the same actor and change for the same reason. + +## Violation Patterns + +### 1. God Class / Blob Class +**Heuristic**: Class exceeds ~200 lines, has 10+ public methods, or touches 3+ +unrelated domains (e.g., database access + email sending + PDF generation). + +**Look for**: +- Methods that could be grouped into separate, named clusters. +- Instance variables that are only used by a subset of methods. +- Imports at the top that span unrelated domains (e.g., `import smtplib` alongside + `import sqlalchemy` alongside `import matplotlib`). + +**Refactoring**: Extract cohesive method groups into separate classes. The original +class can delegate or coordinate. + +### 2. Mixed Abstraction Levels +**Heuristic**: A single class handles both high-level policy/orchestration AND +low-level detail (e.g., a `ReportService` that both decides *what* to report and +*how* to format HTML). + +**Look for**: +- Methods mixing business logic with I/O, serialization, or formatting. +- A class that would need to change if either the business rules OR the + infrastructure changes. + +**Refactoring**: Separate policy from mechanism. Create a high-level class for +business rules and a low-level class for the technical details. + +### 3. Feature Envy +**Heuristic**: A method in class A spends most of its body accessing data from +class B. + +**Look for**: +- Long chains like `self.order.customer.address.city`. +- Methods that take another object as a parameter and then call 5+ methods on it. + +**Refactoring**: Move the method to the class whose data it primarily uses. + +### 4. Data Class Serving Multiple Masters +**Heuristic**: A data/model class has methods added for different consumers +(e.g., `to_api_response()`, `to_csv_row()`, `to_admin_view()`). + +**Look for**: +- Serialization methods for different formats or consumers on the same class. +- Presentation logic mixed with domain logic. + +**Refactoring**: Use separate serializer/presenter classes per consumer. + +### 5. Utility Dumping Ground +**Heuristic**: A `Utils`, `Helpers`, or `Common` class/module that accumulates +unrelated functions over time. + +**Look for**: +- Functions in the same module with zero shared state or concept. +- The file grows with every feature because "it didn't fit anywhere else." + +**Refactoring**: Group related utilities into purpose-named modules +(e.g., `string_utils`, `date_utils`, `validation`). + +## Language-Specific Notes + +- **Python**: Modules themselves can be a unit of responsibility. A module with + related top-level functions is fine — SRP violations happen when unrelated + concerns are lumped together. +- **Java/C#**: Classes are the natural unit. Watch for `Service` classes that + accumulate unrelated methods. +- **TypeScript**: Both modules and classes apply. Watch for barrel files that + re-export unrelated functionality. +- **Go**: Packages are the unit of responsibility. A package with a clear name + and focused purpose satisfies SRP. + +## False Positives to Avoid + +- A class with many methods that all operate on the same cohesive data structure + (e.g., a `Matrix` class with `add`, `multiply`, `transpose`, `determinant`). +- A facade or coordinator class that delegates to focused collaborators — the + coordination itself is the single responsibility. +- DTOs/value objects with multiple fields — having many fields isn't a violation. |
