The One Correct Way to do Dependency Injection

A couple of weeks ago a coworker told me that they have a little utility in their projects in order to set private fields for tests. He kind of claimed they needed that since they are using Spring, which in production sets the dependency. I was a little confused. But after looking into it I realized that there is an (anti)pattern going on that needs some fighting against. Lets have a look at the pattern.

Spring (and other DI frameworks) can inject dependencies into private fields. It looks like people like to use this because it combines some compelling properties:

  • Very little boilerplate code. All you need is a simple annotation on the field.
  • Since the field is private nobody outside the class can change it.

But now when you want to test your class and want to assign e.g. a mock to the field you have to either setup a Spring context in your tests or use reflection in order to access the field (just as Spring does).

Now something is really starting to smell bad here. One of the purposes of Dependency Injection is to decouple code, which in turn should make the code easier to test. Now that didn’t work out to well.

So how can we clean up this mess? We can write a setter for the field. That solves the testing problem. It adds a little boiler plate code, but hey we are talking Java here, so you should be used to that. But now we have a setter which anybody can call at any time. At the very best that doesn’t make any sense at all in the production environment. In the worst case somebody actually uses it and creates some ugly bug.

So what do you do when you don’t want anybody to change a field? Correct you make it final. Great, now you have a compile time error in the setter.You can only set a final field in a constructor. This leads naturally to the solution of all our problems (and possibly to world peace): Make your dependency a constructor argument!

This is the only place where dependencies really belong. If you try to stick to that rule I’d expect you’ll encounter the following two problems.

  1. You’ll find classes with lots and lots of constructor arguments. I’ll bet that class does a lot of different things and really needs to get broken down into smaller pieces.
  2. You’ll find cases where a class A needs an instance of B and B needs an instance of  A. This is a typical case of a circular dependency and is obviously bad. In my experience the solution is either to make B a part of A when the two are so strongly dependent that they really should be one class. More often though there is at least one more class C hiding in there so that B doesn’t need A but only C.

So really the problems you’ll find when doing constructor based dependency injection are problems that are present in your current code already. So don’t shoot the messenger, but fix the problems in your code and go for constructor arguments for dependency injection.

The second D in TDD

Some proponents of TDD say, that TDD forces you to find a good design. Some even translate TDD to Test Driven Design. I don’t agree. Mostly.

Lets start with the small part where I do agree.

Since TDD forces you to write test first, it forces you to think about how you want to use an API, because that is the very first thing you write down. This is a good thing and in my case led to more usable APIs (often in the form of Builders or little internal DSLs).

Since it is almost insane to test anything if it isn’t properly decoupled from stuff it uses, TDD also forces you to factor out dependencies in order to be able to mock them. Thats the other point where TDD encourages good design.

But there are bad news: There is more to software design than just nice to use APIs and SOLID principles: Your design actually has to solve a problem! And TDD does almost nothing for you in order to find a good solution for your problem. Yet this is really the hard part in software design.

Can your domain model represent all the cases needed by the business? TDD won’t tell you.

Should represent some edge cases of the domain in a ‘dirty’ way and thereby making the domain model simpler and therefor 99% of the use cases way easier? Or should you go for the complete but awfully complex solution? TDD won’t tell you.

Which of all the design patterns would help you? Or is there some well known algorithm you could use to solve your problem? TDD won’t tell you.

Does the problem become trivial once you approach it in a functional way? TDD won’t tell you.

Is recursion a solution or a dead end? TDD won’t tell you.

You have to understand the problem and know lots of possible approaches to a problem in order to design software well. There is just no way around it.

Once you have the solution in your mind, once you know the basic data structures and algorithm you going to use, TDD will help you to implement it in a clean, well factored way. It will also help you to stay focused on the task at hand (which is making the next test green).

Sketch and Test

I try to do TDD as much as I can. But sometimes (for example last Friday) I don’t know how to get started. This happens when I don’t have a plan of how the code should look in the end, possibly because don’t completely understand part of the problem. Especially I didn’t know how I would be able to test it, due to the fact it the code had to interact with a different system and a database.

The pragmatic programmer recommends a tracer bullet or spike in this case. This means you implement some kind of prototype in order to clarify how to implement it properly.

And than you throw that away and implement it properly again, which for me means test driven.

Do you really do that? For me this goes against the principle of baby steps and of refactoring instead of rewriting code.

Also I was fearing it wouldn’t help much, because the problem wasn’t so much that I was unclear about the code, but about how to test it.

So last Friday I tried a different approach. I call it ‘Sketch and Test’

I started by implementing the complete thing in one single class. Without tests (scary, I know). But I never actually executed the code. I just wrote it as I thought it should work.

Once this sketch was finished I looked at the code and considered the changes I needed to make in order to test it.

I did the changes and finally created the tests, fixing the bugs I found in the progress.

Only when all the tests where in place and green I actually started the application and tested the new feature.

It worked, i.e. the feature did what I expected. The code qualifies as clean and I had complete code coverage with the only exception of some weird exception handling code, that I found hard to trigger in tests.

What do you think? Is this a viable approach? Or is it just a symptom of my limited TDD skills? Or both?

Posted in: Softwaredevelopment by Jens Schauder 2 Comments , , ,