#29: Coupling
Hello! After having discussed cohesion yesterday, let’s now discuss its counterpart: coupling.
Coupling refers to how dependent two components are on each other. A component can be a module, class, function, or even a whole application. The more one component depends on another, the more tightly they are coupled.
Let's use an analogy to illustrate this concept. Consider the plumbing in a house. Imagine that when we flush the toilet, the shower suddenly runs cold for a few seconds because both systems share the same water supply. This illustrates tight coupling, where two systems are overly dependent on each other.
Now imagine a house where flushing the toilet causes only a slight drop in water pressure in the shower, but the temperature remains stable. In this case, the systems are loosely coupled—they interact, but the impact of one on the other is minimal and manageable.
The fundamental principle behind loose coupling is reducing the assumptions two components have to make about each other. For example, if one class directly accesses and modifies the internal data of another class, it makes the assumption that the internal structure of the other class remains the same. Any changes to the second class can have ripple effects on the other.
Here are some factors impacted by coupling:
Maintainability:
Tight coupling: Changing one component is likely to affect another, making updates more difficult and error-prone.
Loose coupling: Changes in one component have minimal impact on others, simplifying maintenance.
Complexity:
Tight coupling: Highly interconnected components are harder to understand and reason about, increasing the complexity of the system and the debugging time.
Loose coupling: Reducing dependencies makes the system easier to understand, reason about, and modify.
Testability:
Tight coupling: Testing is more difficult because changes to one component often require updates to tests for other components.
Loose coupling: Each component can be tested in isolation, improving the overall reliability of the system.
As we discussed cohesion yesterday, it’s important to understand that cohesion and coupling are not orthogonal concepts1:
Instead, cohesion and coupling are closely related:
High cohesion often leads to lower coupling because when a component focuses on a single responsibility, it tends to interact less with other modules.
Conversely, low cohesion can lead to higher coupling because if a module tries to handle multiple responsibilities or tasks that should be spread across different parts of the system, it tends to interact more with other modules, leading to higher coupling.
One way to observe this relationship is that the three code smells related to low cohesion we discussed yesterday (shotgun surgery, god object, and middle man) are also code smells for tight coupling.
However, the relationship is not always directly inverse. For example, it’s possible to have high cohesion and still have some degree of coupling.
Here are some common types of coupling:
Content coupling: Occurs when one component directly modifies the internal data of another, leading to very tight coupling (e.g., a class accessing data in another that should have been private).
Assumption: The modifying class assumes how the other class organizes its data.
Common coupling: Two or more components that share the same global data.
Assumption: Each component assumes that the global data is structured in a way that suits their specific needs.
Control coupling: One component controls the behavior of another by passing information that dictates its logic (e.g., passing a what-to-do flag).
Assumption: The controlling component assumes that the other will behave in a particular way based on the flag.
Stamp coupling: Occurs when components share a data structure but use only parts of it, possibly even different parts, causing unnecessary dependencies.
Assumption: Each component assumes that the data structure will remain stable and that irrelevant parts won’t affect their own logic.
Temporal coupling2: Components rely on timing. For example, one service makes a synchronous call to another.
Assumption: The other service is ready and available.
Minimizing coupling3 and maximizing cohesion are essential practices in software design and distributed systems, as they can lead to increased maintainability, better testability, and decreased complexity.
Tomorrow, you will receive your weekly recap on the code health theme.
Said differently, they are not independent of each other.
My favorite!
In future issues, we will discuss techniques to minimize coupling.