Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Understanding Software Component Cohesion

Recommended resources

In our previous lesson, we took a deep dive into coupling, the force that binds software modules together. We established that the goal isn't to eliminate dependencies but to manage them, favoring stable, explicit interfaces. Now, we turn our attention inward to look at cohesion, the logical counterpart to coupling. While coupling describes the relationships between modules, cohesion describes how well the elements within a single module belong together.

This lesson is dedicated to defining and evaluating cohesion. You'll learn to recognize the difference between a module that is a well-organized, purposeful unit and one that is merely a random collection of code. Mastering cohesion is fundamental to your goal of planning maintainable architectures, as achieving high cohesion is one of the most direct paths to creating healthy, loosely coupled systems.

What is Cohesion?

At its heart, cohesion is a measure of relatedness. A module—be it a class, a function, or a package—is highly cohesive if all its internal parts are working together towards a single, well-defined purpose. Think of a specialist's toolkit: every tool is there to perform a specific job, like watch repair. This is high cohesion. In contrast, a junk drawer filled with old batteries, random screws, and a single sock has very low cohesion.

To formalize this, let's start with a clear definition.

Understanding Cohesion, Coupling and Connascence in ...

This article provides a concise introduction to the concept.

Please read the two short paragraphs under the introduction that define Cohesion.

As the article notes, a highly cohesive module is predictable and its purpose is clear. This directly translates to maintainability. When a module has low cohesion, it presents several distinct problems for developers.

Understanding Cohesion, Coupling and Connascence in ...

Continuing with the same article, let's examine the negative consequences of poor cohesion.

Read the section Drawbacks of Low Cohesion to understand why this is a property we want to avoid.

The key insight, as highlighted in the article Cohesion and Coupling in Software with Examples, is the inverse relationship between these two concepts: increasing cohesion within your modules is a primary strategy for reducing coupling between them. When a module contains everything it needs to fulfill its single purpose, it has fewer reasons to depend on other modules.


A Hierarchy of Cohesion

Just as with coupling, cohesion isn't a binary property; it exists on a spectrum. Understanding the different levels of cohesion gives you a qualitative framework to evaluate the design of a class or function.

This diagram illustrates the generally accepted levels of cohesion, ordered from worst (Coincidental) to best (Functional).

Let's explore this hierarchy. The following reading provides excellent descriptions of each level.

Cohesion and Coupling in Software with Examples

This article gives clear definitions and examples for the different levels of cohesion.

Please read the section "The Different Levels of Cohesion". Pay close attention to the definition of each level, from Coincidental to Functional.

Let's summarize the key levels you just read about:

  1. Functional Cohesion (Best): This is the ideal. Every part of the module contributes to a single, well-defined function. A Python function like math.sqrt() is perfectly functionally cohesive.
  2. Sequential Cohesion: The elements of a module are chained together, where the output of one part becomes the input for the next. This is very common in your field of computer vision. An image processing pipeline that first loads an image, then applies a filter, then detects features exhibits sequential cohesion.
  3. Communicational Cohesion: Elements are grouped because they operate on the same data. For example, a class ImageMetadata that has methods to read_exif_data, update_gps_coordinates, and write_metadata_to_file would be communicationally cohesive as all methods operate on the same underlying data structure.
  4. Temporal Cohesion: The elements are related only because they happen at the same time. An initialization function that opens a database, loads a configuration file, and sets up logging is temporally cohesive. The tasks themselves are unrelated, but they all need to happen at application startup.
  5. Logical Cohesion: Elements are grouped into one module because they perform logically similar, but functionally distinct, operations. A common anti-pattern is a single function that does many different things selected by a flag or a mode parameter.
  6. Coincidental Cohesion (Worst): The elements have no discernible relationship other than being in the same file. This is the hallmark of the infamous utils.py or helpers.py file that becomes a dumping ground for unrelated functions.

Your goal as an architect is to push your components as high up this scale as possible, constantly striving for Functional and Sequential cohesion.


From Low to High Cohesion: A Practical Refactoring

Theory is essential, but seeing the principles in action is what solidifies understanding. We're now going to watch a detailed video that takes a Python class with very low cohesion and refactors it into a set of highly cohesive, well-structured classes. This process of moving behavior closer to the data it operates on is a core skill in software design.

First, let's see the initial problem.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

This video from the channel ArjanCodes will guide us through a practical refactoring example.

Watch the short segment from 06:35 to 06:46, where the presenter analyzes the register_vehicle method and correctly identifies its very low cohesion.

The register_vehicle method is doing far too much: generating IDs, calculating prices, computing taxes, and printing output. It exhibits a mix of logical and coincidental cohesion and is a prime candidate for refactoring.

Now, let's watch the full refactoring process. The key strategy is to identify the different logical data groups and create new, more cohesive classes around them, moving the relevant behavior into those new classes.

Cohesion and Coupling: Write BETTER PYTHON CODE Part 1

Now, let's observe the step-by-step process of improving the code's cohesion.

Watch the main refactoring walkthrough from 07:47 to 23:53. Pay attention to how the single, bloated method is broken down by: Separating the data into VehicleInfo (brand data) and Vehicle (instance data). Moving logic to the class that "owns" the data (e.g., compute_tax moves to VehicleInfo). Delegating responsibilities to the new, more cohesive components.

This refactoring exercise perfectly demonstrates the principle of "Information Expert" — that logic should reside with the data it operates on. By doing this, each class becomes highly cohesive, and the overall system becomes easier to understand and change.

Here is another, more concise example in Python that illustrates the same pattern of breaking down a large, non-cohesive data structure into smaller, more focused ones.

This Python code shows a 'bad' `ExcelSheet` dataclass with many unrelated fields. It's refactored into a 'good' version where related fields are grouped into separate, cohesive dataclasses (`FileProperties`, `SecurityProperties`, `DocumentDating`), which are then composed inside the main `ExcelSheet` class.

In your work with computer vision, you might encounter a large Detection or FrameAnalysis object that holds everything from bounding boxes and segmentation masks to tracking IDs and timestamps. The principle shown above applies directly: you could improve cohesion by refactoring this into smaller, composed objects like GeometricData, Identity, and TemporalInfo.


Architectural Cohesion: Domain vs. Technical

So far, we have focused on cohesion within classes and functions. However, the concept scales up and has profound implications for your overall architecture. When organizing the modules of a large project, you have a fundamental choice: do you group code by its technical role or its business purpose?

This leads to two architectural styles of cohesion.

Highly COHESIVE Software Design to tame Complexity

This video by CodeOpinion introduces a crucial distinction between two types of high-level cohesion.

Watch the segment from 01:25 to 04:50. The presenter contrasts informational cohesion (grouping around data structures, which we'll call technical cohesion) with functional cohesion (grouping around business behaviors, which we'll call domain cohesion). The 'product in a warehouse' example is particularly insightful.

The video makes a powerful argument. Organizing your code around domain capabilities ("functional cohesion") creates a more resilient and intuitive architecture than organizing it by technical layers ("informational cohesion"). Let's explore this further.

Cohesion and Coupling in Software with Examples

This article expands on the idea of domain vs. technical cohesion and its architectural consequences.

Please read the section "Cohesion of Different Architectures". It contrasts a typical MVC (Model-View-Controller) structure, which has technical cohesion, with an architecture organized around business domains.

A technically cohesive architecture, like the classic layered or MVC pattern, groups all "models" in one place, all "views" in another, and all "controllers" in a third. While seemingly organized, a single business change (e.g., "add a new field to the user profile") often requires you to modify files in all three layers.

In contrast, an architecture with domain cohesion groups code by business feature. You would have a Shipments module and an Orders module. Each module would contain all the necessary logic—data representation, business rules, and even interface adapters—for that specific business capability. This approach aligns the structure of your code with the structure of the problem you are solving, which is a cornerstone of advanced architectural patterns like Domain-Driven Design and Hexagonal Architecture, which we will cover later in the course.


Conclusion

In this lesson, we've defined cohesion as the measure of how strongly the elements within a module are related. By moving from a "junk drawer" of low cohesion to a "specialist's toolkit" of high cohesion, we create code that is more understandable, maintainable, and reusable.

Key Takeaways:

  • Cohesion measures the relatedness of elements within a module, while coupling measures the dependence between modules.
  • The primary goal is high cohesion, which simplifies modules and naturally leads to lower, healthier coupling.
  • We can evaluate cohesion using a qualitative scale, aiming for Functional or Sequential cohesion and avoiding Logical and Coincidental cohesion.
  • A key refactoring technique is to create small, focused classes and move behavior closer to the data it operates on.
  • At an architectural level, organizing code around domain capabilities (domain cohesion) is generally more resilient to change than organizing it around technical layers (technical cohesion).

The principle we've been exploring—that a module should be responsible for one, and only one, thing—has a formal name: the Single Responsibility Principle (SRP). This is the "S" in the famous SOLID acronym. In our next lesson, we will formalize this principle and see how it serves as the most direct and powerful tool in our pursuit of high cohesion.