SOLID principles are guides that, if applied in your day to day, can help you generate quality software. In this article, we tackle each principle and their benefits.
In every software project, there are types of tasks that are called technical debt. Many of them are caused by bad software design and require big and expensive refactors, and there is hardly ever time to tackle them.
Luckily, we can take some basic guidelines into account that identify bad software design, since the art of programming also needs guidelines to produce good designs.
Although there are several principles and design patterns that help us write good code, there are 5 that are very related to each other, and they are considered basic for the object-oriented programming paradigm.
Let’s take a look at the 5 most known principles that will lead us to design and generate quality code.
What are the SOLID principles?
The SOLID term was created by Michael Feathers as an acronym to represent the 5 basic principles of programming — principles that when used together, allow us to design and develop a scalable and maintainable system. These principles were initially invented by other experts, but Feathers gave them a name, and they have revolutionized the world of software in recent years.
Each letter represents a principle:
- S: Single Responsibility Principle (SRP) by Robert C. Martin
- O: Open Closed Principle (OCP) by Bertrand Meyer
- L: Liskov Substitution Principle (LSP) by Barbara Liskov
- I: Interface Segregation Principle (ISP) by Robert C. Martin
- D: Dependency Inversion Principle (DIP) by Robert C. Martin
Maybe you have already heard about them or know some of them, but it may be difficult for you to understand or even apply them day to day. That is why we are going to explain each of them in a simple and practical way.
Single Responsibility Principle
“A class should have one, and only one, reason to change.”
“Gather together the things that change for the same reasons. Separate those things that change for different reasons.”Robert C. Martin
This principle aims to avoid having classes with several logic responsibilities that are too complex to understand, maintain, or scale. Instead, it is better to have a class that handles one single thing, that is, it has a single logic responsibility.
To put this into practice, there are situations where one class could be divided into several:
- There is more than one layer of the architecture involved in the same class.
- There are a large number of public methods, imports, or lines of code in general.
- We find a trend of fields being used in the different methods. If we can identify different groups of variables, perhaps they and the methods that use them should belong to different classes.
- We have a hard time testing the class.
- Every time you write new functionality, that class is affected.
Open Closed Principle
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”Bertrand Meyer
This principle suggests that when including new functionalities, the ideal way would be to add or extend functions but not modify them so much. If we have to modify existing entities, it could indicate problems in the design of the solution. In other words, we should be able to extend a behavior without having to modify it.
The easiest way to see if we are violating this principle is to identify which classes we modify more often and then think if there is any way to avoid those modifications.
Liskov Substitution Principle
“Let phi(x) be a property provable about objects x of type T. Then phi(y) should be true for objects y of type S where S is a subtype of T.”Barbara Liskov and Jeannette Wing
This principle states that a class should be able to be replaced by any of its subclasses without altering the proper functioning of the program. For this, it is important to correctly use inheritance and polymorphism so that you adequately represent each object and its relationships.
One way to notice if we are violating this principle is to detect methods of the base class that do not apply or are not needed in the subclass. In these cases, the subclass overrides them by leaving them empty or by returning an exception. The code that calls that method must now have to handle those cases. This forces us to add conditions in every place, thereby making the code too difficult to maintain and extend.
Interface Segregation Principle
“Clients should not be forced to depend upon interfaces that they do not use.”Robert C. Martin
This principle recommends having many specific client interfaces rather than one general purpose interface.
When defining an interface, it is important to ensure that all classes that implement it are able to fulfill all specified methods. In other words, all methods are applicable to each class. Otherwise, it would clearly indicate that it is necessary to divide it into smaller interfaces.
The easy way to identify a violation of this principle is similar to the previous one.
If we find methods that do not apply to some cases and force us to leave them empty or throw some kind of error, then we should think about segregating said interface.
Dependency Inversion Principle
“A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.”Robert C. Martin
This principle states that it is better to depend on abstractions and not implementations, which allows code to be tested and maintained.
A violation of this principle would be to instantiate a complex class or module. Instead, we must use a dependency injection that takes care of the instantiation and provides them to the correct modules. This design pattern allows the modules to be decoupled.
The last words
At this point, you may have realized that these principles are very related to each other. It is probable that if we comply with one of them, we also comply with the others. However, we should not be obsessed with them, but have them in mind while designing new solutions could help avoid generating technical debt tasks.
These principles are solutions based on real experiences, which follow good practices and design patterns to create flexible, scalable, and decoupled software. A code that respects these principles can be reused and extended in an agile way and is easy to understand and maintain. It will be simpler to generate efficient, robust, stable, and well performing code.
In short, the SOLID principles are guides that will help you avoid bad designs and all the problems that it brings. These principles will allow you to generate quality software with all the benefits that we already know.
Comments? Contact us for more information. We’ll quickly get back to you with the information you need.