Crafter Coder

Low Coupling and High Cohesion

7 min read
...

I have known, for almost as long as I've been coding (two years), that coupling and cohesion are two very important concepts in software design. To be specific, low coupling and high cohesion *. I've known this, and yet, I don't think I really understood it until my time as an apprentice at Codurance.

On a superficial level, I knew that code should be DRY and encapsulated into relevant and self-contained components. I also knew that related code should live close together but I think this was limited to just ensuring private methods are close to the method that calls it, or maintaining sensible project folders.

So this post will be my attempt at explaining what coupling and cohesion are, why they're important properties to think about and how to code in a way that takes them into account. As always I'll provide examples in Java.

Coupling

My good friend Wikipedia states that:

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

In simplest terms coupling is about how much your different things know about and depend on the stuff of other things. Your modules (classes) should be as isolated and independent from other modules such that changes in one don't cause cascading changes in others.

If you were to have high coupling in your software this would mean that modules know too much about each other and the inner workings of one module impact another (or many). This makes things hard to change and modules become brittle. A change in Module A, for example, would likely cause a change to be made in Module B, which may even break functionality in Module C and vice versa.

A common indicator of coupling is when the Law of Demeter is broken, such that modules are talking to their friend's friend's cousin's auntie once removed. That is to say, one object is reaching through another to modify or query yet another object. This is also associated with a common code smell Message Chains.

All of this, in case you were wondering, is bad. Bad for you, bad for your clients, and bad for the future of your software. By making it difficult to change anything in your code you make it harder to adapt to shifting requirements or requests for new features.

By reducing the coupling, or better yet, designing your code from the get-go with coupling in mind, you can easily make changes to modules without fear of breaking code elsewhere. It's important to note however that there is no way to remove coupling altogether, only to limit the impact any changes have. Low coupling also makes it easier to write, test and maintain code. This is due to modules exisiting in isolation and without an interdependentness on one another.

Interfaces are a powerful tool in the object-oriented paradigm. They can be used to promote low coupling, in my opinion, and according to the Gang of Four are what we should working towards.

Let's look at an example:

public class Example {
    private Thing thing;

    public void doAThing() {
        thing = new Thing();
        thing.getAnotherThing().doSomething();
    }
}

public class Thing {
    public AnotherThing thing;

    public AnotherThing getAnotherThing() {
        return thing;
    }
    //code
}

public class AnotherThing {
    public void doSomething() {
        //code
    }
}

This class is highly coupled to the Thing and AnotherThing. We cannot make changes in the behavior of the doSomething() method without it affecting directly this class. You may also recognize that it violates the O in SOLID, the Open/Closed Principle. It is not open to extension or closed to modification. We can solve both issues by using an interface.

Like so:

public class Example {
    private Thing thing;

    public Example(Thing thing) {
        this.thing = thing;
    }

    public void doAThing() {
        thing.doSomething();
    }
}

public interface Thing {
    public void doSomething();
}

public class ConcreteThing implements Thing {
    public void doSomething() {
        //code
    }
}

Now, if I need to change the expected behaviour I can simply create another concrete implementation. The code is now loosley coupled, easier to test and simple to extend. Of course, it's not always the better approach. You should start with the first and only when you understand the requirements for change move towards the second example (YAGNI).

Cohesion

Let's look at the Wikipedia definition once more:

In computer programming, cohesion refers to the degree to which the elements inside a module belong together. In one sense, it is a measure of the strength of relationship between the methods and data of a class and some unifying purpose or concept served by that class. In another sense, it is a measure of the strength of relationship between the class's methods and data themselves.

Again, thinking in the most simple form, cohesion is about how much all the stuff in a thing belongs together. Stuff meaning classes, modules, packages, etc., and a thing meaning an attribute, a method, etc.

If your code suffered from low cohesion it would mean that your functionality is spread out over your system or is not organized in a way that makes sense. Maybe there is duplication of knowledge, shared functionality between modules, or arbitray grouping of functionality within modules. All this leads to a very confusing codebase that is hard to maintain, difficult to test and nigh on impossible to make changes to. Each change requires careful consideration across multiple modules and changes don't seem to be made in logical places.

Take this example:

public class Example {
    private EmailThing a;
    private DatabaseThing b;
    private UserThing c;

 //code

    public void doAnEmailThing() {
        a.doSomething();
    }

    public void doADatabaseThing() {
        b.doSomething();
    }

    public void doaUserThing() {
        c.doSomething();
    }
}

Here we have what is called coincidental cohesion. The only connection between the parts of this module is that they are part of this module. Each method is doing something completely different and so none are strongly related to another. It's possible they are from a roughly similar domain but the only relationship between the parts of this module is that they are part of this module.

This is the lowest of low cohesion and is a very simple example. However, it's not out of the ordinary to see varying degrees of this in the wild. What do you think is the better alternative to this? For me, each of these methods belong in their own module.

To borrow from The Pragmatic Programmer:

We want to design components that are self-contained: independent, and with a single, well-defined purpose

A highly cohesive module will have elements that are directly related to the single functionality that module is providing. This way our code stays DRY, is easier to maintain and can be tested in isolation (the I of FIRST).

In Summary

Suffice it to say these two properties are hugely important and are vital to understand when designing good software. The two often work hand in hand. A codebase that is highly cohesive is also likely to be low in coupling and vice versa. Not always but one is a good sign the other may be present. Writing code that is both highly cohesive and low in coupling means that it will be much more maintainable, an invaluable quality for programmers and clients alike.

Keeping them in mind and working towards these principles will make it easier to design and implement new features and write code. It means we can add value quicker and more easily. Debugging becomes a joy and not a chore (ok, I may be pushing it with that one) and the risk of adopting change is dramatically reduced.

Thanks for reading! As always I welcome comments or challenges to my interpretation of the topic.

* sometimes referred to as loose coupling and tight coupling

Share: