summaryrefslogtreecommitdiff
path: root/prompts/skills/solid-principles/references/isp.md
blob: 150084b748e0b07c9645f622ed936535342f1875 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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.