Tool Impact on Developer Discipline: Dependency Injection Frameworks

There's a lot of tools that exist to make what developers to easier to do. If it's something we do a lot, we tend to find ways to have something do it for us.
I've done it. I'll probably do it again. I built a little tool to automate the generation of Android Services based off JSON because I spent so much time copy/pasting existing services and editing them into the new serevice. Copy and Paste means you're missing an abstraction - I couldn't find it, so I built a tool to deal with it.

We find challenges and difficulties accomplishing what we want to do, so we find things we can do to reduce that pain, to simplify the process. It's one of the things I really enjoy about being a developer - I can simplify things I want to do.

The downside of the things that get built, is they get built for the way that developers write code and that's very often not inline with the technical practices. These tools promote writing code in the fashion they're built to simplify. I find that they make it very hard to write good object oriented code. It's simpler to write (not maintain) bad OO code, especially with these tools.

Dependency Injection Frameworks

Why do we need these?

Why do we need a framework to do our dependency injection?

Let's look at where objects can come into our object that dependency injection framworks try to help us with.

class Foo{
    public Foo(/* here */){}
    public void Bar(){
        /* here */
    }
}

We can either get it passed in via the constructor or instantiated within the class.

This is at least the only places I've ever seen a DI Framework used.

Why don't we want to do this?

Creates a class with all the reasons to change

I've been on a project that had dependency injection. When new functionality was added, the DI setup grew by a few more C&P lines.

This single method would change for so many reasons... The churn of this file was very high. High churn files tend to be problem files. This was a huge problem file. It was 100 lines of configuring objects.

There's zero push to "Object Orientify" this type of setup. That smells. It means that it doesn't REALLY belong in an object oriented codebase.

Resists Refactoring

Dependency Injection frameworks resist refactoring. This is my biggest concern. The code is harder to change. There's more to change than just the class. If we refactor out a collaborator, we ALSO have to update the dependency injection - and if you get down the rabbit hole; there can be A LOT of complexity; complexity engineers may not take the time to understand.

My favorite example of this was a smaller project to pull data from a few APIs and display a report.
Mostly 2 APIs. These two flows were 99% identical. All the classes used dependency injection for their collaborators. They all had custom interfaces making it really easy to set up in the DI-Setup. Really easy - this is something that bites us almost every time. It's quick and easy until it needs to change.

When I see similarity I want to change it.
I helped refactor the code to be a much more common flow with common interfaces.
We got to the top level that held the a collaborator for each flow. And we had to revert.

Two classes shared the same interface. The classes needed two different objects with the same interface. I think this is a smell, but it's a step to get to the better solution. We can't go from crap to gold in a step; many small steps that continues to work.

We had to throw away a simpler system because Dependency Injection Frameworks make it HARD to understand how to provide different implementations of an interface to the same class.
Then, when we change it later, we ALSO have to go futz with the DI-Setup more.

Dependency Injection Frameworks RESIST refactoring the code. They add extra weight and make it take more time with more risk of a mistake with the untested code.

Untested

The setup for dependency injection framworks is typically untested. There's not test coverage for when you make a mistake in the configuration. Ummm.... What becomes the backbone of how the system actually works has no tests around it?
You can get some in easy for code coverage. To ensure everything actually works; really hard to unit test that.

Hides 'new' inline

When we use a DI framework that gives us back objects for a specific interface, we're hiding the fact we're creating an object inline. We might be able to provide different objects for tests; but it's still instantiating inline.
It's why I don't like factories; they hide that something is being instantiated inline for you. The practices of "No 'new' inline" isn't just about testability. There's design and architectural implications that dependency injection and factories prevent from being in the code. Improvments prevented from getting into the code.

Dependency Injection frameworks make it harder to improve your design; and that's not an acceptable limitiation for me.

Summary

Dependency Injection frameworks only work well in classes that are examples of bad object oriented code.
If we write good OO code; we don't need DI. Inversion of Control - Absolutely. Functionality should not control the collaborators used.

DI is a code smell, removing it without reducing testability will reduce the complexity of your code.

Show Comments