Implicit Dependencies Are also Dependencies
Once upon a time there was a project was developed in two countries. It was a large project, its functionality sufficiently complex to justify multiple servers for different portions of the job. For convenience, the project was split along the computer boundaries. Each development site became responsible for the software running on one computer. Each site had to fulfill its share of requirements and do its share of testing. The communication protocol between the computers was specified according to the desired functionality.
A few months later, each site declared victory: The software was finished! The integration team took over and plugged everything together. It seemed to work. A bit. Not much though: As soon as the most common scenarios were covered and the more interesting scenarios were tested, the interaction between the computers became unreliable.
Confronted with this finding, both teams held up the interface specification and claimed their software conformed to it. This was found to be true. Both sides declared victory, again. No code was changed, and they developed happily ever after.
Software components have dependencies, particularly in large projects, particularly when you strive to increase your code reuse. You have dependencies, but that doesn't mean you like them.
Dependencies make it hard to change code. Whenever you want to change code others depend on, you will encounter discussion and extra work, and possibly strong forces of resistance from other developers who would have to invest their time. This force can become very strong in environments with a lengthy development micro cycle, such as C++ projects or in embedded systems.
Many technical approaches have been adopted to avoid dependencies. Parameters are passed in a string format, keeping the interface technically unchanged, even though the interpretation of the string's contents changes. Recycle unused existing error codes to avoid having to define new ones, or reuse error codes differently from their intended meaning, and document the shift in meaning somewhere in your component documentation. At a larger scale, component communication has replaced direct interface calls by a more anonymous bus where you do not need to contact your service yourself. It just needs to be out there somehow.
Here is the first warning: Obfuscated dependencies are also dependencies.
Source-level or binary independence does not relieve you or your team from dependency management. Changing an interface parameter's meaning is the same as changing the interface. You may have removed a compilation step, but you have not removed the need for redeployment. Plus, you've added opportunities for confusion that will boomerang during development, test, integration, and in the field — returning when you least expect it.
Looking at sound advice from software experts, you hear Fred Brooks talking you into conceptual integrity, Kent Beck urging once and only once, and the Pragmatic Programmers advising you to keep it DRY (Don't Repeat Yourself). While these concepts increase the clarity of your code and work against obfuscation, they also increase your dependencies.
Second warning: Application dependencies are also dependencies.
With increasingly powerful code generation engines, it is very tempting to place the application knowledge into some model, and then generate code that is technically independent of other code portions. However, their combined behaviour is meaningful only if all the versions fit together and stem from the same model. Explicit dependencies will not give you the whole picture: The implicit application dependencies that are not directly visible in code are also dependencies.
This work is licensed under a Creative Commons Attribution 3
Back to 97 Things Every Programmer Should Know home page