Dependency Injection Is a Virtue
Marcel Weiher rebuts David Heinemeier Hansson:
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’snow
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.
2 Comments RSS · Twitter
It is very interesting that what Marcel considers the ultimate solution, Newspeak's modules, is about as far from "dependency injection" as you can get. Dependency injection is what you have to settle for when your language is insufficiently dynamic.
And as far as Newspeak's method goes (dynamic lookup of everything but with all state explicit), "Time now" in Newspeak is as undeterminable as is a stubbed `Time.now` in Ruby. That is apparently fine with Marcel - and fine by me, I should say - but it seems to worry some of the commentators.
As far as I'm concerned, this is still an unsolved problem that no one has gotten right yet. The crux is that concerns are separate. You should not have to worry about how an object you're going to use came to be and you should not have to take to strange incantations to make sure an object is magically composed. Your code should be completely oblivious as to whether it works with real time or virtual time or temporarily fixed time, and that means also not needing to be handed a TimeServiceFactory using which you can instantiate a TimeService from which you can then GetCurrentTimeOrIsItNowReally().
So what do I do, then? I don't know the solution. But I have recently taken to using a Universe object that I put most of the "services" on, and pass that around everywhere. Now, that gives everything far too many capabilities, but the languages I'm using all can grab these weapons from the global state anyway, and it means I have to spend a few lines instantiating everything by hand up front, yeah, but that's proven to be negligible.
(While I'm spitting on people who disagree with DHH, I also think that unit testing and TDD are not useless but highly overrated. They give you a false sense of comfort that you've thought of everything and make you decouple things several steps than would otherwise be needed just so you'll "cover more code". I use unit testing when it makes absolutely clear sense, and I even take pains to do so when it doesn't but when the effects are obvious. However, I will not defect to the fiction that every line of code I write has to be unit tested. There is a cost-benefit analysis for unit testing, and it does have an equivalent of "biking to Hawai'i".)
And yes, I seemed to have missed pointing out that mucking with global state is not a particularly proud way to solve the problem. For one thing, there goes predictable, isolated unit tests and using all of those cores at the same time to run unit tests concurrently.