Cohesion is one of those things that are often overlooked, especially in front end code. You can some pretty nasty habits and massive, spaghetti monster creatures created in code. While principles of object orientated programming are baked into the process of learning traditional back end technologies like Java and C#, the front end is usually split between different modes of thought that cohesion is often left on the sidewalk.
In my first ever experience of Angular, spaghetti code was so baked into the components that any change resulted in a cascading effect of breaking the code. It was easier to just create something new, tack it onto the side and call it a day. We’ve all done it at some point in our careers.
Since that experience, I’ve been a big advocate for structure and cohesion in code. If you don’t want to do major refactoring in the future (or tempt future developers into the dark arts in order to curse you with voodoo dolls), contemplation about code cohesion for your project before you begin coding is a necessity.
What is cohesion?
noun. the action or fact of forming a united whole. synonyms: unity, togetherness, solidarity, bond, connection, linkage, interrelatedness. — Dictionary.com
In computer science, code cohesion refers to how well a piece of code is crafted to fit together and how much inter-dependency is required for it to work effectively. Cohesion is often paired with the topic of coupling. This is because if cohesion is lacking in code, then coupling is probably very high.
Lack of cohesion often leads to spaghetti code in the long run. Sometimes it starts off as code smell — where things are technically right, everything works and seems alright, but quickly decays at a faster rate and its usefulness becomes obsolete quite quickly.
Cohesion determines what a class or function can do and how the developers have organized the functionality of the code. One could even say that cohesion is related to separation of concern.
But cohesion is more than just the organization of thought in code, it is also how related and focused that particular bit of code is. Low cohesion often arises when massive classes are created and bits of code are connected together with some crazy seemingly logical super glue, often resulting in it being difficult to read and maintain. You can’t reuse the piece anywhere and it is fragile in nature — that is, if you change one thing in the code, it can potentially and will most likely break something else like a falling tower of dominoes.
Low cohesion code is scary. It might bring job security for the person that wrote it because no one else understands what is going on, but it’s a horrible way to stay in a job.
The principle of single responsibility gone wrong
When we write code, especially when we’re young and naive to the ways of what beautiful code looks like, we often just try to get things working first.
The idea behind cohesion is that you organize your code in a way that reduces it down to a single responsibility. However, what we often do wrong is reduce it down to a single responsibility but then require a dependency injection for it to work. It doesn’t take long for it to have struts and strings attached to it, which in turn actually transforms your function or class into a mammoth of an accident.
If one of the struts or strings were to change for whatever reason, your class or function becomes defunct. If your dependency injection is used in multiple places, it will have a cascading and web-like effect on your application. While you can argue that each function and component has high cohesion because each function or class has a single responsibility — but just used multiple times in multiple places, which isn’t single responsibility at all.
Single responsibility goes both ways — that is, how it is used, consumed, uses and consumes others. A function or class should only be consumed or used once for a singular purpose and not in multiple places. We often confuse the physical manifestation of the component on the screen with the relationship it has with other classes and functions with reusability. In the front end, we see it as separate and complete on its own. But behind the scenes, a multitude of things are required to make it work.
In reality, behind the scenes should be linear and easily traceable for it to qualify as being truly cohesive in construction.
Cohesion requires layers
Cohesion is like an onion — it requires layers to keep things separate and distinct. It’s not just about how small your functions are, it’s where they are in relation to each other.
I often construct a four-layer approach to a lot of the applications I build — rendering, logical, data and service layers. Depending on how complicated things are, I might create sub-layers within those layers.
Each layer is attached to another layer in a vertical fashion and there is only one connection. Horizontal connections are defined clearly and grouped together based on functionality. This allows for clear definitions of single responsibilities that are interconnected but also distinct at the same time.
Rendering is where the entire functionality of the component is reused in multiple places within the app. The logical and service layers, however, should not be shared across multiple components. This means that if you were to change something, the point of change will only affect one thing.
In Angular, this structure is often done through a physical separation of code in separate files. In React, the rendering and logical layers are often in the same file but data and service layer can be in a different space. However, this doesn’t make React less cohesive. The relationship between the rendering and logical layers is generally one to one, therefore if a change occurs, the impact is clearly visible and radius of change is identifiable.
Cohesion is one of those concepts that can be a bit hard to get your head around until you’ve actually experienced the sticky mess of spaghetti code. Or perhaps you’re in the process of looking for an answer or prevention of the possible sticky mess.
You can break your code down into small chunks but that’s not going to solve you the root cause of your problem. The real issue often manifests as code that is too interconnected under the guise of reusability.
From past experience, what needs to be done is someone needs to take that time to sit down and see how, what, why and where things are connected to each other. Only then can you start to untangle the threads, move things around and create new and distinct nodes of connections that are defined. It is important to keep the sphere of influence defined with clear boundaries — and make it obvious too for future developers.
While there is no safeguard that the person who is working on the code base after you will have the same ideas and understanding when it comes to defining boundaries, if you design your code in subsets that are connected but not connected at the same time, then chances are, whatever changes that future developer may make won’t hurt as much due to good design and high cohesion.