I can’t believe the potent response I’ve gotten on my posts about Dependency Injection. Ayende Rahien responded individually to each of my posts himself, which is more than a little bit intimidating all on its own and a couple of development heavyweights left comments directly. Nate describes Ayende’s posts as the cavalry arriving and links to a couple of other responses. All of these posts disagree with me, though a post by Aaron Jensen indicates that he’s at least willing to consider the possibilities.
All of this should have been foreseeable, really, as soon as I decided to publicly post my skepticism of a technique in the midst of its ascendency just as mature tools are penetrating its development space. Surely something that popular must have some foundation in reality/fact and give measurable real-world benefit, right?
The Benefit of Dependency Injection
Its boosters see DI as assisting in loose coupling of objects. Anyone around when OOP came in and trounced all comers knows the benefits of loose coupling, right? Darn skippy. Benefits include ease of maintenance and being able to refactor elegantly.
The thing is, DI itself doesn’t really do much for loose coupling as I pointed out in my posts. DI alone has little discernable benefit.
DI aids loose coupling when you create container objects or a framework to make dependency management and injection easy. It’s essentially an additional layer of abstraction that allows you to create a dependency on the object or framework in place of the actual objects themselves. This is what Ayende means when he talks about a "powerful container". This is also the key behind Google’s Guice framework for Java and Nate’s own Ninject project as well as the Windsor project mentioned by both Nate and Ayende.
See a pattern there? I did. All the heavyweight advocates of DI rely on powerful, relatively invasive tools to realize the benefits of Dependency Injection. Until you get those tools, the benefits of DI seem mainly theoretical. The fact that the addition of a framework or container objects unlocks the benefit makes me wonder if there isn’t a different pattern effectively at work here.
Which leaves me where I started, really. You see, context is everything and the context of the vast majority of discussions around Dependency Injection involves using mock objects in unit testing. Bright young developers looking to do unit tests without hitting the database search for tools that will help them accomplish this task. They’ve heard of mock objects and figure that mocking is the key to solving their problem.
The trouble is that everywhere they look, mock objects are telling them about "design for testability" and the importance of loose coupling. It’s such a litany that many of them have come to buy into the party line because hey, "it’s just good design."
I’m sorry, but that is bunk. You cannot separate good design from the context and resources of your individual project. If my client or company needs web pages that validate users against Active Directory and then allows them access to reports based on their group memberships, then telling me that I need some robust loose coupling framework in order to test my user maintenance library is flat out wrong.
Or take a project like Subtext. I love that project even if it’s been a couple of months since I’ve done anything substantive for it. Telling me that the architecture of Subtext should be crammed into a dependency injection mold just to increase our ability to use mock objects in our unit testing makes me shudder (not that anybody has done anything of the sort, mind. I’m talking theoretically here). In the whole, that’s a lot of overkill in order to extend your test coverage.
I don’t want to come across as too much of a shill, but I have to agree with Eli Lopian (CTO of TypeMock, so hardly a disinterested party) when he says this has YAGNI written all over it. Have I been involved with projects that took a hit because a dependency had to change? Absolutely. But I’ve also been with projects that took a serious architecture hit they never came even close to needing. Outside of major corporate infrastructure projects, stringent loose coupling just isn’t as much benefit as more naturally encapsulated OO techniques.
So here we have a lot of architecture big wigs selling DI as best practices and not bothering to contextualize those statements at all. Again, Ayende is a good example of this. In Dependency Injection: More than a testing seam, he wraps up with "Dependency Injection isn’t (just) for testing, it is to Enable Change." Nice. So if I don’t use DI, my projects can’t change at all? That’s his implication and he’s not alone.
It all comes down to cost vs. benefit and that’s what I’m not seeing enough discussion of, here. What are the costs of using DI? Balance that against how often you really need to change dependency configuration. Add in how hard it would be to go from not using DI to using it and what kinds of triggers you’d use to identify when a project is big enough that the risks of architectural change justify the cost of wide-spread DI. All of these proponents are trying to claim that DI has little or no cost and/or that the benefits are incalculable. I’m sorry, but I’m not taking that bait.
Resisting the Hard Sell
Frankly, this whole thing puts me in mind of a classic hard sell technique. A customer comes to you wanting widgets. You tell him he can’t have widgets without blodgers but that’s okay because blodgers have so many additional benefits that really they should be coming to you for blodgers anyway. Indeed, all professionals are already using lots of blodgers and they all agree that it’s a good idea to have them and really you can buy yourself some widgets as well! And you’re in luck because we have plenty of the highest quality blodgers in stock right now!
So it is with DI and mock objects. I want mock objects so that I can test code without actually traversing the wire and hitting a database. Loose coupling is so far down my project priorities that standard OO practices are more than sufficient. Only, everywhere I go for mock objects, I’m being told that all the best projects are loosely coupled so what kind of incompetent fool am I for not implementing DI even before I ever thought of needing a mock object framework?
Fortunately, I grew out of that kind of bullying before I got out of high school. If I ever get into a project where loose coupling is a big enough concern that I have to consider seriously intrusive techniques in order to reduce the risks of dependency change, believe me, I’ll be looking into one of those magic DI containers. There’s some seriously cool mojo there. I just don’t have that need and since I can mock objects without it, I will.
On Better Testing
A lot of people talk about the benefits of TDD. I tend to agree with a lot of their tenets. I’m not entirely sold on "Test First" (the heart of TDD), but I’m to the point where I’m going to give it enough of a shot to see how much difference it makes and in what direction. Since TDD is such a big deal and considered by most to be extremely important, I wonder why they let themselves be hampered by the DI crowd trying to ride their coattails.
The biggest roadblock to ubiquitous testing for me came when I ran into methods that had latent processes embedded in them. This was mostly the case with database access, but also web services or anything else that crossed the network or accessed important global resources. Mock objects were the obvious solution to the problems I ran into. I balked, however, at the degree of change needed to use any of the mock object frameworks I ran across. So I lived with gimped unit tests until I ran across a stronger mocking tool.
I can’t be the only one to do this. How much have we set consistent unit testing back by insisting on selling DI as a prerequisite to using our mocking tools?
Enforcing purity tells people that you’re not as interested in solving their problems as you are in enforcing your code of conduct. "You can have mock objects, but only if you join our priesthood" they seem to say.
Again, Ayende is an example of this attitude when he says, "The main weakness of Type Mock is its power, it allow me to take shortcuts that I don’t want to take, I want to get a system with low coupling and high cohesion." As a personal statement, I guess this is fine. If Ayende doesn’t trust himself to write code appropriate to his requirements and resources, then by all means, he’s free to handcuff himself with less powerful tools.
That’s not at all what he means, though. Too often this kind of statement isn’t intended to actually target the speaker. The speaker is perfectly confident in their own abilities—in this case to create low coupling and high cohesion. It’s the abilities of other developers they want to constrain. It’s another manifestation of the "programmers not doing things my way are idiots" meme used against Visual Basic for so long.
Personally, I’m a fan of powerful tools that let me do things my way. If there’s something I’m doing wrong, please tell me. If there’s a principle or pattern that you think might be useful to me, I want to hear it. I want to hear the best case you can make and as much of the cost/benefit as you can include. But I’ve been around long enough to know that no principle or pattern is useful in every context so I’m just not going to buy arguments that claim that anything that needs X should also be using Y unless you can show me how Y is a fundamental and indivisible part of X.
And I’m sorry but DI is not a fundamental and indivisible part of mock objects.
Infinite Cost Arguments
I had a post a couple of months ago on my personal blog about safety and infinite cost arguments. Summary: I don’t buy arguments that boil down to one side having an infinite cost any more than I buy arguments that claim one side is free of all cost. This tenet applies to the Dependency Injection equation—loose coupling is a beautiful thing, but the absence of DI isn’t a sign of sure disaster.
Frankly, loose coupling isn’t an absolute value anyway, so arguments that every decrease in coupling is automatically worth the cost are absurd on their face. With DI containers and tool frameworks, I’ve come to see that DI can have benefits that aren’t solely mock object/unit testing related. I just don’t think that DI is a fit for every project because the cost is not zero and the alternative cost is not infinite. Frankly, I don’t see DI as a benefit for most projects but then, nobody has bothered to make that argument.
As such, I’d appreciate more exploration in the middle and discussions that admit that mock objects that don’t require dependency injection would be a huge boon for widespread penetration of comprehensive unit tests. Or discussions that explain why I’m mistaken in my statements above.
Most developers I know have been on projects where some dependency got spread throughout the code in such a way that any changes in that dependency created a nightmare. I’ve certainly been there. In those situations, the problem was improper encapsulation and the tight binding that brings. Comparisons between DI and those examples are like comparing a bag lady to Ms. America. Yeah, there’s a difference and all, but the fact Ms. America had a manicure isn’t it. If you’re going to make an argument I can buy using comparisons, you’ll need to compare the best of the alternatives to the best of DI.
What I’m saying is that while you should put your best argument forward I’m going to balk at straw men put in place of my position.
And the fact of the matter is that you can get pretty far achieving loosely coupled architecture before you get to DI. I’m not saying DI can’t do it better, I’m just saying that there are a lot of techniques out there that even medium-sized projects already routinely implement that provide perfectly adequate change protection. DI is on top of, not instead of, those techniques.