The Dark Side of SOLID
Problem
Everyone is asking about SOLID principles in all interviews and most people are trying to enforce them without really thinking.
Goal
The goal of this article is to underline that these, like many other principles, are not meant to be used everywhere like a silver bullet. Like any other pattern, you need to know every one of them, but you shouldn’t use them blindly.
Let’s dig into them
Single Responsibility Principle (SRP) — In very small projects, enforcing SRP can lead to an excessive number of classes, making the code harder to follow. Sometimes, a class with a few related responsibilities is actually easier to understand. Think about a “doctor” that you use to correct some data. If the doctor is a console app with 200 lines of code in total, does it really make sense to create 10 classes?
Open/Closed Principle — Applying it all over the place can lead to overly complex inheritance structures and unnecessary abstraction. Extending behaviour with inheritance or interfaces might be overkill.
Liskov Substitution Principle (LSP) — Enforcing LSP might limit design flexibility in some cases, especially when dealing with polymorphic behaviour that doesn’t strictly fit into the “is-a” relationship. Sometimes, a pragmatic approach might require bending LSP to address specific requirements or performance needs. Also, if you actually look over the dependency tree after you just use LSP without thinking, you might go crazy.
Interface Segregation Principle (ISP) — Creating too many specific interfaces can result in a huge number of types and contracts, making it hard to track or understand the process flow of your app. In smaller applications, it may be simpler to consolidate interfaces.
Dependency Inversion Principle (DIP) — Overuse of dependency injection and abstraction can increase complexity, especially in simpler codebases. If you use it only because it’s cool, and use it everywhere extensively, this may also impact runtime performance and it might end up with an app that is hard to debug or test without a solid dependency configuration. Think about real-time data processing or in games, are you sure you want 1000 layers? This will end up with multiple unnecessary layers of abstractions, unnecessary allocations and poor performance.
General issues
- You might get a complexity overhead, just by applying blindly S and I. You might end up with a huge boilerplate code, for nothing.
- In apps that in the past 2–3 years you never changed a line of code, if you start applying O, all over the place, you’ll end up with a huge refactoring and for sure that code won’t be simple anymore.
- If you just use them all the time without thinking, I am almost certain that you’ll end up with a premature unnecessary generalization.
Conclusion
Stop behaving like this is the word of God and SOLID is from the bible. Think about KISS and YAGNI also.
I am curious how many people will hate me or think I went crazy by writing this article.