From posts that politely offer their criticisms to others that outright deem them “for dummies,” it seems that bashing the SOLID principles is all the rage nowadays.
The fact that SOLID is being criticized isn’t a bad thing. The problem is that I don’t think the arguments against it are really that good. There’s some valid criticism, but it seems that a large portion of it comes from some misunderstanding of the principles. Some people even read obscure agendas in them.
This post is meant to investigate some of the more common criticisms of the SOLID principles, offering my take on why I believe they’re not quite justified.
SOLID Principles: Some Background
In object-oriented design, the SOLID principles (or simply SOLID) are a group of five design principles meant to make code cleaner, more flexible, and easier to change. The principles were compiled by Robert C. Martin, although he didn’t invent them. In fact, these specific principles are a subset of many principles Martin has been promoting over the years.
The name SOLID is an acronym, made up of the names of five principles. Namely, these principles are
- the single responsibility principle (SRP),
- the open-closed principle (OCP),
- the Liskov substitution principle (LSP),
- the interface segregation principle (ISP), and
- the dependency inversion principle (DIP).
Martin himself didn’t come up with the acronym; rather, it was Michael Feathers that suggested it to him, several years after he was already teaching them. I’ll come back to this later because, believe it or not, the name itself is at the center of some criticisms.
Before we get to the meat of the article, I think it’d make sense to do a quick overview of the five principles so those of you who are familiar with them can get a reminder and those who aren’t can get a sense of what we’re talking about.
The Single Responsibility Principle
The “S” in SOLID stands for single responsibility principle, and it’s probably the most misunderstood of all of them. It basically says that each class should have just one responsibility.
This sentence for me is in the same category as “separate logic from presentation” or similar aphorisms. For some reason that I can’t pinpoint, it just sounded like it must be true, even though I had no idea how to put it into practice when I first heard about it.
But what is a responsibility? In the words of Martin himself:
A class should have one, and only one, reason to change.
A favorite example of his is an “Employee” class that both calculates the pay and saves the employee data do a persistence store. If something changes about the way the pay is calculated, the class will have to be changed. If the DBA makes some change in the database schema, the class will probably have to change as well.
Here we clearly have two reasons for change: one is business logic related and the other infrastructure related. According to the SRP, these concerns shouldn’t reside in the same object.
The Open-Closed Principle
The second principle is the open-closed principle. Unlike SRP, the open-closed principle didn’t ring a bell with me right away, and I know I’m not alone in that. People way smarter than me have struggled with this principle as well.
This is OCP’s definition:
You should be able to extend a class’s behavior, without modifying it.
Ideally, you should only create new classes instead of modifying existing ones.
The Liskov Substitution Principle
The third SOLID principle is named after Barbara Liskov, who defined it in 1988 in this way:
What is wanted here is something like the following substitution property: If
for each object o1 of type S there is an object o2 of type T such that for all
programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T.
The definition above is somewhat formal. Martin defines the LSP as follows:
Derived classes must be substitutable for their base classes.
People sometimes dismiss this principle as being obvious. So why are so many people still writing code like what’s shown below?
1 2 3 4 5 6 7 8 9 10 11 12 |
public void Foo(IAnimal animal) { if (animal is Dog) { // do the thing a certain way } else if (animal is Cat) { // do the thing another way } else // you got the picture } |
In an application that follows LSP, a client consuming a polymorphic interface shouldn’t care about the concrete implementation it’s getting. That’s the LSP in a nutshell.
The Interface Segregation Principle
The ISP is, in my opinion, the most to-the-point of all the principles. It states:
Clients should not be forced to depend upon interfaces that they do not use.
It’s simple and to the point: leave your interfaces as small and granular as possible. It maximizes the probability of other developers reusing them, while at the same time preventing clients from depending upon methods they don’t really need.
The Dependency Inversion Principle
And we’re finally at the last one. The “D” stands for dependency inversion. Yep, that’s right: inversion, not injection, even though they’re closely related, as you’ll see. The DIP states:
Depend on abstractions, not on concretions.
In short, depend on interfaces instead of concrete classes.
Common Criticisms of SOLID
Here’s where the fun begins! After doing a quick overview of the SOLID principles, it’s time to visit some of the common criticisms people make about them.
SOLID Principles Are Vague
A very common SOLID criticism is that they’re vague platitudes. For instance, Dan North calls the SRP a “pointlessly vague principle.” And it’s not hard to find other examples. Is there some truth to this claim?
First, I want you to read the following sentences:
- Favor composition over inheritance.
- Low coupling, high cohesion.
- Separate logic from presentation.
If you’re a developer with at least a few years of experience, I bet you know these sentences and probably agree with and try to abide by these principles when designing and writing your applications.
Now answer me honestly: when you were taking Software Engineering 101 at college, with no job experience whatsoever, did these sentences immediately make sense to you? Just by hearing your teacher mutter “low coupling” a couple of times (no pun intended), you started writing highly decoupled code?
I can’t know for sure, but I’d bet the answer is no. The sentences above are like a condensed piece of software wisdom. They’re catchy and memorable…but not shortcuts or substitutes for real experience. You gain a real understanding of and appreciation for principles like these—SOLID included—by writing code day in and day out in the trenches.
SOLID Leads to Complex Code
Some people say that the SOLID principles lead to code that is harder to understand and maintain, which would undermine the whole purpose of using them. Take a look at what Brian Geihsler says in his piece titled “Why I Don’t Teach SOLID“:
If I could use one word to represent these problems, it would be unintelligible. Developers (myself included) applying SOLID frequently produce codebases that are unintelligible. These SOLID codebases have low coupling. And they are testable. But they are unintelligible. And often not as adaptable as the developers had hoped.
Is this true? Is SOLID code more complex? I think there might be some truth to this claim. For instance, if you follow both SRP and ISP, together with general advice from Martin’s Clean Code book (e.g., make your functions very small), you’ll probably end up writing lots of very granular interfaces and small classes.
Understanding a single feature could require you to navigate and read several files. Is this a problem? The answer to the question (and probably all questions in software engineering) is “it depends.”
Take a look at this quote from Sandy Metz’s blog:
Relative to complex procedures, OO is easier to understand and change. Relative to simple procedures, OO can be as easy to change, but might well be harder to understand as a whole.
So, OO isn’t a slam-dunk, hands-down winner. It depends on the complexity of your problem and the longevity of your application.
Sandy Metz, “Breaking Up the Behemoth“
As is the case with pretty much everything in software engineering, there are trade-offs involved in adopting not only the SOLID principles, but also design patterns, clean architecture, and—in a broader sense—OOP in general.
At the end of the day, it’s up to each developer to identify the complexity threshold where things like OOP, architecture, Domain Driven Design, and SOLID starts to pay off.
SOLID Is Too Idealistic
From time to time, people denounce the SOLID principles as too idealistic or utopic to really work in the real world. Or, to use the words Joel Spolsky used on the infamous Stack Overflow podcast #38:
[…] extremely bureaucratic programming that came from the mind of somebody that has not written a lot of code, frankly.
This one is, frankly, the harder to contest. That’s not because I believe it to be true but because it’s too subjective. How could we measure how idealistic a body of ideas is? One could make the same kind of statements about TDD, pair programming, the agile methodologies, and the list goes on.
What do we objectively know? Well, Robert C. Martin was already a developer with decades’ worth of experience when he first published about these principles. He collected them from respected sources both in academia and the industry.
All of that doesn’t automatically make them right, of course, but at least it gives us some confidence that we shouldn’t be so quick to dismiss them as utopic nonsense.
SOLID Is a Marketing Gimmick
I’ll mention this final one just because I happen to find it quite amusing. Apparently, some people think that the whole SOLID thing is just an elaborate scheme to make money from unsuspecting developers.
People will say, for instance, that the Liskov substitution principle is completely useless and that Martin added it just so the acronym “SOLID” could be complete. Of course that doesn’t make any sense since, as we already mentioned, the acronym itself came after Martin started teaching the principles, and it was an idea from Michael Feathers.
Book authors want to sell books? Whoa, that’s a surprise! Next you’re going to say that recording artists want to sell tickets!
Yes, SOLID is catchy. Who doesn’t want to write solid code, after all? And that’s exactly why Robert C. Martin started using it! It’s memorable. That’s the same reason we use rhymes in songs and poems and all sorts of acronyms and mnemonics. To attribute an obscure meaning to it is just silly.
SOLID Principles != Rules
From “DIP requires dependency on DI frameworks” (dependency injection guru Mark Seemann begs to differ) to “LSP requires implementation inheritance” (no it doesn’t), there are many other criticisms of the SOLID principles that could be covered in this post.
On a different note, it’s telling that so many accuse the SOLID principles of being vague, but they offer even vaguer alternatives, such “write simple code” or “write low coupled code.”
It’s important to keep in mind that the SOLID principles are just that: principles. They’re not some kind of magic recipe that will somehow lead you to the promised land of milk, honey, and perfect software. You still have to study, to practice, and in a broader sense, to think for yourself. Not doing so is to take part in cargo cult programming, which we already know is bad.
This is a wonderful article. SOLID principles are just that, principles, not rules. Do they sometimes introduce a little bit of complexity? Yes, but not nearly as much as not following them.
People forget that these result from years of understanding why software became too complex and difficult to maintain.
If you look at software that’s out of control and you try to figure out what’s wrong with it and how it got that way, you can usually trace it back to one of those principles (along with preferring composition and inheritance and a few others.)
Hi, Scott! Thanks for your comment. I really like the way you put it: the principles come with a cost, no doubt about it. But it is definitely lower than the cost of not following them in the first place and allowing your application to descend into chaos.
I find your interpretations very compelling, but I’ve found so diverse interpretations of these principles on the web that I can say they are close to useless. The idea of a software design principle IMHO, is to provide a base framework for developers to discuss software development best practices. But when the advocators can’t even agree on what their principles mean, it’s time to look for alternatives.
I also have found that people trying to follow these principles create extremely over-modularized architectures mostly because they decompose simple implementations into even smaller modules, disperse over the project which makes it close to imposible to discern the purpose of these micro-modules in the context of the whole project.
I would agree with Joel on this. Things like TDD are good for old codebases where things don’t change very much or you have this amazing client that knows exactly what they want and things aren’t going to change at all and the architect has sat down with the client and defined every class, every method, its parameters, what it returns and the programmers just write unit tests around that, then they write the code…..wait….let’s just stop right there – I’ve never had any client that knows know what they really want. In 20 years of coding for employers and working for myself as a contractor.. not once. It’s not just me – that is the real world. Writing a bunch of unit tests before you’ve even written code is a waste of time.
Like Joel mentioned, you have all these unit tests, then things change one day. So now you have to go edit all the unit tests to compensate for the changes.. THEN change your code so that the unit tests pass… then two days later the client changes their mind again – or they want to add a new feature and that new feature suddenly impacts a big chunk of code, including any UI layouts… so now you have to go change the unit tests AGAIN… and so on and so on. how many man hours did you spend writing unit tests that really really don’t matter?
The same setup could be used against SOLID. There’s just too many changes in a real world environment where features are adding, removed, edited to an ever evolving piece of software. You really have to take each project as a separate project and trying to cram an ideal into every project is going to hamper productivity and creativity in the end.