#TIL Dependency Injection Is Both Scary and Awesome

I’ve written about this before, but recently had to debug some Dependency Injection code and ran into a few problems. While you can do some cool stuff with DI, there is a price to pay in the way of debugging if you’re not familiar with the existing system.

For those unfamiliar, dependency injection is the process of taking a class or external component your code is dependent on and instead of “hard-coding” a relationship to that component you instead have it dynamically created and passed into your codebase. This usually requires some level of abstraction or interface definition and a utility class that uses reflection to determine which dependency to inject.

In theory, this is a really good idea – being able to separate your code into reusable and exchangeable components is usually a good plan. Once you have something like this set up you can also modify the instance you “inject” at a configuration or even a runtime level allowing you to make conditional changes to your application’s codebase without having to recompile a thing. Imagine a plug-in or module system where your application automatically loads and executes libraries in a given folder. Cool, right?

In practice, it’s actually pretty hard to follow along in code when you’re trying to debug or trace through an execution path. There’s a level of “magic” involved when it comes to setting up or configuring a DI engine which may make sense to the original coder / team who created it, but if you’re coming into a project from scratch it can be hard to pick up how things are set up.

When looking at a class without DI you can tell pretty easily where external dependencies are being set up – there will be some initialization, usually passed into or declared directly in the constructor, that indicates the concrete implementation that we’ll be calling.

With DI all your constructor will say is “I’d like an IDoSomething class please”. From there you lose a lot of your traceability, particularly if there’s more than one implementation of that interface. Your best bet is to try to find the “setup” or “installer” class that actually maps the dependencies to use, and even those can be confusing if you’re not used to concepts like reflection.

I guess the moral of the story is if you’re planning on using DI, make sure it’s for a good reason and not just because it’s the flavour of the week. There’s a hidden debugging cost, particularly for new coders, that you may not be aware of.