summaryrefslogtreecommitdiff
path: root/prompts/skills/solid-principles/references/ocp.md
blob: 476b7d293cd68123ced3b377b5afb472254a8bc7 (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
# 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.