A few months ago, it was fashionable to complain about the 10x developer myth. I agree that such people don’t exist, but, in my opinion, proper software architecture can transform every developer into a highly productive person.
When the code is structured in the right way, everyone can deliver features very fast, programmers don’t produce many bugs, and the ones they produce are isolated and don’t break multiple parts of the system.
We can distinguish between good and poor software architecture by comparing two crucial characteristics of code: cohesion and coupling.
In this article, I am going to discuss high cohesion. I will also show you some heuristics you can use to determine whether you reached the optimal cohesion of your software.
What is cohesion?
The Cambridge online English dictionary defines “cohesion” as “(of objects) the state of sticking together, or (of people) being in close agreement and working well together.”
How does it apply to software?
A highly cohesive software is code that serves only one purpose and minimalizes noise. That rule applies to pieces of code of any size (packages, modules, files, classes, functions, etc.).
Such a design allows us to construct a program as a sum of smaller programs that implement only one feature (business, technical, or whatever). Moreover, a highly cohesive code does not contain anything that is not related to the functionality it implements.
How to write a highly cohesive code?
Here is a list of things you should avoid if you don’t want to destroy your carefully crafted software architecture.
Let’s look at the definition again. Does it mean that we can have a util module in our code? Anything that is called “common,” “shared,” “util,” or “global” is, by definition, not highly cohesive.
I have an extreme opinion about util modules. I think that you must never create them. It is way better to create a specialized module that contains only one method than to spoil your code with a “util.” I recommend thinking of “util” as a massive pile of garbage.
Everyone who cooks has that one “util” drawer in the kitchen. The one where you keep everything that did not fit anywhere else. Is it useful? Can you quickly find what you need? Sure, we have to keep that stuff somewhere, and there is no space in the kitchen to keep it separately. Fortunately, that is not a problem in code.
Organizing code by technical layers
Can we create a module that contains all controller or repository classes? At first glance, such things seem to support a single technical aspect (feature), so everything is fine.
However, when you look deeper into the code, the controllers quickly start supporting multiple business features.
It gets even worse when we call something a Service. How often do we see an UserService that has everything? It registers new users, changes passwords, reset passwords, updates the user’s profile, sends notifications to the user, sometimes it even makes reports.
What is the biggest problem? We quickly end up with a large file with dozens of methods that seem to be related to each other only because all of them get the userId as one of their parameters.
Where is the code that changes passwords? It is somewhere in the UserService. Why do we have the newsletter provider as one of the dependencies? There is one method that updates the newsletter subscription status.
Do you have a status field in your objects that switches between multiple branches in a long chain of if/else statements? If yes, I am going to risk stating that you are trying to support numerous features by the same piece of code.
I am not saying that using statuses is wrong. What I am saying is that replacing a rich domain model with a simple “status” field leads to unmaintainable code.
Lack of language skills
Speaking of the rich domain model, can you create it if you don’t know the language used by the domain experts? If you are implementing features for an online shop, and the only English word you know is “order,” you will have to model everything using statuses.
Things like “sales proposal,” “offer,” “withdrawn offer,” “processed payment,” “delivered order” will not magically appear in the codebase if the developers don’t know that such words exist.
Try not to overkill it
There is a risk that if you are supposed to write a CRUD application, you may try to build a complex architecture just because you think you should do it or because you are tired of writing boring software. However, if you remember that cohesion is all about minimizing the noise, it should be evident that the goal is to create a minimal amount of code that does the job.