The Inextricable Link between High Cohesion & Low Coupling

Gary Blair
7 min readDec 12, 2023
Chain Connection by Michaela on Pixabay

“programs that were the easiest to implement and change were those composed of simple, independent modules” W. P. Stevens, G. J. Myers, and L. L. Constantine, 1974

If you are in the business of software development, then one thing is certain. You have been told too many times to remember that always, always make your software have high cohesion and low coupling.

I can recall it at school. Firstly an introduction to the terms. Then the clarification that one should be high and the other should be low. But never the other way around. That was bad.

But then something else. Something curious. As it was delivered more in faith than with a clear explanation or understanding of why.

If you achieve one then you tend to get the other.

Why is that then?

If we look at the origins of these words, cohesion is “sticking together” and coupling is “joining together”. This implies a subtle distinction between the two. But why then if they are similar does high cohesion lead to low coupling?

Referring to the seminal 1974 paper that introduced the terms to software development, cohesion is described as relationships within a module and coupling is relationships between modules. This confirms they are both about coupling. Also, it clarifies the difference in meaning.

But to understand more deeply the impact cohesion has on coupling, we shall look at two different perspectives of how we think of cohesion: relatedness and unity of purpose

Relatedness

When we use cohesion, we organise related code and concepts together. This helps us to locate things.

A bit like in your kitchen having a cutlery drawer with all the knives, forks and spoons. We can orient quickly.

This organisation can also be applied at different levels of abstraction. We can have knives and forks in separate compartments of the cutlery drawer. But in the kitchen, we will have many other drawers and cupboards. Organised into pots and pans and cups and plates. Then within the house, we have different rooms. And in the bedroom for example we can have another drawer full of socks.

Software is the same – from methods to classes to services or modules.

Of course, there are many ways to organise. However, when it comes to cohesion, one in particular is agreed to give the greatest benefit for coupling.

Unity of Purpose

When describing cohesion, Stevens, Myers and Constantine ordered types of cohesion from worst to best:

Coincidental – no cohesion at all. A disparate collection of unrelated code. Like a utility class or a miscellaneous module.

Logical – related to a logical concept such as a report module that does a variety of reports for different needs.

Temporal – related to things that happen at the same time such as an initialisation function.

Procedural – related by sequence such as a main function

Communicational – related by the input or output data they operate on. Such as storing, retrieving and printing a record.

Sequential – related by sequence such as functions to read, process and write a record.

Functional – this is the ideal. Everything contributes to a single task. Serving a single goal or purpose. Quoting again from the paper:

“if the elements of the module all contribute to accomplishing a single goal, then it is probably functionally bound”

Now that we understand how to maximise cohesion, let's explore how that translates to reduced coupling.

The Micro Perspective: Dependency Utilisation Cost

When we are strongly dependent on something, it seems obvious that we should improve the efficiency to utilise it.

For example in Morse code, it is no coincidence that an E is a single dot. Whilst a Z is dash-dash-dot-dot.

Or in your kitchen, your favourite coffee or tea cup is at the front of the shelf in a cupboard at head height, rather than buried at the back of another cupboard up high and just out of reach. And conversely, in that inaccessible cupboard, you will keep stuff that you very rarely use.

Or take our mobile phone. Its utility is not just how many things it can do, but its continuous accessibility as it is mobile with us in our pockets. If we keep it at home it is not nearly as useful.

If you are changing the software, and you have two pieces of code with a strong dependency, when it is cohesive both might be in the same class. The entirety of their relationship is right there in front of you. Easy to understand. You are free to change it. Re-run the unit tests. And you are more or less ready to commit the code.

On the other hand, if the dependency is in another class. A separate git repo. In another service. Across a process boundary. Maintained by another team. Suddenly the friction to change is infinitely higher. Not just through code coupling. You are now coupled with someone else’s priorities that are unlikely to be the same as yours. You are coupled to network calls and their lack of reliability.

All of these examples have different types of costs. In the case of the cup, it is accessibility. In the case of the phone, it is proximity. In the case of the code, it is coupling. But more generally they all relate to the amount of work or effort required to utilise a dependency.

The Macro Perspective: Network Optimisation

In Lean manufacturing, one of the seven types of inefficiency is transportation. An example would be when we constantly move materials between two stages of the manufacturing process that are at opposite ends of the factory.

Instead value stream mapping would be done to visualise the process through which materials move, and then reorganise the layout of the factory so that materials move through a set of adjacently located stages (a type of sequential cohesion perhaps?). This is optimising dependency utilisation costs at a system scale.

Other systems or networks also exhibit patterns of optimisation.

Take a large company with thousands of employees. The CEO is not going to have a direct relationship with every employee. Tell them what to do. See how they are doing. So a hierarchy is grown. Now the cost of sharing knowledge and aligning business goals between the CEO and all of their employees is much cheaper.

Or if I want to fly from Inverness to Las Vegas I am not going to fly direct. Imagine the infrastructure costs if, at every airport, you could fly to any other airport in the world. So instead we utilise hub airports that have better infrastructure. And smaller regional airports connect via these.

These patterns have to emerge otherwise the network could simply not function. There must be a certain economy in the connections concerning the intended purpose.

In software engineering our purpose is to continuously change the software to meet customers' needs.

And the pattern that enables that the most is functional cohesion. As we add each new customer need, there is a tendency towards new interactions which can be framed as a new task.

Therefore when we abstract and decompose functionally, we significantly increase the probability of localised side-effect-free changes. We encapsulate changes. Enable modularity. When applied systematically we drastically reduce the overall friction of change. Software maintenance would simply be unsustainable without it.

Extending High Cohesion

In the original 1974 paper, the power of cohesion was summarised as:

“Simplicity can be enhanced by dividing the system into separate pieces in such a way that pieces can be considered, implemented, fixed, and changed with minimal consideration or effect on the other pieces of the system.” W. P. Stevens, G. J. Myers, and L. L. Constantine, 1974

Nearly fifty years later it is no less relevant.

We might call it other things. Such as the Single Responsibility Principle. Where Uncle Bob tells us that classes that change together belong together.

And we might have extended the idea to deal with the increased complexity of products in the modern world. Where the challenge is no longer just a network of modules that must be changed. But a network of code bases. Maintained by a network of teams. Running a network of databases.

We harness cohesion through layers of abstraction such as in Domain-Driven Design.

Where we localise control with a bounded context. Aligning a team with a perspective of a business role or speciality. Decoupling the team from the decisions of others. Giving them autonomy to continuously deliver value.

Localising persistence with the aggregate. Decoupling a long-lived business concern with all the other complexity of the database. Allowing direct ownership and data consistency of persistence concerns.

And localising change through individual user interaction. Using CQRS and approaches such as vertical slice architecture to isolate all of the logic into a single task and file. The embodiment of functional cohesion.

Summary

The link between cohesion and coupling is not immediately obvious. The tendency is to think of coupling as how things in reality are dependent on each other. Cohesion is more abstract. More cognitive. How we organise things to find and understand them better.

But when we understand what effective cohesion is, and how we should organise, it starts to become clearer. For that is functional cohesion. We organise for tasks to belong together, purposeful things, that meet particular customer needs. The coupling within code to achieve an overall function is localised.

Software development within a business is about continual change. Oriented to meet new customer needs. So as we change for each new need, and we are organised functionally voila! We are likely to encounter localised change and impact. Avoiding coupling across class boundaries. Module boundaries. Process boundaries. Source code boundaries. Persistence boundaries. Team boundaries. Most importantly, from the perspective of the most pervasive side effect of these dependencies, an overall reduced friction to change.

--

--

Gary Blair

Curious about all things in software development, building of teams and better organisational design