Design Pattern Evangelist Blog

Smart pointers about software design

Coupling and Cohesion – Take 2

Let’s revisit these concepts one more time


Introduction

I received feedback on my most recent blog, What Are Cohesion and Coupling? The comments mentioned historical references I had missed and questioned some of my content and examples.

I consumed the references provided by those who had made comments and realized that I do need to make some updates. I don’t think my original blog is wrong, but it’s missing some nuance and context.

I originally planned to update the previous blog and call it a day, but I realized that these updates to the previous blog might change too much. For example, I should have introduced Coupling before Cohesion. Flipping them would too drastically change the original blog.

Therefore, I decided to create a new blog entry. Plus, a new blog ensures that everyone knows about the follow up.

Coupling and Cohesion History

Coupling and Cohesion were invented by Larry Constantine as part of Structured Design. The terms gained popularity in the Structured Design book co-authored with Ed Yourdon. Chapter 6 is devoted to Coupling and Chapter 7 is devoted to Cohesion.

Meilir Page-Jones also contributed to the Coupling and Cohesion literature in What Every Programmer Should Know About Object-Oriented Design, but I haven’t found many online references with details.

Coupling and Cohesion Word Salad

Coupling is dependency. Cohesion is dependency as well, but more aggressively so when all the elements depend upon one another like a team. Coupling is an attribute of a bad design. Given that cohesion is even more aggressive coupling, one would think that it’s an attribute of a bad design, but no, cohesion is an attribute of a good design.

There’s more confusion when throwing in more terms, such as loose/tight coupling and low/high cohesion. Grouping loose-coupling/high-cohesion together and tight-coupling/low-cohesion together as paired opposites adds more fuel to the fire. Continue the befuddlement by throwing in multiple types and dimensions of coupling along with several types of cohesion. Meilir Page-Jones put a bow on the confusion package when he defined a whole new term, Connascence, which is coupling in the context of Object-Oriented design.

It’s no wonder developers get confused.

Coupling and Cohesion Definitions

Coupling vs Cohesion

According to the Glossary provided by Constantine and Yourdon:

Their definition and the corresponding diagram to the right represent Coupling and Cohesion between two sets of modules. Coupling represents the number of connections between elements between two modules. Cohesion refers to the connections within a module.

These two concepts are not mutually exclusive. Constantine and Yourdon also wrote

Clearly, cohesion and coupling are interrelated. The greater the cohesion of individual modules in the system, the lowering the coupling between modules will be. In practice, these measures are correlated; that is, on average, as one increases, the other decreases, but the correlation is not perfect. Maximizing the sum of module cohesion over all modules in a system should closely approximate the results one would obtain in trying to minimize coupling. However, it turns out to be easier both mathematically and practically to focus on cohesion.

Coupling and Cohesion - Possibly Two Sides of the Same Coin

I think there’s more to Coupling and Cohesion than their interrelatedness. Coupling and Cohesion represent dependency and knowledge at a distance. Coupling represents dependency upon and knowledge of elements at greater distances, i.e., dependency and knowledge beyond the module. Cohesion represents dependency of and knowledge of elements at lesser distances, i.e., dependency and knowledge within the module. Coupling and Cohesion could be thought of describing the same concept but with two opposing terms.

The original definition of Coupling and Cohesion applied to connectivity being inside or outside a module, but I think the principle expands on a spectrum beyond connectivity that is more granular than the module.

Thinking more abstractly, the cubes in the diagram above can be thought of as any boundary, which can represent expanding borders, such as: a statement block, method, class, package, service, etc. It depends upon context. Boundaries representing different layers of borders can be nested.

The dots are elements within each boundary. The Good illustration shows high cohesion among the elements in each boundary and relatively little, i.e., loose, coupling between the boundaries. The Bad illustration also shows low cohesion among elements within the boundaries and high coupling among elements in the other boundary.

Coupling represents dependency and knowledge between boundaries. Cohesion represents dependency and knowledge within a boundary.

Coupling and Cohesion are on a boundary spectrum. That’s why loose-coupling/high-cohesion and tight-coupling/low-cohesion tend to be paired together. They represent the same basic concept of dependency and knowledge at a distance relative to a boundary.

High Cohesion means that most elements that are responsible for a behavior (or feature) reside within a boundary. Elements not responsible for a behavior do not reside within the boundary. Behavior modifications may cause updates to all cohesive elements within the boundary. You or your team should have complete responsibility for the boundary meaning there’s little to no coordination with others.

Loose Coupling means that dependency and knowledge among boundaries is limited. Abstractions, such as interfaces and APIs are boundary access ports that encapsulate internal elements within the other boundaries. Interfaces and APIs provide access to behavior provided by other boundaries without requiring external coupling to internal elements of those other boundaries.

Big Ball of Mud

Low Cohesion and Tight Coupling render the boundaries meaningless. Elements have dependency upon and knowledge of other elements anywhere. A modification anywhere can have an impact everywhere. You and your team may have limited responsibility for many of the impacted elements requiring coordination with others. Additionally, updates made by others may impact your team’s elements as well. A design like this is often called a Big Ball of Mud.

Tight Coupling also leads to daisy chained dependencies. An update to one element may require an update to a tightly coupled element, which may require an update to another tightly coupled element, etc. This daisy chained dependency can trigger what at first appears to be a simple update to spread like wildfire as cascading updates spread throughout the system.

With this more fluid interpretation of Coupling and Cohesion, we could have a system with loose-coupling/high-cohesion on the macro design level and tight-coupling/low-cohesion on the micro design level. That is, there may be low coupling between packages and high cohesion within a package, but the design inside the package could have tight coupling and low cohesion among the classes and methods within the package.

Economics of Coupling and Cohesion

Why should we care about Coupling and Cohesion? If we never have to modify our code again, then we don’t care. But as we all know, code is rarely frozen. We almost always need to modify code as business needs change. The degree of Coupling and Cohesion will determine how complex the modification will be and how long it will take.

Kent Beck provides a great review of how coupling and cohesion affect the cost of modification in his A Daily Practice of Empirical Software Design presentation at DDD Europe 2023.

Beck recounts how Constantine and Yourdon examined multiple programs at IBM and compared the ones that were cheap to change against those that were expensive to change. What was common among the cheap-to-change programs, and what was common among the expensive-to-change programs? Their analysis follows this logical conclusion:

We manage coupling in the system’s design so that we can more easily change it and do so at a reasonable cost.

Kent Beck mentioned Vlad Khononov’s presentation, Balancing Coupling in Software Design from the same DDD conference. Khononov provided a review of the coupling, cohesion and connascence and their types and dimensions.

He included a graph, which inspired my own version to summarize the impact of change as the cost of coupling, cohesion and distance:

Coupling and Cohesion Cost

This graph illustrates how coupling increases and cohesion decreases as the distance between elements expands. An update with a larger boundary footprint will require more time, coordination, buy-in and cost, than an update with a smaller footprint. For example, modifications scoped to a few statements only require the resources of one or two developers. Modifications scoped to systems require multiple teams and possibly multiple companies.

Coupling and Cohesion Examples

I struggled to provide good coupling and cohesion examples in my previous blog. I’ll make another attempt here.

Real World Examples

I used the screw/screwdriver example to describe high cohesion, but it’s coupling too. I guess it depends upon how easily you can find a matching screwdriver when you need it. And while I initially liked my Reese’s Peanut Butter Cup example, I’m not sure what I was trying to convey. Chocolate and peanut butter definitely have separate identities, which implies coupling, but darn it, when combined in a Reese’s Cup, they sure feel like a cohesive unit.

Meal Kit

I think I have another real-world example high cohesion and low coupling: meal kits. Each meal kit has all the ingredients required to make the meal without any extraneous ingredients. You don’t have to purchase different meal kits to make a meal either. If a meal is updated, it only affects that meal’s kit.

Software Design Examples

Let’s consider coupling and cohesion with some real software design examples. I shall return to some examples from previous blogs.

State Machine

I briefly mentioned State Machines in the previous blog in the Low Coupling section.

My concern was the distribution of state machine behavior in an application. I’ve seen too many cases of state machine implementations where state is a private attribute accessible via public get and set accessors. State machine behavior was implemented throughout the application by any code that acquired, changed and saved state via the accessors.

Though not obvious at first, this is a highly coupled design. All code that references the accessors indirectly depend upon one another since they are all taking some responsibility for state machine behavior. A behavior change in one section of the code could change an assumption about the state machine in another section of code. Almost as bad, if not worse, no one knows what the actual state machine behavior is, since it’s distributed in so many different places.

I would prefer an implementation that keeps state machine behavior cohesive, possibly via the State Design Pattern. This consolidates the state machine behavior. The state machine could still provide a get accessor for its state, but it should not provide a set accessor. The state machine would define public methods for each event that would trigger an internal transition and the implementation to determine the new state would occur in the event method within the state machine class. The state implementation could be enhanced with the Observer Design Pattern so that the applications could subscribe to future state change notifications.

I suspect that the coupled design occurs when developers don’t realize that there’s state machine behavior lurking in their application. If they don’t design a cohesive state machine, the design will have a tendence to be distributed.

Wrong Abstraction

My Getting the Right Abstraction is Hard blog entry is also about coupling and cohesion. In this blog, I described an implementation I inherited. The implementation featured a base class with several extending classes. As I examined the code to understand it, I realized that some behavior implementations were in the wrong places. Some behavior implemented in the base class needed to be moved into the extending classes and some behavior implemented in the extended classes needed to be consolidated in the base class.

In its original design, a change to behavior associated with an extended class, but placed in the base class, could impact the base class as well, which could impact the other extended classes.

Once I redesigned the code, using the Template Method Design Pattern, the design was less coupled. A change to an extended class would not affect the base class or the other extended classes.

Contracts

We cannot avoid coupling, in fact we need it. But we want to keep coupling loose. How can we manage that?

A contract declares behavior without defining how that behavior will be implemented. It should also declare the expectations and obligations of both the supplier and consumer of that behavior. Interfaces and APIs are types of contracts.

Many modern software practices, such as Design Patterns, Hexagonal Architecture/Ports & Adapters, focus upon contracts. The first Design Pattern Principle addresses this with: Program to an interface, not an implementation.

Context Coupling

There are multiple types and dimensions of coupling along with several types of cohesion. Follow the links for definitions or watch some of the presentations in the References below which describe some of them as well.

I’ll provide a scenario of one, which I think is technically External Coupling, based upon the Wikipedia definition:

External coupling occurs when two modules share an externally imposed data format, communication protocol, or device interface. This is basically related to the communication to external tools and devices.

My scenario fits the spirit of external coupling, but it’s not one of the listed cases. In my scenario elements may be coupled to the schema of another element, and then that schema changes. That is, the coupling could be based upon context.

We can still have coupling with a good design with good contracts. Contracts may be Leaky Abstractions. Joel Spolsky pointed this out in his blog post, The Law of Leaky Abstractions when he wrote: All non-trivial abstractions, to some degree, are leaky.

I didn’t fully appreciate leaky abstractions until writing this blog entry. I had only considered leaking abstracts in the context that implementation details could be leaked in a contract.

We don’t want to leak implementation details in a contract, but most contracts will contain context, and both the consumer and supplier of the contract depend upon and have knowledge of that context. If that context changes, and it may need to change for business reasons, then the consumer and supplier may both need to change as well.

As an example, a contract may include the definition of Person, with a name, address, landline phone number, email address, etc. Later, possibly much later, there’s a business need to update a Person to include a cellphone number or an additional email address.

The original Person attributes, landline phone and only one email, may be a domain invariant that’s been incorporated in many parts of the system. Adding a cell phone or additional email address will have an impact upon all of them since the domain invariant has been updated.

Context coupling relates to the Stable or Fixed Design Elements section in my Hexagonal Architecture – Why it works blog entry from last year. Stable elements, such as interfaces, don’t tend to have dependencies upon other elements. However other elements have dependencies upon them. This is visually apparent with a design rendered in a UML class diagram. Stable elements will tend to have the arrowheads on relationship lines pointing into them and very view pointing out of them.

In the example, other elements depend upon Person by referencing it, but Person doesn’t depend upon them. Therefore, when Person is updated, this can have a significant impact upon the elements that reference it.

Summary

Hopefully this second take at Coupling and Cohesion has provided a bit more clarity and not introduced any confusion by me.

The more I considered this addendum, the more I kept thinking about a previous blog, as mentioned above: Hexagonal Architecture – Why it works, which is about dependency and knowledge management.

Coupling and Cohesion are inverse measures of Dependency. Coupling is Dependency at great distances that forces components in a system and possibly external systems to maintain lock-step coordination during modifications. Cohesion is Dependency at near distances that allows different components and systems to be modified independently.

Several months ago, I mentioned an elusive grand unified theory of software engineering in Test Doubles:

I feel there may be a grand unified theory of software engineering that’s still just a bit beyond my grasp. If there is such a grand unified theory, I’d be willing to bet that Dependency and Knowledge Management is part of it.

I now feel that Coupling, Cohesion and Dependency/Knowledge Management are different facets of the same basic principle.

References

Comments

Previous: What Are Cohesion and Coupling?

Next: What Is Cohesive Abstraction?

Home: Design Pattern Evangelist Blog