Have you ever found yourself pondering endlessly about whether a piece of code follow this or that SOLID principle? It's as if following those principles is more important than the problem itself.
A hypothesis is that it has to do with the word "principle". If SOLID are truly principles, we must always follow them. But should we?
To address this question, let's first revisit SOLID, then study some definitions of "principle", and finally evaluate if SOLID aligns with any of those definitions.
Revisiting SOLID
S.O.L.I.D. is a set of design so-called principles coined as an acronym by Robert "Uncle Bob" Martin, with each letter representing a key aspect of software design:
- S for the Single Responsibility Principle: a particular view of cohesion that organizes code around potential changes.
- O for the Open-Closed Principle: a way of promoting extensibility through abstractions.
- L for the Liskov Substitution Principle: a strong view of sub-typing.
- I for the Interface Segregation Principle: a technique for minimizing the "surface area" of non-cohesive code.
- D for the Dependency Inversion Principle: an approach that decouples policies and business rules from implementation details.
These ideas are quite useful in Object-Oriented code and likely to be applied to other paradigms. But are they truly principles?
Fun fact: the concepts above were first published in book format in Agile Principles, Patterns, and Practices, released in 2002. But the acronym SOLID itself never appeared in this original edition nor in the 2006 C# edition. The order in the table of contents for both editions would form S.O.L.D.I.!
Defining Principles
What are principles? Let's consult the Merriam-Webster dictionary:
- a comprehensive and fundamental law, doctrine, or assumption
- a rule or code of conduct
- the laws or facts of nature underlying the working of an artificial device
- a primary source, origin
- an underlying faculty or endowment
- an ingredient (such as a chemical) that exhibits or imparts a characteristic quality
- a divine principle, GOD (when capitalized)
Are SOLID laws or rules? It doesn't seem to be the case. Maybe they're about natural facts or origin? Unlikely.
Examining Interface Segregation
Now that we've looked at what principles really mean, let's examine the Interface Segregation Principle (ISP) a bit closer. It states that "Clients should not be forced to depend on methods that they do not use."
When we have a non-cohesive piece of code, its clients end up having access to unneeded behavior.
Think of a class with numerous unrelated methods like the following:
public class Employee {
public BigDecimal calculatePayment() {
//...
}
public BigDecimal calculateTaxes() {
//...
}
public BigDecimal calculateOvertime(List<Hour> hours) {
//...
}
public void saveToDB() {
//...
}
public static Employee retrieveById(Long id) {
//...
}
public String toXML() {
//...
}
public static Employee fromXML(String xml) {
//...
}
}
The Employee
class above deals with:
- Payment, taxes and overtime.
- Saving and retrieving objects from a database.
- Converting to/from XML.
Most of the time, code using this class only needs some of its methods. For example:
- Finance-related code might use
calculatePayment
,calculateTaxes
, andcalculateOvertime
. - Persistence tasks would use
saveToDB
andretrieveById
- Integration with other systems might call
toXML
andfromXML
.
This situation goes against the ISP. Clients of the problematic class could mix up different usages, spreading lack of cohesion throughout the codebase.
A solution would be to create separate interfaces for each group of clients, eliminating dependency on unused behavior. Hence, we would segregate behavior to narrowly focused interfaces such as:
public interface EmployeeFinance {
BigDecimal calculatePayment();
BigDecimal calculateTaxes();
BigDecimal calculateOvertime(List<Hour> hours);
}
public interface EmployeePersistence {
void saveToDB();
static Employee retrieveById(Long id);
}
public interface EmployeeXmlSerialization {
String toXML();
static Employee fromXML(String xml);
}
Our Employee
class would implement the finer-grained interfaces above:
public class Employee implements EmployeeFinance,
EmployeePersistence, EmployeeXmlSerialization {
// rest of the code...
}
Now, clients of Employee
could use it behind a specific mask like EmployeeFinance
for finance-related code and so on, without even knowing about other behaviors.
Identifying the root cause
The underlying problem here lies in lack of cohesion: a class is doing too much. And this means it's a violation of the Single Responsibility Principle (SRP), as Employee
has at least three reasons to be changed, related to finance, persistence and system integration. We should refactor this class to be more cohesive, having less reasons to change.
Actually, this is almost the exact example arguing for SRP in Robert Martin's book UML for Java Programmers.
If breaking apart a troublesome class isn't an option, we can apply Interface Segregation.
Therefore, Interface Segregation is not much a principle as it's a technique or a pattern: a solution in the specific context of not being able to refactor non-cohesive code.
Exploring the Author's Perspective
So, if the ideas in SOLID aren't truly principles, what do they represent?
Let's turn to their creator, Robert Martin, who, in the 2002 book Agile PPP, described them as:
"These principles are the hard-won product of decades of experience in software engineering.
They are not the product of a single mind but represent the integration of the thoughts and writings of a large number of software developers and researchers."
Hence, SOLID emerges more as a collection of pragmatic advice from seasoned software engineers rather than immutable laws or rules.
In his 2009 blog post Getting a SOLID start, Martin clarified his use of the term "principle":
"The SOLID principles are not rules.
They are not laws.
They are not perfect truths.
They are statements on the order of 'An apple a day keeps the doctor away.'
This is a good principle, it is good advice, but itโs not a pure truth, nor is it a rule."
Martin further suggests that SOLID principles serve as mental frameworks for addressing common software development challenges:
"The principles are mental cubby-holes.
They give a name to a concept so that you can talk and reason about that concept. [...]
These principles are heuristics.
They are common-sense solutions to common problems. They are common-sense disciplines that can help you stay out of trouble.
But like any heuristic, they are empirical in nature. They have been observed to work in many cases; but there is no proof that they always work, nor any proof that they should always be followed."
Characterizing SOLID as names for common solutions to recurrent problems resembles the definition of design patterns. It offers guidance within a specific context.
In his 2003 book UML for Java Programmers, Martin argues that we shouldn't always apply SOLID and shows the effects of doing so:
"It is not wise to try to make all systems conform to all principles all the time, every time.
Youโll spend an eternity trying to imagine all the different environments to apply to the [Open/Closed Principle], or all the different sources of change to apply to the [Single Responsibility Principle].
Youโll cook up dozens or hundreds of little interfaces for the [Interface Segregation Principle], and create lots of worthless abstractions for the [Dependency Inversion Principle]."
So, are SOLID principles really principles? No. They're more like guidelines.
By viewing SOLID as guidelines coming from pragmatic advice, we can avoid needless complexity in our code and make more effective design decisions considering the unique contexts of our projects.
Top comments (2)
Great article @alexandreaquiles ! I've found it very clear and interesting ! ๐
The conclusion of your article reminded me fo this gif, which could be a pretty neat summary ๐
That's a good one! haha