Complexity: Excessive Dependencies
Dependencies increase complexity because it adds more to consider. If developers are unsure of what hidden dependency will break, they will have to dig into every line of code to make sure it is safe. The less a component is aware or interacts with others the better.
"Developers who are afraid to change code because they aren't sure what might be affected. Systems with many unnecessary dependencies are very hard (and expensive) to maintain, and tend to be highly unstable."
-Pragmatic Programmer by Dave Thomas Á Andrew Hunt
Let's look at a simple example to explain why dependencies add to complexity:
- Example of a simple app: one which is fully self contained, depends on nothing. eg a html based site with no serverside functionality.
- Example of a complex app: one which depends on 100 different packages which needs updates and has version conflicts, it has to reach 20 different apis, it consumes messages from many different queues, it reads and writes from a db, it logs to files, other applications are dependent on it's API and messages it produces, etc.
Efferent coupling Á afferent coupling
Efferent coupling - what this component depends on
Afferent coupling - Other components which depend on this component
To make efferent coupling easier to manage, all dependecies must be injected via a classes constructor. That way a class can only be instatiated if it has all its dependencies and you can easily see what the class depends on.
While efferent coupling can be easier to manage afferent coupling is much harder to see.
High cohesion and Low coupling
Modules should not be tightly coupled to one another, so that it is easy to swap and change modules without interfering with eachother. In order to incorporate high cohesion and low coupling, you have to have a good understanding of the SOLID principles, more specifically of Single Responsibility Principle and Dependency Inversion Principle
"Dependencies on other classes within the same module are less harmful than those outside. Likewise, when two objects are naturally tightly coupled, multiple operations involving the same pair can actually clarify the nature of the relationship. The goal is not to eliminate all dependencies, but to eliminate all nonessential ones."
-Domain Driven Design By Eric Evans
Cut out the dependency
If you can do without the dependency, scrap it. Often developers throw in every possible small tool they can find because it does something small or because the default is "not good enough". Adding an extra package, tool, dependency must be an absolute last resort, not the first thing you do.
Improve Visibility with explicit dependencies
"Even within a MODULE, the difficulty of interpreting a design increases wildly as dependencies are added. This adds to mental overload, limiting the design complexity a developer can handle. Implicit concepts contribute to this load even more than explicit references."
-Domain Driven Design By Eric Evans
Each module may or may not have dependencies on other modules or libraries. Make sure that the dependencies are well defined and explicitly mentioned so that it is very clear what is requiree to run a compotent/module/class.
Hidden dependencies can make any form of change very difficult. Do NOT hide dependencies by instantiating them in random places scattered throughout the code base. Rather keep the full list of dependencies in one place by instantiating them in one place or by injecting them via a DI container, so that the dependencies are clear. Dependency injection is one way to make dependencies more explicit, because you can't create and use the code without injecting its dependencies.
uni-directional flow of dependency
Make sure the flow of dependency is uni-directional, it should be easy to reason about the flow of dependencies without explaining the details of the code. When you open an application you have never seen before and it's structure represents the flow of dependencies, the overall picture of the project becomes far easier to understand. Compared to a structure where anything could be dependent on anything and the project structure does not represent a clear flow.
Dependencies should be encapsulated and have in Intenet Revealing Interface
David Lorge Parnas developed the concept of information hiding in modular programming. Each module should hide their inner workings and only expose what needs to be public in a fashion which makes it's use easy. Having a well thought out public interface will save you a lot of trouble. Encapsulation is one of the principles of Object-Oriented Programming which you should take a look at as a refresher.