Welcome to this first article in which I will show the importance of applying the Single Responsibility Principle when implementing the code of our application.
There are many advantages to applying this principle: code comprehension, component reusability, increased development speed, ease of testability, etc. All of them affect the development team in the way they work and, indirectly, affect the end-user in the way they receive new functionalities.
What Single Responsibility means
The Single Responsibility Principle is framed within the set of SOLID principles, a set of rules to guide our code in what it's called Clean Code. In this case, each letter represents a different principle:
- S for Single Responsibility Principle,
- O for Open/Closed Principle,
- L for Liskov Substitution Principle,
- I for Interface Segregation Principle,
- D for Dependency Inversion Principle
Different programmers tried to describe this principle in the past, the one by Robert C. Martin is the standard as de facto:
A module should have one, and only one, reason to change.
The statement is quite simple, although, according to the author, it is the principle least understood. At first sight, the sentence is clear and there seems to be nothing unusual. However, the module
word could have different meanings for each person and lead to confusion.
We understand module
as a set of files or components that involves a use case or functionality in our application just for one actor. I have to say that this definition is no as specific as I would like to be because it relies on the programmer's experience to decide on what implies an actor and a use case.
I don't have any other better definition; however, I try to solve it following this principle in functions and objects. At the end of this article, you will be able to change your way of coding based on what I'll show you.
Coding for learning
Most of the time, seeing an example is easier to understand than reading a load of lines in an article. This example shows how the code is changed to follow what the Single Responsibility Principle says. We will start with a simple example of code that works fine and then refactor it without affecting the functionality.
In this case, I want to show the sequence of Natural numbers (starting from 0 to Infinity) waiting one second between each printing. It seems so simple to code. The most complex task here is to wait for 1 second each time a new number appears to the user. We will use the setInterval
method to accomplish that.
var number = 0;
var id = setInterval(function() {
console.log(number);
number++;
}, 1000);
As you can see, this is very simple. If you run this code on your browser or your Node.js console, you will see one line per number.
However, this code is doing several things at the same time:
- Sets 1000 ms for each execution,
- Implements the code to be executed in each execution
In resume, too many things for this little example. If you are doing pair programming and your mate doesn't know the use case, it will be difficult for her to understand what this code intends.
Refactoring the code
Now, we need to separate responsibilities. For the first of the things I've listed before, we define a Timer
entity that will be in charge of executing some functionality every second of a frequency passed. The code for the Timer
entity could be like the following:
function Timer(frequency) {
let intervalId = null;
return {
start: function(job) {
intervalId = setInterval(job, frequency * 1000);
},
stop: function() {
if(null !== intervalId) {
clearInterval(intervalId);
}
}
};
}
As you can see, I'm defining a way to start
the timer and another to stop
it. A timer is just a tool for our system with no other concern than run tasks with a frequency.
For the second thing I've listed, we define a Job
entity that will be in charge of executing a task. In our case, we have to define one job for printing the Natural numbers sequence:
function PrintNaturalNumbersJob() {
let number = 0;
return {
next: function() {
console.log(number);
number++;
}
};
}
Simple code where we are taking care of the number shown before to print the next one. Next
method prints the current number and increments the same variable for the next time.
As we are implementing an application, we also need an App
entity defined in our code. It will combine both previous entities in the following code:
function App() {
let frequency = 1; // In seconds
let timer = new Timer(frequency);
let job = new PrintNaturalNumbersJob();
timer.start(job.next);
}
This code is easy to read, so to maintain. Your mate who needs to validate that your code is compliant with the acceptance criteria of the use case will identify all that she needs to verify. The frequency of execution sets to 1 second in the first line of the app, and the task to be executed will print the sequence of Natural numbers. Now, your mate can reach the code of each entity and check if it is doing the right things, or, in a better way, its responsibility.
Wrapping up
This article shows how to improve our code and its maintainability by applying the Single Responsibility Principle.
This is something complex to apply at the beginning if you are not used to it but makes you grow up on how to design the components of your app. Once you have some experience you can apply this principle to your app modules.
What do you think about the exercise made in this article?
Hope this can be useful to you or just have fun reading it.
Top comments (0)