How to Design Software, That's Easy to Scale
Digital solutions undergo lots of changes within their life cycle. To facilitate them, developers should follow the five SOLID Principles for good software design.
by Rana Al Huniess and Hamza Rabah, developers at L-One
A successful software product constantly evolves. It gets improved, obtains new features, additional interfaces and more. However, if the software hasn’t been designed well from the beginning, it’s increasingly difficult to implement those changes.
This won’t happen, if the developers follow the five SOLID Principles for good software design introduced by the American software engineer and instructor Robert C. Martin in 2000.
Relying on those principles, you create programming structures that are easy to:
- Maintain & test
- Extend & reuse
Robert C. Martin
»Good software systems begin with clean code. On the one hand, if the bricks aren’t well made, the architecture of the building doesn’t matter much. On the other hand, you can make a substantial mess with well-made bricks. This is where the SOLID Principles come in. «
The acronym SOLID stands for:
- S - Single-Responsibility Principle
- O - Open-Closed Principle
- L – Liskov Substitution Principle
- I - Interface Segregation Principle
- D - Dependency Inversion Principle
We’re going to introduce you to each principle individually to show you, how SOLID can help to develop better software, that is easy to scale.
› Functions are self-contained modules made of code, that are designed to carry out a certain task. Normally this includes receiving and processing data and pass back a result
› Data structures are formats for retrieving, organizing, processing, and storing data. They arrange information for a particular purpose and facilitate that users can access the information and work with them.
The SOLID Principles determine, how functions and data structures are being sorted in classes, and how these classes should be connected to each other. Using the word “class” does not mean that these principles are only being applied in object-oriented software. In this case, the word “class” describes a certain combination or group of functions and data. The five principles should be applied to these groups.
1. The Single-Responsibility Principle (SRP)
Robert C. Martin defines this first principle in the following way:
Robert C. Martin
»A class should have only one reason to change.«
However, this principle is usually described and formulated in more than one way. Let’s address some of them:
“A module should be responsible for one, and only one, actor.”
“Gather together the things that change for the same reasons. Separate things that change for different reasons.”
Robert C. Martin states: “Of all the SOLID Principles, the Single Responsibility Principle (SRP) might be the least well understood. That’s likely, because the SRP has a particularly inappropriate name. It’s too easy for developers to hear the name and then assume that it means that every class should do just one thing.”
Unfortunately, that’s a mistake. However, this applies to functions: A function should do one, and only one thing.
Responsibilities act as primary axes for change
Martin defines a “responsibility” as a reason to change and concludes that a class/module should have only one reason to be changed. If you can think of more than one reason for changing a class, that class has more than one responsibility. This is sometimes difficult to see, as we are accustomed to think of responsibility in groups.
What is the problem of having a class/module with more than one responsibility? Why is it important to separate those responsibilities into multiple classes/modules?
The reason is, each responsibility acts as a primary axis for change. So when the software requirements change (and they will), these changes will be reflected as changes in the responsibilities. If these responsibilities apply to several classes/modules, the ladder will have more than one reason to change. The result: Implementing the changes will require a lot of effort.
Why is the Single-Responsibility Principle important? An example
The following use case illustrates the problems, that occur, when developers don’t follow the Single-Responsibility Principle.
Imagen that we have a “Person” Class, which is designed the way the picture below shows:
The picture indicates that the Person class has two responsibilities. The first responsibility is to provide report hours to the HR Department, the second responsibility is to provide report payroll functionality to the Accounting department. Both functionalities use the method “regularHours” that returns the regular hours for employees, including break time.
By putting the source code for these two responsibilities into a single “Person class”, the developers have coupled those two actors, the HR department and the Accounting department.
What negative effects could that have?
Imagen HR would ask the developer to change regular hours to not include break time anymore. Then the developer would change the method “regularHours” to exclude break time.
However, this would break the reportPayroll functionality. Accounting still assumes that the method “regularHours” returns the time including the break time. They make their calculations based on this assumption.
If the developer forgets to check which other methods use the “regularHours” method, this would lead to incorrect payroll data without anybody noticing.
This coupling prevents us from reusing the functionalities within the Person class. Furthermore, it can cause that the actions of the HR team affect something the Accounting team depends on and vice versa. In other words, there is more than one reason to change a person.
Make digital solutions easy to scale
The Single-Responsibility Principle is one of the simplest principles, but one of the most difficult to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities is much of what software design is really about.
Finally, if a function, a module, or a class has one and only one reason to change, it means that they conform to the Single-Responsibility Principle. This makes it easy to implement changes and scale the digital solution.
2. The Open-Closed Principle (OCP)
Robert C. Martin defines the second principle like this:
Robert C. Martin
»Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.«
When a single change in your application results in a cascade of changes to dependent modules – in other words, it forces massive changes to the software – then the design is fragile.
If the Open-Closed Principle is applied well, changes are achieved by adding new code, not by changing old code that is already working.
Gain flexibility, reusability, and maintainability
The goal of the Open-Closed Principle is, to make a system able to extend without causing a high impact change.
Applying this principle is what yields the greatest benefits claimed for object-oriented technology: flexibility, reusability, and maintainability.
Classes/modules that conform to the Open-Closed Principle have two primary attributes:
1) They are open for extension:
The module behavior can be extended. That means, if the requirements change, we can extend the module with new behaviors that satisfy those changes. That is to say, we are able to change what the module does.
2) They are closed for modification:
Adding or expanding the behaviors of these modules will not change their source code, meaning that the executable form of these modules will not be changed.
Use abstraction, but in the right way
Clearly, those two attributes are at odds, because the normal way to extend the behavior of a module is to change its source code.
So the question is: How can we change, what a module does without changing its source code?
The answer is, by utilizing abstraction. Through the process of abstraction, a programmer hides all irrelevant data about an object in order to reduce complexity and increase efficiency, which can be done by using different design patterns.
There are some design patterns that provide the ability to apply the Open-Closed Principle well, for example:
However, the developer is responsible for applying abstraction only to those parts of the solution that exhibit frequent change. Resisting premature abstraction is as important as abstraction itself.
You want to find out more?
Stay tuned! Soon we’re going to publish the second part of this article, which is going to explain the remaining three principles:
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle