Summary
| Simplicity | Replaceability | |
|---|---|---|
| Function Call |
|
|
| Class Method Call |
|
|
| Object Method Call |
|
|
| Injected Function Call |
|
|
| Injected Object Method Call |
|
|
| Inheritance |
|
Worst case:
|
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:
- Avoid inheritance, in general.
- Be very careful with dependency injection.
- Favour simple direct connections.
- Connect a module if and only if it is a dependency. (If it aint' a dependency, don't couple it. If it is a dependency, connect it as directly as possible.)
🙠