Why Are We Still Talking about SOLID?
Funny story. During one of the last technical interviews I had, I was asked what SOLID was. After I answered that it was a philosophy in object-oriented programming for single use objects, encapsulation, interface usage, and dependency management, I was then asked to name each of the principles explicitly and define them. Although senior engineers from places like Google, Microsoft, etc. rage tweet about technical interviews that focus on semantics, memorization, and whiteboarding algorithms, it seems that technical interviewers are not paying attention (at least not back when this interview occurred).
Do you know what a laser is? Did you know that it's actually an acronym, and can you tell me what each letter stands for? If you don't know that, does that diminish your knowledge and experience of/about lasers? Which is more important: Understanding what a laser is and how it works, or understanding explicitly the definition of acronym components?
There's a difference between conceptually understanding the salient philosophy of SOLID and what it means for object-oriented programming versus memorizing the acronym and detailing the definitions. Now I'm not saying that it isn't important to know those things. What I'm saying is that it's a terrible interview question, and I feel bad for unsuspecting programmers who walk into those questions.
And that's just the problem with an interview question about SOLID. Let's talk about the problems with SOLID itself.
With databases, we often talk about normalization. The justification for this normalization is that we need to eliminate the duplication of data. Today, the focus is on normalization to maintain data integrity: Keeping two copies of the same data in two different places could lead to the data eventually (mistakenly) becoming different, putting integrity at risk.
The reality is that normalization and data de-duplication was primarily a way to save disk space in an era when disk space was expensive. From a technology perspective, this is no longer a problem, which is why the focus has drifted to data integrity. But there is a philosophical problem with data de-duplication for integrity purposes. Not all data represents the same thing in the given temporal or transactional event. Richard Campbell used a order address as an example. In a normalized database, you might have an address table and an order table where the order table references the address table for the mailing address of the order. You need to maintain the integrity of the order, so what do you if the address ever changes? This leads to an insert-only database process where when someone changes or deletes an address record, you maintain the historical data, so it's always available for reference. The alternative, of course, is to store historical records in a data warehouse (which will be denormalized to begin with) or you'll need to take a snapshot of the order, storing that as a single immutable, transactional record. In either case, normalization isn't your friend for the purpose at hand.
This is why some modern document databases talk about "eventual consistency," which is this idea that, yes, data is duplicated, but we have processes to ensure that they remain in-sync, and they will eventually align.
Now let's look at object-oriented programming. Many of the original principles of object-oriented programming (e.g., encapsulation) were specifically meant to create layers of abstraction that helped reduce code, but the primary goal was to reduce resource utilization because hardware and resources were expensive. That is not the case anymore, yet we're still holding onto principles designed for such maintenance in the name of what?
SOLID only tackles one part of software design: Resource utilization—specifically, the design (for maintenance) of objects within object-oriented programs.
SOLID is not effective at tackling application performance and application maintenance (as opposed to object maintenance). Although SOLID principles could certainly improve performance by cutting down on terrible code that isn't it's explicit goal; It's more a side effect that is only occasionally felt.
For application maintenance and readability, SOLID is a travesty. Outside of the single responsibility principle, do you even remember the rest of them? Probably not unless you looked them up after my opening paragraph. The "S" in SOLID is a great principle to keep in mind, but the rest can take its toll on code readability and maintenance. This is a direct result of the focus on dependencies that encourages additional layers of abstraction. These layers of abstraction—although they produce highly uncoupled and testable code—greatly reduce the readability needed for code maintenance, and rarely produce the expected flexibility promised.
The single responsibility principle might be a useful philosophy for creating smaller, uncoupled, testable components, but in practice, implementing code coverage requirements at a 75-85% threshold for builds to pass works just as well without having to keep a fundamental principle in mind.
SOLID can quickly become a code design nightmare where the next programmer coming up behind you can't decipher which piece of code is actually running, and where it's coming from.
I came across a great blog post by Brian Geihsler complaining about much of the same thing as I am above, and his solution is to propose the Dependency Elimination Principle.
Geihsler readily admits that his conceptualization of the Dependency Elimination Principle was inspired by Arlo Belshee's evangelism of eliminating mocks.
The Dependency Elimination Principle consists of the following edicts:
- Treat dependencies as code smells
- Concentrate on whole values
- Use testing as a litmus
SOLID creates unnecessary and over-engineered abstractions and dependencies, but we really ought to be striving to eliminate as many dependencies as possible.
Are we getting something from the dependency? If so, pass the thing we're getting, not the thing that gives it to us.
Are we sending something to the dependency? If so, consider a model that uses events + event binding instead of passing in an interface. Or use a DTO that represents the state of the dependency and respond to changes to it (e.g. MVVM).
Brian believes that most programs rely too much on dependencies because they don't have clear enough concepts defined as whole values. Whole values are object values containing meaning for the business domain. In fact, defining values as they relate to the business is one step closer to implementing a domain-driven development approach… or at least an approach where objects are defined based on the business domain and object values or properties represent the whole value of the business meaning. Functional programming does well in this area, as do programming languages that utilize the concept of duck-typing such as Ruby and TypeScript.
Lastly, when it comes to testing, the simpler you can make the tests, the better. If your unit tests are filled with integration components (a no-no to begin with), mocks, or require additional objects (themselves requiring testing) in order to complete the test, it becomes more difficult to ensure the test result matches the working conditions of the function, method, or class. The further we use abstraction to approach quality SOLID principles, the more our testing reaches additional levels of complexity and potentially introduces false passes.
With the Dependency Elimination Principle, SOLID becomes "SO" since the final three mostly deal with dependencies, which we're trying to eliminate. Additionally, the open-closed principle—meant to preach about extensibility—is mostly covered by domain-driven development principles, whole value construction, and duck-typing. This leaves us with the single responsibility principle, which, honestly, is something we all strive for even without knowledge of SOLID, and as mentioned earlier, is something that code coverage practices can easily help to enforce.
SOLID—like any philosophical principle—has its place, but it's important to understand why the principle was developed to begin with, and how the industry has evolved since then, instead of blinding following ideas of the past.