How 7 Years of Shipping Code Turned Me Against “Textbook” SOLID — And Then Back to It
Most developers think they understand SOLID because they can recite the five principles.In reality, I’ve seen senior engineers — including myself years ago — apply SOLID like religion and end up with code that is over-abstracted, unreadable, and impossible to change.
The truth is simple:
SOLID isn’t about elegance — it’s about survival in large systems.
I never appreciated it when things were small and fresh. I appreciated it when a 2-year-old codebase started to rot under real users, real deadlines, and real mistakes.
The Wake-Up Project (Real Context, Not Theory)
Years ago, I worked on a product that started clean and fast. After dozens of features, the code turned into what I call a dependency cemetery — every edit broke something else. Adding one feature meant changing twelve files. No one touched anything without fear.
That wasn’t a lack of intelligence.
That was a lack of design discipline.
That’s when SOLID stopped being a book concept and started being a survival strategy.
S — Single Responsibility Principle
Not: “One class = one function.”
Actually: “One reason to change.”
Bad (too many reasons to change):
class InvoiceService {
generateInvoice() { ... }
calculateTax() { ... }
sendToCustomer() { ... }
}
Improved:
class InvoiceGenerator { generateInvoice() { ... } }
class TaxCalculator { calculateTax() { ... } }
class InvoiceDispatcher { sendToCustomer() { ... } }
When tax logic changes, only one place changes.
When delivery logic changes, only one place changes.
That’s the point.
O — Open/Closed Principle
Systems must be easy to extend but hard to break.
Anti-pattern I’ve seen everywhere:
if(type == "paypal") { ... }
else if(type == "stripe") { ... }
else if(type == "square") { ... }
Every new provider means modifying old code — that’s debt.
Better:
interface PaymentMethod { pay() }
class Paypal implements PaymentMethod { ... }
class Stripe implements PaymentMethod { ... }
Add ApplePay tomorrow — nothing else breaks.
L — Liskov Substitution Principle
If a subclass breaks expectations of its base type,
then your polymorphism was a lie.
Most violations happen because inheritance was used to reuse, not to model abstractions.
I — Interface Segregation Principle
Clients shouldn’t depend on things they don’t use.
I keep seeing “God” interfaces with ten methods, and half the implementors only support five — that’s forced coupling disguised as flexibility.
D — Dependency Inversion Principle
High-level policies should not depend on low-level details.
Wrong approach:
class Report {
constructor(Database db) { this.db = db }
}
Improved:
class Report {
constructor(StorageInterface store) { this.store = store }
}
Now the storage can be DB, API, JSON, or Cloud — the Report doesn’t care.
When NOT to Use SOLID
I don’t apply SOLID when:
- The code is small and unlikely to live long
- The feature is experimental or will be rewritten
- The abstraction would add more files than value
- The team needs speed over structure (early phase)
- The cost of clarity is higher than the cost of change
You don’t spray SOLID everywhere.
You deploy it where survival actually matters.
What Senior Engineers Get Wrong About SOLID
- They apply it too early — overdesigning trivial code
- They apply it dogmatically — converting everything into abstractions
- They treat it as aesthetic correctness, not change-resilience
SOLID is not for today. It is for the day your own success makes your code painful to change.
My Current Rule
don’t start with SOLID.
I refactor towards SOLID when the code earns the right to stay alive.
Premature SOLID creates clutter.
Delayed SOLID creates chaos.
Context decides the timing.
If You Take Only One Sentence From This Article
SOLID is not a philosophy — it is insurance against future complexity.
Like this story? Follow me on Medium!