When designing software, the degree of coupling and cohesion has a major impact on both the efficiency and maintenance of the programs.
When writing anything more than a very simple program, we need to design the software which typically means breaking it down into functional areas so they can be developed relatively independently.
For example, a program that uses a database may have a module of code to handle the interface with the user, but a separate module to handle interaction with the database itself. Such modular development is very common and has very many advantages.
For example, each module can be developed and tested separately perhaps by different developers. By separating out the functionality into discrete modules of development, the overall development time can be reduced by having people working in parallel.
Of course, sometimes one module requires another one to be already developed and available before testing can take place. There are dependencies between modules and this causes increased complexity. Although the ideal is that each module is independent, in reality that is rarely if ever the case.
If module A requires module B before it can be tested, but module B also requires module C, we might think that developing C first is the answer. Often though, these dependencies are interrelated so perhaps C also requires elements of A.
For this reason, designing the modules can be complex and requires us to think about how the modules are related together. We want to reduce the dependencies but not at the expense of reproducing the functionality in two or more places.
For example, if both A and B need to access a database, it would be sensible to put the functionality to handle a database in a single module and allow A and B to use it, rather than building in that functionality to both A and B.
To help us think about these issues we use two related concepts: coupling and cohesion.
This refers to how many dependencies there are between modules and the nature of the links. A module which is highly coupled means that it has to use many other modules for its own functionality to work.
A highly coupled module is difficult to test on its own and since it uses many other modules, there are interfaces between them which increases the likelihood of defects. The level of complexity of the interactions is therefore high and therefore so is the risk that it won’t work.
High coupling is generally seen as a negative in software development because we are aiming for simplicity and ease of testing. High coupling does the opposite: it increase complexity and makes testing more difficult.
If we have a single module interacting with many others, we may want to revisit the design and try to reduce the number of interactions, perhaps by moving some of the functionality to other modules, or even taking that functionality and putting it in a single module.
This is the term that refers to how self-contained the functionality is. If all of a specific set of functionality resides in a single module, we say it has high cohesion. If it is spread amongst several modules, perhaps many, we say it has low cohesion.
Generally we want to make sure that specific functionality is located in a single place where possible because this makes development and testing easier. If other modules are using that functionality, then the ease of testing it in one place helps the testing of all the others as well.
Low cohesion means that the opportunity for defects is spread across many modules and a defect could appear in several locations, making identification and repair that much more difficult.
Coupling versus Cohesion
Of course, complex software will make use of many modules and often it is simply not possible to locate all of a given set of functionality in one place. If we have a single module called by many others, the performance of the system as a whole may suffer badly.
For example, if all contact with a database is fed through a single module, the performance of that single module becomes critical to the system as a whole. This may be acceptable if we can engineer that module to have high performance, but often we cannot.
Sometimes therefore we see a contradiction between the principle of high cohesion and the principle of low coupling. We want the functionality to be in one place but that slows the system, so we decide to distribute it. The functionality is spread out over several modules to make the system more performance or resilient.
With a low-coupling, high-cohesion system, testing is often easier. Modules with discrete functionality can be tested using harnesses or a framework without necessarily having much other code available. Get the core modules right and well-tested, and the risks for the whole development are therefore reduced.
If a defect is discovered, it can be located quickly in a single module and repaired rapidly with benefits to all the other modules using it. By the same token, the module can be updated in one go with benefits to all other modules using that component.
Identifying the location of a defect is a serious problem for developers bug-fixing software and where the system has low cohesion, there are many more places to look at and test.
But the other side of the coin is that if a single serious bug is found in a high-cohesion module, the impact is more widespread affecting all the other modules that use it. A serious bug in a crucial component can bring down the entire system.
Designing software is always a compromise between what would make the testing easier, and what is necessary to meet user and system requirements in a performant and efficient manner.
The concepts of cohesion and coupling help us make sensible decisions about how the functionality is to be distributed throughout the software taking into account the future maintenance needs and changing requirements.
A highly modular system is more amenable to changing customer needs because modules can be modified as required. Low coupling makes such changes easier as does high cohesion. So if frequent change is expected, the design would need to show the appropriate characteristics to avoid costly reworking.
A system which is relatively static can accept a higher degree of coupling and lower cohesion though this still implies some increased risk.
The balance to be struck depends on the nature of the system but these two concepts help up clarify the problems of software design to improve the systems we develop.