Coupling Summary

2023-04-16

Previous: Coupling By Inheritance Next: Coupling Modules Into A Structure

Summary

Some Connections Between Software Modules
Simplicity Replaceability
Function Call
  • Simple as possible
  • Coupling is simply Call
  • New function
  • One-line change
Class Method Call
  • Simple
  • Class is namespace
  • Coupling is simply Call
  • New class or method
  • One-line change
Object Method Call
  • Medium
  • Two-part Coupling:
    Instantiation + Call
  • New class or method
  • One-line change
Injected Function Call
  • Somewhat Complex.
  • Three modules, but each
    module is simple
  • Two-part Coupling:
    Injection + Call,
    in different parts of code
  • New function
  • One-line change
Injected Object
Method Call
  • Complex
  • Three modules
  • Three-part Coupling:
    Instantiation + Injection + Call,
    Call in different part of code
  • New class
  • One-line change
Inheritance
  • Complex
  • Three modules
  • Two-part Coupling:
    Inheritance + Call
  • Possible irrelevant inherited connections
Worst case:
  • New parent class
  • Refactor class hierarchy
  • One-line change

Conclusions

This little analysis shows that connections between software modules vary quite considerably in their complexity, and therefore in their readability. Direct connections are considerably simpler than any form of dependency injection. Inheritance may be fairly simple as far as it goes, but it may also drag along an arbitrarily large amount of 'collateral coupling' to things that are not actually dependencies.

The obvious question is: Are there benefits to the more complex forms of connection?

In the limited scope of this preliminary analysis, the surprising answer seems to be 'no'. We have defined a specific simple form of code modification, that we have called 'Replaceability', and discovered that achieving it is equally easy for all forms of code connection, except inheritance: For all connections except inheritance, swapping out a module simply requires writing the new module and changing a single line of code.

Inheritance suffers from the fact that, in the worst case, the inheritance/abstraction hierarchy may be discovered to be wrong, and the entire inheritance hierarchy needs to be rewritten, just to swap out a module. Or even worse, an ugly workaround needs to be hacked into place, which will severely damage readability. This fragility of inheritance appears to be well-understood in the Object-Oriented community nowadays. The standard advice since at least the dawn of the 21st century has been to "favour object composition over inheritance".

Now, this little analysis of 'ease of code modification' has been restricted to 'ease of swapping out one module for a replacement'. Of course, there may be other reasons to favour a more complex connection such as dependency injection. For example, ease of testing, or confining code changes to certain parts of the code base. The lesson from this little analyis is that sometimes claims of 'extensible, modular code' turn out to be hollow. When designing a software architecture, we want an accurate picture of the true costs and benefits of each design, so we can decide on the appropriate trade-offs. It is not enough to blindly accept dogma.

A deeper analysis of connections is beyond the scope of this work. But we can propose a few rules of thumb:

🙠