Coupling and cohesion are distinctly different concepts but often confused. Coupling is the degree of dependency between an application’s modules or components, i.e., the strength of the relationships between the different parts. By contrast, cohesion is defined as the measure of the intra-dependency that exists between the elements of an individual module or component.
I discussed the basics of coupling and cohesion in an earlier article here. In this article we’ll explore coupling in more detail — including afferent (incoming) and efferent (outgoing) coupling, and how they impact software quality. We’ll also stress the importance of low coupling (also referred to as loose or weak coupling), which makes it easy to isolate independent services and components.
Tight coupling and loose coupling
Tight coupling makes it challenging to change the modules of an application because one module will impact numerous dependent modules. This high interdependency makes it difficult to change the modules or to test them after a change. When the components are tightly coupled, even refactoring is difficult because changes to any one component affect all connected components.
Loose coupling denotes an approach in which the modules or components of an application have minimal interdependencies. A system is loosely coupled if each component has little knowledge of the other components. We should always strive to achieve loose coupling between the classes and modules of our applications. That’s because loose coupling facilitates testability, maintainability, scalability, and extensibility.
The Fenton and Melton method
When the coupling between software modules is strong, we may infer that the modules are interdependent, i.e., they cannot operate independently. The stability of an application is measured by how easily you can change one module or component of your application without affecting another.
Good design and well-written code pay off in the long run through easier testing, maintenance, scalability, and extensibility. Software metrics such as the Felton and Melton method can help you understand how good or bad your design and code might be.
The quality of any software application depends mainly on the extent of coupling in the application. Hence it is imperative that you measure the degree of coupling between the components of your application.
According to Fenton and Melton, the coupling between two components a and b is given by the equation
C(a, b) = i + n (n + 1)
where n is the number of interconnections between the components a and b, and i is the highest or strongest (i.e., worst) level of coupling type observed between the two components a and b.
In this case, 0 indicates the lowest level of dependency and 5 represents the highest. This number is determined by examining each of these components and determining the highest dependency relationship. This determination draws on six types of dependency relationships, or coupling types:
- Data coupling (0, lowest dependency): One component passes homogenous data to another component.
- Stamp coupling (1): One component passes an entire data structure to another component (including data that is not used).
- Control coupling (2): One component passes control flow information to another component.
- External coupling (3): Two components share an externally imposed data format, communications protocol, or device interface.
- Common coupling (4): Two components share access to the same global data.
- Content coupling (5, highest dependency): One component directly references the content (i.e., uses the code) of another component.
You can apply coupling to classes, modules, and methods. You could define coupling as the extent to which a system, subsystem, module, or class depends on others. Coupling is an important metric used to decipher the complexity of an application.
Afferent coupling and efferent coupling
It is recommended that a class should have a single responsibility. Efferent coupling (denoted by Ce) is a measure of the number of classes this class depends on, i.e., it is a measure of the number of outgoing dependencies of the class or the interrelationships between the classes.
It helps to decompose highly efferent classes into smaller classes while at the same time adhering to the single responsibility for these classes. If you have a high efferent coupling for a class, it is an indicator that the class is doing too many things. It is hard to understand, reuse, test, and maintain a class that has many dependencies.
As evident from the above figure, class C has four outgoing dependencies (classes B, X, Y, and Z) and one incoming dependency (class A). Hence the value of Ce for class C is 4.
Afferent coupling (denoted by Ca) measures the number of classes that depend on or use this class. Classes with high afferent coupling are usually small with minimal responsibilities. Still, because several types depend on them, classes with high afferent coupling are challenging to change. The value of Ca for class C is 1, indicating low afferent coupling.
High afferent coupling is not necessarily bad — this can occur when you have a specific piece of code (such as the core framework) accessed throughout the application. It will become a problem if the afferent coupling is exceptionally high across the entire application.
Instability is a measure of the relative susceptibility of a class to changes, i.e., it indicates the potential need to modify a software module (class, package, subsystem, etc.) because of changes in other modules. This metric is defined as follows:
I = Ce / (Ce + Ca)
Here, Ce denotes efferent coupling, i.e., the outgoing dependencies, and Ca denotes afferent coupling, i.e., the incoming dependencies. From Figure 1 it is evident that there is one incoming dependency and four outgoing dependencies. Hence the value of I can be determined as shown below:
I = 4 / (4 + 1) = 4 / 5 or 0.8
If a component has many outgoing dependencies but few incoming ones, the value of I will be close to 1. Such components or modules are considered unstable because it has many dependencies that could easily change. By contrast, if the component has many incoming dependencies and few outgoing ones, the value of I will be close to 0, meaning they are quite stable.
In other words, the closer I is to zero, the more stable the module or component is said to be. This makes intuitive sense, as components and modules with minimal outgoing dependencies will rarely be affected by external changes.
The degree of abstraction of a module or a component is also an indicator of software quality. The ratio of abstract types (i.e., abstract classes and interfaces) in a module or component to the total number of classes and interfaces indicates its degree of abstraction. This metric has a range from 0 to 1. If the value of this metric is zero, then it indicates an entirely concrete component or module. And if the value is one, it indicates that the module or component being analyzed is entirely abstract.
Let us now suppose that Ta is the number of abstract classes present in a component or module, and Tc is the number of concrete classes. Then the degree of abstractness denoted by A is given by the following equation:
A = Ta / (Ta + Tc)
The quest to develop software with high cohesion and low coupling increases the burden on software developers. It can also increase the complexity of the software application. The pursuit of low coupling in a software application must always be balanced against these other considerations.
By taking advantage of the above metrics for coupling, instability, and abstractness, you can work to improve the quality of your applications without introducing too much complexity in the software or putting undue stress on your developers.