In January, Robert Martin posted an article about the overuse of dependency injection in many open source frameworks. At the time, I had only an academic interest in the subject. (And Uncle Bob’s rants are always a fun read.)
But that was before I started taking a look at several open source frameworks for WPF. Now I’m feeling ranty too!
Dependency Injection is an important principle of software design, but it can be abused. My biggest concern with overusing inversion-of-control (IoC) containers is that it makes learning a framework ten times harder than it needs to be.
First, you can’t tell precisely when an object gets created. Sure, it’s nice that your framework provides control over the lifetime of objects, but this should be an optional feature for advanced scenarios. Not every solution requires lazy loading. Sometimes a simple “new” will do.
Second, by placing all the wiring and object discovery in the container or service locator, you hide important information about how the framework components fit together. Yes, the global object factory knows what classes go with what types, but you don’t. That information is hidden among all the framework plumbing.
For similar reasons, Microsoft cautions against using factory methods in place of constructors in its Framework Design Guidelines:
Prefer constructors to factories, because they are generally more usable, consistent, and convenient than specialized construction mechanisms.
I think that goes double for dependency injection and IoC containers. Eliminating constructors makes for less readable code. I can’t tell what classes other classes depend on to do their jobs.
Sure, IoC containers are great when everything gets wired together automagically. But when an error occurs, I want it to be thrown from my code, as close to the source of the error as possible. (Like Jeff Atwood, I assume it’s always my fault.)
Instead, frameworks that overuse dependency injection throw most of these setup, configuration, and instantiation from its internal IoC container, usually with some horribly generic error message.
Then it’s up to me to find which dependency failed to resolve, which constructor failed to bind, or which concrete implementation didn’t get registered. And in order to do that, I have to go spelunking in the framework code — which I don’t know well — rather than the code I was trying to write, which I know quite well.
Frameworks that rely on dependency injection and IoC containers practically require you to have the framework source available and for you to become an expert in its mysterious inner workings. If I need to step through the framework code or use reflector on it, the framework has failed me.
This is exactly what Uncle Bob was decrying in his article. I don’t want to become an expert in the XYZ framework for ABC. I just want to get my stuff done.