You may have heard the advice that maintainable software is build using highly cohesive and loosely coupled building blocks. This is a statement that's easy to remember, but difficult to understand. This article will clarify cohesion and coupling.
According to the Merriam-Webster dictionary, cohesion is the act or state of sticking together tightly. We will apply this definition in three domains: objects in real life, text and software.
We start with the most concrete domain of the three: solid objects. We tend to know that a stone is generally more cohesive than a snowball. Snow can melt, fall apart and mix with other substances more easily than a stone.
Text is a bit more abstract. Instead of measuring how letters stick together to form words, cohesion measures how words form sentences, and how sentences form a meaningful text. Elements of cohesion ensure that the text you read transmits one and only one idea at a time, without distractions.
In some sense, cohesion in text is quite similar to cohesion in software. While text is built of chapters and paragraphs, software is built using packages containing classes, and classes containing properties and methods. Although cohesion can be defined on multiple levels, we will restrict ourselves to the most common one: cohesion on class level. A class is highly cohesive if its properties and methods contribute towards solving the one single responsibility of the class. As soon as you dilute the class with a loosely related operation, the class becomes less cohesive.
For instance, including the full definition of a file uploader or a timepicker in a form presenter class results in lack of cohesion. Instead of solving the single responsibility of presenting a form of self-contained components, your class also deals with the implementation of the components themselves.
Why is lack of cohesion bad?
A class focusing on one and only thing is easier to read, unit test and maintain. If you have a lot of responsibilities mixed together, you lose track of what your class is supposed to do. Any bug fixes or feature updates would make it hard for you to find which of your classes are affected.
For instance, suppose that your application contains a mixin consisting of utility functions and widgets for forms. Whenever your team wants to abstract anything, they put a utility method in the form mixin. Therefore, you will soon find that the mixin is responsible for validation, overriding default radio button styles, file uploader components, timepickers, currency formatting and so on. In the end, you find yourself maintaining a single utility mixin consisting of several thousand lines of code. Suppose there is a buggy method used by four distinct utilities. Fixing the bug implies that you have to test all 4 of your utilities in all places where they occur. Welcome to maintenance hell.
Coupling describes the relationship between components. Tight coupling between two components means that they depend on each other more than loosely coupled components. Making a change in one component forces a change on the other.
Software developers like loose coupling, because a change mostly stays self-contained inside the component. In case of tight coupling, you may end up changing a lot of components as a change may force changes in its dependencies recursively.
Similarly to cohesion, coupling also depends on granularity. It is very important to look at coupling on the same level as cohesion.
When two objects are too tightly coupled, decoupling techniques help. For instance, suppose that a charting software has two chart types: bar chart and candlestick charts. A control lets you switch from bar chart to candlestick charts. This control may be far away from the canvas of the chart. If the chart component has any knowledge of the chart switcher control component, it't time to decouple them. One way is to use an Event Aggregator or Event Bus. The chart switcher would publish a
chart:candlestick event, while the chart drawer would subscribe to it. The chart drawer has no knowledge of who published the event and has no further knowledge of the chart control components either.
Cohesion and Coupling solve the same purpose
Cohesion and coupling solve the purpose of easier maintainability. Highly cohesive units make it possible that you know which unit to modify. Loose coupling makes it possible to limit the scope of the change to one and single unit.
Elements are coupled if a change in one forces a change in the other. An element's cohesion is a measure of whether its responsibilities form a meaningful unit.
Maintainable code consists of highly cohesive elements that are loosely coupled with each other.
Building blocks with lack of cohesion simply fall apart. If you have trouble naming a constructor without using the words
or, most likely you are trying to solve multiple loosely related responsibilities in the same place.
Tight coupling between building blocks is also discouraged. A change may result in consequences you are not prepared for.