SOLID in the AI Era (Part 2): Open/Closed Principle Without Overengineering
Part 2 of a SOLID series: how to apply Open/Closed Principle pragmatically, extend behavior safely, and avoid unnecessary abstraction.
In Part 1, I covered SRP as the foundation for clean boundaries.
Part 2 is about O: Open/Closed Principle (OCP).
The classic definition is useful:
Software entities should be open for extension, but closed for modification.
But the way this principle is taught often pushes teams into premature abstractions. The real value of OCP is not adding layers. The real value is reducing risk when requirements evolve.
What OCP Actually Means
OCP does not mean "never edit existing code."
It means you design stable core behavior so that common future variation can be added with minimal changes to already-tested logic.
A practical interpretation:
- Protect stable, high-confidence code paths
- Add new behavior through clear extension points
- Avoid reopening core modules for every new variant
If every feature request forces edits to the same central file, your change risk grows with each release.
OCP and the Rest of SOLID
OCP works best when the other principles are already in place.
- SRP gives focused modules that are easier to extend safely
- LSP ensures new implementations remain substitutable
- ISP keeps contracts narrow so extension points stay clean
- DIP helps core logic depend on abstractions, not concrete details
I think of it this way: SRP gives you clean modules. OCP gives those modules a safe growth path.
A Simple Intuition: Building Codes and New Tenants
Imagine a building that follows a strong safety code.
When a new tenant arrives, you customize interiors and services. You do not rewrite the structural foundation every time.
Good software architecture is similar:
- Core invariants stay stable
- New cases plug into designed seams
- Existing behavior remains trustworthy
OCP is about preserving structural confidence while allowing business change.
Where OCP Delivers Real Value
Not every module needs OCP-heavy design. Apply it where change patterns are predictable and frequent.
High-value candidates:
- Payment providers
- Notification channels
- Discount or pricing rules
- Export/import formats
- Policy engines and feature variants
Low-value candidates:
- Small modules with low volatility
- One-off scripts
- Areas with no realistic extension pressure
If extension demand is low, a direct implementation is usually better.
The Tradeoff: Flexibility vs. Simplicity
This is the part that matters most in practice.
You can over-apply OCP and make code harder to understand than the problem itself.
Common overengineering signals:
- Interface for a single implementation "just in case"
- Deep indirection chains for simple logic
- Many pass-through wrappers with no decision value
- Plugin architecture before actual plugin needs exist
A better approach is progressive design:
- Start simple: handle one or two variants directly
- Extract seams: when change repeats in the same area
- Generalize carefully: only after recurring extension pressure
This keeps complexity proportional to real requirements.
A Practical Evolution Path
Stage 1: Direct and Clear
Use straightforward conditionals when the problem space is small and stable.
At this stage, clarity beats abstraction.
Stage 2: Identify Repeated Change
When you see frequent edits to the same decision logic, that is a signal.
Now create an extension point that isolates variation from core flow.
Stage 3: Independent Extension
Only when variants need independent ownership or release cadence should you move toward registries, handlers, or plugin-like structures.
Earn this complexity. Do not assume it.
Why OCP Matters More in the AI Era
AI-assisted coding increases implementation speed. That also increases the speed at which bad structure spreads.
A weak design can now be duplicated across many files in a single day.
OCP gives teams a guardrail:
- Stable core flows remain stable
- New behavior is added in bounded areas
- Reviews focus on isolated extensions, not fragile rewrites
In other words, OCP helps convert generation speed into reliable delivery speed.
Common Misunderstandings
-
Myth: OCP means no modifications ever
Reality: You still modify code; the goal is to reduce risky modifications to stable core behavior. -
Myth: OCP always requires polymorphism and interfaces
Reality: Sometimes a small function map or strategy table is enough. -
Myth: More abstraction is always better design
Reality: Abstraction is a cost. Pay it only when it buys real change safety.
A Pragmatic OCP Checklist
Before introducing a new abstraction, I ask:
- What kind of change do I expect, specifically?
- How often has this area changed recently?
- Can I add the next variant without touching stable core logic?
- Does this abstraction reduce risk, or just relocate complexity?
- Will a new teammate understand this extension flow quickly?
If the answers are weak, I keep it simple and revisit later.
Wrap Up
Open/Closed Principle is not about architecture theater.
It is about protecting what already works while making future changes safer.
Use OCP where variation is real and recurring. Skip it where simplicity is the better engineering decision.
The best systems are not the most abstract. They are the easiest to change with confidence.