Having hard-coded class-names like in the example Time.now
is effectively the same as communicating
via global variables. DHH's suggestion of stubbing out the Time class's now
is selling us mutable
global variables as the solution to global variables. Or more precisely: passing an argument to a method
by modifying a global variable that the method reads out and restoring the state of the global variable afterward.
If that's "better", I don't really want to see "worse", and not wanting that sort of thing has nothing to do with being a Java drone limited by the language. And my experience with time-dependent systems tells me that you really want to pass the time into such a system generally, not just for unit testing.
Of course, having n-levels of AbstractFactoryFactory
indirection could actually be argued
as being worse, as Tim convincingly does, but that's one implementation of DI that's hobbled by Java's limitations. For a DI solution
that's actually simple and elegant, check out Newspeak's module system (PDF): there is no global namespace, modules are parametrized and
all names dynamically resolved.
If you want synthetic time for a module, just instantiate that module with your own Time class.
18 comments:
Amen. That article was complete hogwash. As were the majority of HN comments that were seemingly jumping on the "DI hate bandwagon".
My argument: Dependency Injection is not a virtue most of the time.
As vulgarly used and misused:
- brakes encapsulation (internals are at the skin of classes - ctor &| setter - to be injected), what have I done wrong as a user to see your internals?
- brakes the valid upon construction protocol
- introduces a level of indirection by default even if no one ever needs it in overwhelming majority of cases - there will be no alternative implementation to inject most of the time - there will be no time for it most of the time
- it is an explicit invasive technique (take the container away and your code is worthless)
- in most implementation you will no longer read fluently the code but the dependencies will be in configuration files and even not obvious
Inversion Of Control on the other hand has genuine good applications. For instance when generating data one can invert the call so that the user will create his own structure with it...
I do not hate ;)
"The heart of any algorithm is an idea. If your idea is not clearly revealed when you express an algorithm, then you are using too
low-level a notation to describe it."
-- Skiena, The Algorithm Design Manual
DHH's article is more Java hate than anything else. DI increases modularity at the cost of some complexity. In Java you need to use a lot more DI to maintain a good balance of modularity, but it is not necessary to the same extent in more powerful languages.
@Anonymous: Did you actually look at Newspeak? I'd say it solves all the objections you raise, which are objections to bad/limited implementations of DI.
Inversion of control, otoh, is something that, well, seemed like a good idea at the time. While it can still be beneficial, I'd use it extremely sparingly and with great care (for more details, see one of my favorite articles: Architectural Mismatch: Why Reuse is Still So Hard, http://repository.upenn.edu/cgi/viewcontent.cgi?article=1074&context=library_papers )
Time is pretty global variable...
Which is simpler: stubbing a call to Time.now, or passing an explicit parameter?
@jeremias: ("Time is pretty global variable...")
Rrrrinng....Rrrriiiing...Hello? Mr. Newton? There's a Mr. Einstein who says he'd like to have a word with you..
At first blush, Time appears pretty global, but in fact, it's not, physically (Einstein), geographically (GMT, MEZ, EST, PST, PDT) or pragmatically: do you want the time your user entered a request (her machine, your machine?), the time you received it?, always the current local time (different times at different points of processing the request...).
And of course, you also really need synthetic time for testing.
Funny fact:
Write in assembly, why parameters... play around with stack pointers (everything is global)
Write in C and put some structure into the program (and something is global)
Write in Haskell and make sure that everything you can't have globals (not 100% true, but almost there)
Write in ruby following DHH ideas: you can't say if it's global, local, where it come from, where it is going, I don't even know how I got here first.
Rails is a great project and help a lot of folks to get stuff done. But DHH crosses the line with it's "cool way of programming"
" you can't say if it's global, local, where it come from, where it is going, I don't even know how I got here first"
Yes, I think it's the breaking of expectations that's the problem with stubbing like that, and IMHO with the Mock approach to testing.
I've always wondered why I find "operator overloading" so troubling in C++, yet effectively the same capability causes me no stress at all in Smalltalk, and I think this may be it: in Smalltalk, my expectation is that everything is a dynamic message send, whereas in something that looks like C, I expect operators to be fixed.
" If your idea is not clearly revealed when you express an algorithm, then you are using too
low-level a notation to describe it."
Very nice! And similar to Kent Beck's "intention revealing selectors". I also like:
"When programming a component, the right computation model for the component is the least expressive model that results in a natural program."
http://c2.com/cgi/wiki?ConceptsTechniquesAndModelsOfComputerProgramming
@jeremias time is not a global variable, what if I want to write a test for a one time situation 10 months in the future? I need to be able to fake time, i don't want actual, current, live time. I want the code to think that the current time is 10/7/2012 so I make sure it is doing what I want it to do.
I ended up blogging a response to this and DHH's article, DI Sucks! -or- My Language is Better Than Yours.
TL;DR - I haven't found a lot of cases where a full blown DI framework is necessary, and in most cases DI itself is only really necessary for unit-testing purposes.
Also, DHH makes some pretty opinionated statements. While most of the time I agree with his posts, this one is a bit out of bounds. And I'm not a fan of the lack of comments - what's the point of sharing ideas if you can't get feedback?
I find that DI is actually an aweful level of indirection. It's hard to follow "what the fuck is injected here??!?!?!!" and then you dig through a few XML files, and vuala, and not so simple answer.
However in java, you at least have interfaces. You know approximately what you will get, then you put a breakpoint then you inspect what it actually is, then you find the offending class and fix it.
It's annoying. And of course the primary reason is testability. And the secondary reason is de-coupling and making interfaces truly what they intend to be "my class don't give a shit what I get, only that it adheres to this interface".
In ruby I love that I worry less about DI and more about stubbability. And stubbability can be resolved in other ways (like moving a constructor call into it's own function). So in the end, I am enjoying the lack of DI in ruby. Even though at first I was cursing it's lack-there-of.
I wrote a compiler and changed the language so that you don't need parenthesese on function calls with no args.
Hyperlinear scaling works both ways -- if you can shrink something to 64-K, then all pointers are 2 bytes, for example. I don't do that.
If it's small like a C64, then you can use global variables and complexity is removed hyper-linearly!!
The amusing thing about this DI debate is that it has been good programming practice for decades, but it just didn't have a cool name and it didn't have IoC to muddy the waters. A simple example of DI with no interfaces or IoC
No DI :
class Invoice()
{
void Save()
{
dbConn conn = new dbConn("")
conn.Save()
}
}
DI :
class Invoice(dbConn conn)
{
void Invoice()
{
_conn = conn
}
private _conn
void Save()
{
_conn.Save()
}
}
This is still DI in my opinion, and is a very clean way to code, and doesn't necessarily require interfaces and IoC.
@jasper: sounds cool, I'll check it out.
Post a Comment