Pure Functions: High Cohesion, Low Coupling
Boost quality, maintainability and readability of your code
TL;DR Why would you bother to write pure functions? Do you really need to establish high cohesion and low coupling? Well, if you want a clean and high-quality code that has easy maintainability you must. Period.
For the people who sees any of those terms for the first time, it would be better to start with the meanings. Please keep in mind that I will try to explain the concepts from the aspect of Object-Oriented Programming (OOP). What do I mean by that? For instance, I will refer to classes, not modules.
In software engineering, a pure function is a function that has the following two properties:
1. It returns the same value for the same inputs (no variation)
2. Its evaluation has no side effects (It should not change any other variables that is outside of the scope of the function)
I will come back to why you need to write pure functions and how it makes unit tests perfect. By the way, it really seems a piece of cake at first glance, but believe me, it is a snaky one. I saw engineers who have many years of experience but still doesn’t seem grasped the importance of it.
In a nutshell, cohesion in software engineering is a measure of the strength of the relationship between the class’s methods and data themselves.
Cohesion is often contrasted with coupling, a different concept (we will come to that in a bit). High cohesion often correlates with loose coupling and vice versa.
So, low cohesion means that the class does a great variety of actions. In other words, it has different responsibilities, which conflicts with the separation of concerns in software engineering. It is not focused on what it should do. This is bad. Your class/function should have one specific responsibility. That will increase the cohesion.
On the contrary, high cohesion means that the class is focused on what it should be doing, i.e. only methods relating to the intention of the class.
In software engineering, coupling is the strength of the relationships between classes. In other words, it refers to how related or dependent two classes are towards each other.
Low coupling is often a sign of a well-structured software and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability.
As you can imagine if your classes are dependent on each other, maintainability will become harder. Because if you change anything in a class, you should check whether the dependent classes are still functioning correctly.
Pure functions and unit testing:
I really would like to deep dive into this part in a separate article. I may in the future, but for now, I will try to explain the benefits of the pure function and its positive effects on unit testing.
We can apply “loose coupling and high cohesion” to function level as well. So function units also must have one responsibility (highly cohesive) and independent on each other (loosely coupled).
As we described above, writing a pure function means that you give only one responsibility to the function. It takes a certain amount of inputs and it has an expected output. And most importantly it has no side effects.
I will try to give a clear example of how the function should look like:
As you can see it is really easy to unit test your function. Because you know exactly what does your function do. And btw I have to say that most of the time unit testing is mixed with integration testing. People think that their tests are unit tests, but in most cases, they are not. So, if you write pure functions, which means your function is not dependant on any other global parameter, unit testing will become super easy out of the box.
Say if certain circumstances are satisfied, you want to assign a boolean to a global variable. In a bad practice, you would do that assignment in a function that has also other responsibilities:
As you can see here, it is not unit-testable. You can not unit test someMethod. You can, of course, think that you can write a unit test for it. But it will end up being an integration test. Because you must test that isGreaterThanTen should be true when certain conditions apply.
Rather, in a good practice you would write a function like this:
As you can see we wrote a pure function which is called isLessThanTwo. And it is really really easy to unit test. It has no side effects. Purely does what it should do. Now you don’t have to worry about global isSingle variable. If you unit test isLessThanTwo, isSingle will be covered as well.
A side note: if you embrace Test-Driven-Development it will help you write pure functions.
As we saw in the examples, writing loosely coupled and highly cohesive code with pure functions, will boost your maintainability and code quality. Because now, you have maintainable, fully functional unit tests. If you need to change some stuff in your code, updating both your functions and unit tests will be extremely easy. Your code will be easy to read as well.
PS: I want to thank my colleagues who made me understand the importance of writing pure functions. They know themselves :)
I have spoken. This is the way 🤓