Poking Bears

by Jacob 18. August 2007 05:10

PokedBear 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.

Theoretical Benefits

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.

YAGNI Baby

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?

Development Principles

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.

A Caution

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.

Tags: , , , , ,

Programming

Comments


August 21. 2007 08:54
Sean Chambers
I admire how well written your posts are and your honesty, but I just can't agree with most of your statements above (not all, you do make some good points in there)

It's obvious to me that you haven't had enough exposure to TDD, DI or even mock objects to see the clear benefit from these tools or how/when they should be used. By no means should you ALWAYS use DI but there are many factors that come into play when deciding to use the tool.

You said, how much will it cost to implement DI needs to be considered and I agree, but you also need to ask yourself, how much will it cost to NOT implement DI and what are the effects of choosing to not use it in the increased complexity of your domain.

I won't respond to each and every comment you made, but I suggest you take a closer look at these tools and actually implement them before you make assumptions. I guarantee that you will like the long term benefits that you reap.


August 21. 2007 10:21
Jason Meridth
Also check out www.blog.activa.be/.../AOPIoCDINTYNoThankYou.aspx

I think he agrees with you.

I'm still researching all these topics myself.


August 21. 2007 14:04
Jacob
@Sean: Thanks for the compliment! I try to be as clear as I can, particularly when I'm disagreeing with so many well-respected people.

I'm quite open about my lack of experience with DI. That's because I require convincing before I jump on some new technique or technology. I need more than simple assurances like yours that guarantee "long term benefits". What long term benefits? If they can't be articulated, they can't be large enough to justify the costs of DI.

And just to clarify, one of the costs of DI is the cost of learning to implement it. Why undergo that cost when the benefits aren't clear and compelling?


August 21. 2007 16:13
Bob Lee
I assume you haven't watched the talk I pointed you to yet: crazybob.org/.../...tion-to-guice-video-redux.html

In it, I compare factories and service locators to DI with and without a framework with code examples.

It sounds like you assume DI is some heavy architectural burden, and it would actually make sense to use factories up until your project reaches a certain size. This is not the case.

Users can use learn Guice in minutes, and the result will be a lot less boilerplate code than required by factories, etc. It makes sense to use Guice on all but the most trivial projects.

You're also focusing heavily on mocking for unit tests--I have to abstract code for many more reasons on a regular basis. Guice provides a much cleaner method than any other approach I've seen.

You said something interesting: "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." I've often said that DI frameworks are a symptom of deficiencies in the language. Sometimes I wish Java didn't have "static" at all. Until we fix the language, DI frameworks like Guice are our best bet.

It comes down to this: forget mocking for a moment. Guice enables a much cleaner design for my application, simpler code, and more up front type checking than any other approach I've seen. Do you know of a better approach? If not, what's your point?




August 21. 2007 18:25
Jacob
@Bob: I admit I haven't watched the video you pointed to. A part of the DI overhead I'm talking about here is the learning curve in digesting DI frameworks like Guice. Yeah, it may take "only" an hour, but that's just the initial introduction. As with any new pattern or technique, it takes practice and effort to become familiar enough with it to be able to make competent choices using it. Downplaying this training overhead is a part of what I find so frustrating about DI advocates.

So far, the benefits of going through all that are rather amorphous. Telling me I'll achieve "looser coupling", "cleaner design", "more up front type checking", and "simpler code" than any other approach you've seen isn't helpful because those are all relative measurements with no baseline to compare them to.

THAT's my point. Do I know of a better approach? Absolutely. How about the one I'm using right now where I don't have any learning curve/additional overhead and where I don't have to change habits and thought processes that I have been years in the making.

All of the DI advocates arguing with me have failed to ask me one critical question: what kind of projects am I dealing with?  What's their scope and complexity? Saying that DI beats factories and service locators does nothing for me because I'm not using factories or service locators. They're just not appropriate for the scale of project I'm working on. As I've said repeatedly, context is vital when making architecture and design decisions.

No pattern or practice is applicable to every situation. It'd be helpful if DI folks would honestly explore the contexts where DI doesn't make sense. I'd find that much more useful than all those claims that imply that it always makes sense. Nothing always makes sense. Nothing.

At the scale of my current projects, I want good unit testing because I know that my logic layer will be prone to change. I have a solid use for mock objects because I want to preempt calls to the database in my logical unit tests. At this scale, however, the chances of the location and/or structure of my database changing are slim to none. If, by some chance, the location changes, a simple config file update is all that is needed.  If, by some chance, the structure of the data changes, I'll need fifteen minutes and a recompile for those changes to penetrate my entire code base. My internal objects are small and their interactions and dependencies simple and easy to track so adding DI to them is serious overkill.

Tell me how DI is a benefit in that scenario? In my evaluation so far, DI has no practical benefit to me outside of enabling mock objects. Thus, being able to mock objects in my unit tests without having to resort to DI is of tremendous value to me.


August 21. 2007 18:32
Colin Jack
It goes without saying that I generally believe in loose coupling but coupling/cohesion always need to be considered in context.

For example having my business classes directly coupled to the database or a Webservice seems to me to be a bad idea and I avoid it all costs. I don't want my business classes involved in that sort of work so I try avoid getting them involved. However in some cases it is unaviodable and I then turn to a dependency injection based approach involving interfaces. The key point for me is that I'm decoupling because that is what I think is the best option for the design, not just because I need to avoid it to make the classes testable.

Anyway to me DI isn't that complex an idea and some of the frameworks are ridiculously simple. However redesigning to allow the IoC framework to get in there (interfaces aplenty) does have a cost in terms of complexity. If I need the decoupling or if I think it makes the software more understandable then great, if not I'm buying very little.

I also agree that much of the discussion of IoC is very one sided at the moment, but to me thats just the nature of blogs.


August 21. 2007 18:50
Udi Dahan
I can't prove that riding a bike is faster than walking to someone who doesn't want to hear or know anything about physics. That doesn't mean that riding a bike isn't faster.


August 21. 2007 21:05
Bob Lee
I second Udi's comment.

You don't even use the factory pattern? Then you're right--you don't need DI. Heck, C# is probably overkill for your application. Have you tried Ruby on Rails? ;)


August 21. 2007 21:49
Jacob
@Udi: Nice. Thanks for stopping by and contributing to the discussion.

@Bob: Just because all the cool kids are being mean doesn't mean you have to follow suit.


August 21. 2007 22:18
Bob Lee
Ha ha. I couldn't resist. Smile


August 24. 2007 19:20
Udi Dahan
Am I one of the "cool kids"? God, it's about time! I didn't know I was being mean, though. Maybe it just comes with the cool territory. Hope not.

Anyway, most everything has been said either here in the comments or in the referenced (and referring) blog posts.

Loose coupling is important to me. I believe that its importance increases with the size of the development team, and doubly so if they are geographically dispersed. I have used it on smaller efforts of less than 5 developers and have seen smaller, but still significant gains. The developers themselves attested to the fact that they would not be going back to their old way of working going forwards to the next project.

I would say that DI by itself isn't enough. When adding the Single-Responsibility Principle, Separation of Concerns, and the other OO principles then we see the true benefit. I would agree that practicing DI by itself without the said principles would bring much fewer benefits.

Am I still cool?


August 24. 2007 20:00
Jacob
@Udi: I agree with everything this last comment said so obviously you're still cool. Or, at least as cool as me. Uh, forget I said that...

You know, what you say about multi-developer projects is interesting because it's along the lines of what I was thinking. I'd be willing to bet that the benefits of DI, SoC and other good design practices increase exponentially with the number of developers while their costs are a linear progression. Huh. That bears some thought...


November 21. 2007 04:29
Tim Connor
Stand strong. ;) I am intimately familiar with TDD, I do my own mocking by hand, I have done a couple projects in Monorail in the long past, and I come down on the "no thanks" side of DI.  

Of course, I'm primarily coding in ruby now, and as it's a dynamic language, mocking is much, much easier.  I actually looked into a couple of the DI frameworks for ruby, for a library I wrote, decided it really wasn't worth the overhead, and then read the post by the author of both frameworks admitting that DI in dynamic languages is sort of like a manual clutch in an automatic car (not the analogy he used).

I completely agree that the zealotry behind the DI push is turning developers off of proper testing - it did for me back then.  I would have done more testing on my MR projects if it weren't for the overblown architectural decisions involved, and the cognitive load behind going down that road.  I think most developer would be better served with less architecture astronauts weighing in and more focus on making testing easy, clean, and almost harder not to do.  

Instead of peddling DI as the only way to go, be more agile and keep pushing TDD forward.  Try BDD.  Instead of making everyone else get bogged down on the arcanum of how complicated we can make it, work on lowering the barriers of entry for testing.  Otherwise you get a couple people building castles out of clouds, and the rest continuing to stack piles of mud on top of each other, for lack of interest/knowledge in cloud sculpting.  We need kilns for making bricks more than sky writing in the testing world, still.

For those that want to go down that road, great.  Just realize it may not be the only way to go, and just as you feel you know better, and the rest of the world is missing out, YOU might be missing important developments in how to test better, due to the significant opportunity and sunk cost of DI.

As an aside, I really respect Hammet and Ayende and what they have tried to carve out in  the .NET world.

Comments are closed

Information

    Recent Posts

    Calendar

    <<  September 2010  >>
    MoTuWeThFrSaSu
    303112345
    6789101112
    13141516171819
    20212223242526
    27282930123
    45678910

    View posts in large calendar
    Disclaimer
    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010 Scruffy-looking Cat Herder