All substantial knowledge I’ve come across as a SW developer has been about dependencies, and particularly about managing dependencies to minimize their impact on design and its flexibility.
A thinking tool
By thinking of what I do as “dependency management” I have a tool that will guide me in any situation, including managing projects, servers, build automation, xml- and property files, database design, architecture and object design.
It may sound somewhat abstract, but it always manifests itself very clearly when getting down to work. I just keep the following quote in mind:
“What need this part NOT know about?”
-Joakim Ohlrogge
Good dependencies are the minimal set of vital dependencies on work organized to minimize the set of vital dependencies.
Circular proof?!?
Not really, but usually an iterative process.
The range of dependency management
Dependency management ranges from organisations and projects, down to the smallest piece of bit-coding:
“Hey, we cannot finish this piece of work because we need that
piece of work from that other project and they’re not ready”
or
“It would be so much easier to parse this stream protocol if the length information was in the beginning of the stream…”
There are ways around everything, but having the right dependencies will make all work flow.
Dependency smells and refactoring
Dependency management can also be used when refactoring, by trying to identify “dependency smells”. Here are some examples that will show you what I mean and how I think about dependency management in the software design space. The examples are basically design principles versus dependency smells:
Don’t repeat yourself (DRY) vs Degraded Dependency (DD)
DRY:
“Remove duplication” – One of the core principles of all software development.
DD:
The code modules that should be depending on one single module is actually depending on two identical modules that are not the same, hence degrading both dependencies. This does not increase the code management burden to the double, but rather one order of magnitude, since the knowledge of the duplication must be passed on to and remembered by everyone involved.
Single responsibility principle (SRP) vs Piggyback Dependency (PD)
SRP:
“Let each module have one responsibility”. This principle is also interpreted as “Let each module have only one reason to change”.
PD:
If a module A has a dependency on a two-responsibility module B(r1,r2), but it is only interested in one of the responsibilities (say r1), there is an implicit piggyback dependency A->r2. This piggyback dependency will actually tie the application down, and make it more and more difficult to change over time.
Liskow Substitution Principle (LSP) vs Obscured Dependency (OD)
LSP:
States that a if a base module A (class/interface) is accepted for execution, any module B that extends A should be accepted as well. This principle protects the dependency B has on A.
OD:
Often when extension is used only to make A’s functionality available in B, or when the chain of inheritage gets long, LSP is in danger as complexity makes it harder to manage the original B->A dependency and its meaning. The functionality of the code can be validated by tests, but the design has been obscured.
Abstraction (Abs) vs Hardwired Dependency (HD)
Abs:
An abstraction tells that a module only has a description of its access points, not of how those access points will execute.
With this design pattern there is an explicit anti-dependency on the implementation.
HD:
When not using abstractions in code, executing modules are depending directly on other executing modules. This hardwiring has a tendency to spaghettizie over time, making it virtually impossible to change a piece of code without breaking another.
Dependency management and TDD
Well used, and especially with a low threshold for akward tests, TDD tends to lead to good design regarding dependencies. However, good knowledge of dependency management will lead to even better tests. I personally started to understand the value of dependency management when I started to write tests for my code, but I write much better tests (and code) now that I think in terms of dependency management.
Conclusion
By abstracting your thinking to using dependencies it is often much easier to reach good, concrete design decisions that will stand the test of time.