SOLID Principles Are Not Rules. They Are Warning Signs.
by Arif Ikhsanudin, Backend Developer
When Principles Become a Religion
You're in a code review and someone flags a class as violating the Single Responsibility Principle. The class handles order creation — it validates the input, persists to the database, and publishes an event. The reviewer wants it split into three classes. An animated debate follows about what a "responsibility" is and whether persistence and validation are truly separable concerns.
Forty-five minutes later, you have an OrderValidator, an OrderRepository, and an OrderEventPublisher — plus an orchestration layer that now knows about all three. The code is more SOLID-compliant and harder to follow. The original question — whether this code works correctly and can be safely modified — was never answered.
This happens when SOLID is treated as a rulebook rather than a diagnostic framework.
What the Principles Are Actually For
Robert Martin formulated SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) as a way to describe properties of code that is easy to change. The goal was always maintainability and changeability — not structural purity.
The useful question is never "does this code comply with SOLID?" The useful question is "does this code exhibit the problems SOLID is pointing at?" Those problems have names:
- SRP violation: A class or function changes for more than one reason — meaning changes to unrelated requirements keep breaking the same component. This is the warning sign. Not "it does multiple things."
- OCP violation: Every time a new case is added, you're modifying existing code and risking regressions rather than extending it. The warning sign is the risky modification pattern, not the absence of interfaces.
- LSP violation: You have a subtype that requires callers to know which concrete implementation they're using to behave correctly. The warning sign is
instanceofchecks and casts to subclasses. - ISP violation: Implementors are forced to provide methods they have no business providing — stub implementations, throwing
UnsupportedOperationException. The warning sign is the stub. - DIP violation: High-level business logic directly depends on a database driver or HTTP client. Changing the infrastructure layer forces changes to business code. The warning sign is the coupling, not the absence of an interface.
The Overengineering Failure Mode
Here is how mechanical SOLID compliance goes wrong on real projects.
A service needs to send a notification when an order ships. The naive implementation: call the email library directly from the order service. A SOLID-aware developer correctly notes that the Dependency Inversion Principle suggests depending on an abstraction. So they create:
interface NotificationSender {
void send(Notification notification);
}
class EmailNotificationSender implements NotificationSender { ... }
class SmsNotificationSender implements NotificationSender { ... }
class PushNotificationSender implements NotificationSender { ... }
This is fine — if you actually have multiple notification channels being used. But if the application only ever sends email, you've added an interface and an indirection layer for a flexibility you don't have and haven't committed to building. You've made the codebase harder to navigate for the sake of a principle applied in the absence of the problem it solves.
The correct question was: is there evidence we'll need to swap notification implementations? No? Then the interface is premature.
When SOLID Advice Is Right
The principles are genuinely useful as retrospective diagnostics:
- You're modifying a service to change how orders are taxed, and you keep accidentally breaking the refund calculation in the same file. That's an SRP problem.
- Every time a new payment method is added, you're adding a case to a switch statement that's already forty cases long and regularly causes merge conflicts. That's an OCP problem.
- Your
PremiumUserextendsUser, butPremiumUser.getDiscount()throws an exception for users who haven't set up a billing account, so every caller wraps it in a check. That's an LSP problem.
In all of these cases, SOLID is pointing at genuine pain. The principle names a pattern you've been experiencing as friction.
The Practical Heuristic
Before applying a SOLID transformation, ask: what is the specific change or maintenance problem this is solving? If the answer is "it's just better design" or "it follows the principle," that's not enough. If the answer is "every time we change tax logic we break order fulfillment" or "we've had three bugs in as many weeks because this class does too many things," then the principle is pointing at something real.
Apply the fix proportional to the pain. A class that does two things isn't automatically a problem. A class that regularly causes merge conflicts and bugs because it does two things — that one earns a refactor.
The Practical Takeaway
In your next code review, when a SOLID principle gets invoked, ask the proposer: what specific change, maintenance problem, or failure mode are we protecting against with this restructuring? If they can name it concretely, the review is productive. If they can't, the discussion is about aesthetics, not engineering. Redirect it.