TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Dependency injection is not a virtue

65 点作者 hmart超过 12 年前

21 条评论

javajosh超过 12 年前
Am I the only one who finds this post confused and unnecessarily mean? Roughly, the post goes like this:<p><pre><code> 1. DI is bad, mkay? 2. Ruby offers the best alternative to DI. 3. BTW, programmers *are* their languages. 4. Hence Ruby programmers are the best. QED. </code></pre> What is odd is that #3 is offered almost as an aside, with nothing more than a link to the wikipedia article on "linguistic relativity" for support, and yet it poisons this post and turns it into a mean-spirited rant. It makes the entire post a personal attack on anyone who dares to disagree with it's assertion.<p>This is not okay! I happen to dislike DI even more than the OP, but to <i>attack people</i> on a personal level for liking it is just plain mean, and unnecessarily so.<p>When you assert the identification of self with technology preference, you reenforce a damaging idea that has no merit, which is indeed the very idea that ensures that such discussions often have more heat than light.<p>Shame on you, David, for using your position to promulgate an idea that is not only useless, but actively damaging to the community of programmers.
评论 #5017308 未加载
h2s超过 12 年前
Testability isn't the only good thing about dependency injection. It's much easier to reason about OO code when objects only tend to interact with objects that they have been explicitly given. Too much willingness to introduce static dependencies increases the risk of creating classes with too many collaborators and/or responsibilities. This is a good example of how a testable design is often also a just plain good design.<p>Sure, the given examples of "Time.now" or "new Date" are innocuous and I agree that it's a good thing that Ruby allows us to write this type of code without creating a testability issue. But too many people abuse static dependencies for things like database connections which are:<p>1. well-suited to being represented as proper objects that are passed around<p>2. better off confined to a small area of the codebase<p>This is a complex issue and there's a delicate balance that needs to be struck. Otherwise you can end up with a codebase where a few classes are all up in everyone else's shit. That's when you find yourself in embarrassing situations such as some code not working without a database connection even though there's no real need for that beyond the tangled web of static dependencies chaining it to the database class.
ssmoot超过 12 年前
Calling out DI is a bit of a misdirection as most have come to know DI I think.<p>What he's really arguing against here is more basic: Composition.<p>Pragmatically the examples makes some sense. Why not stub out any global like this though? Because it breaks encapsulation and makes the program harder to understand.<p>Instead of lifting dependencies up to the level of method parameters with good composition, now you have to truly grok the entire method before you can have any confidence you even know what to stub out, and wether that's actually going to produce the expected results.<p>So while I fully endorse this specific, localized, easy to understand example, the poor composition of many Ruby libraries is what tends to make working with Ruby code so damn hard (IMO, see: Bundler). It's not enough to read method signatures. There are no explicit interfaces. Only implicit ones you devine by understanding the full breadth and scope of how parameter A is used in method B, all the way down the stack trace.<p>Fundamentally here DHH isn't talking about "Dependency Injection". He's talking about Composition and a pragmatic example of breaking Encapsulation. While sure there are 101 ways in which breaking Encapsulation can be a useful, pragmatic technique to employ for the seasoned code-slinger in the small, in the large it makes for more difficult to understand, and therefore less maintainable code.<p>I find many of these recent posts by DHH a bit ironic considering the subtext of a guy who read the PoEAA, then went off and wrote an MVC web-framework, packed with a nice Table Data Gateway, then proceeded to confuse Unit for Integration tests, and soap-box on the evils of Design Patterns in general.<p>[EDIT]<p>PS: The obvious example for making it easy to test, without breaking encapsulation, would simply be to avoid globals and use a default parameter.<p><pre><code> def publish!(current_time = Time::now) self.update published_at: current_time end </code></pre> TA-DA. So trivial examples might show how a very shallow example of monkey-patching can be a nice convenience, you also have simple "fixes" that actually take _less_ code to implement.<p>You could easily come up with deeper stacks, presenting more difficult problems, but then you're not really making a great case for the beauty and simplicity of monkey patching if I have to have such a deep understanding of the side-effects in your code before I can even start making sense of your tests.
评论 #5017301 未加载
评论 #5017217 未加载
评论 #5017249 未加载
评论 #5017595 未加载
评论 #5017390 未加载
hackinthebochs超过 12 年前
I'm sure my opinion is in the minority here, but all the pages and pages of blog posts that we have collectively created to rail against various design patterns seem like we're just trying so hard to justify bad programming practices.<p>I don't know ruby so the example given wasn't exactly clear, but it seems like you're able to (temporarily I hope) override the method Time.now to return specifically what you want. While that certainly seems reasonable for testing purposes, I would hate to have that kind of stuff happen in production code. The fact that methods can be overridden at any point in such a manner is a detriment to predictability, on the level of the longjump statement itself. It's hard for me to believe that people actually advocate this stuff as a "good thing".<p>Yes its true that dependency injection is a substitute for missing language features, namely modifying private data or globally scoped objects at any point. This is a good thing! Dependency injection allows you to have the same power while still giving you the predictability that truly private scope brings. If the cost of this is a few extra lines of code and some indirection in the form of interfaces, I'd say it's well worth it.
评论 #5017063 未加载
评论 #5017086 未加载
swanson超过 12 年前
There is a camp in Ruby that thinks monkey-patching (extending or modifying behavior at run time) is a great feature and defining characteristic of the language that should be exploited.<p>There is also a camp that thinks it is powerful, but dangerous and makes it harder to reason through a program (since your objects can be changed out from under you).<p>If you put yourself into the mindset of the pro-monkey-patch camp, it is easy to see how DI comes across as an over-engineered solution to a non-problem.<p>One thing I am finding more and more important is to try to understand the perspective of the author of a given blog post. While it is more difficult to do (since no one puts a disclaimer stating their views at the top of the posts), it ultimately helps to improve my understanding of competing arguments and how they can align with the problems I face in my own projects.<p>It is okay to come to the conclusion that an argument is not right or wrong in the absolute sense, but right or wrong for me and my work at the given time.
评论 #5017083 未加载
评论 #5020870 未加载
nbevans超过 12 年前
This article is just so biased and full of odd opinions.<p>"If your Java code has Date date = new Date(); buried in its guts, how do you set it to a known value you can then compare against in your tests? Well, you don't. So what you do instead is pass in the date as part of the parameters to your method. You inject the dependency on Date. Yay, testable code!"<p>Seriously? I've never seen things done in this way, ever. The correct way is to have a TimeSource interface and forego the direct use of Date. Simple. At this point it feels like the author's premise of the article has been invalidated.<p>I don't like using the terms DI or IoC. I prefer to talk about "composition" and "componentisation". Because those are the real goals. Testing is almost a secondary concern in this regard, it is just a nice side affect and bonus of writing well designed software. The primary reason for designing your software in the style of composition and componentisation is for the separation of concerns and the achievement of all the SOLID principles. But let me guess, Ruby hipsters think those are bad too huh?<p>As an interesting note, I once went to a Hacker News meetup in London and not a single one of the Ruby developers I spoke to even knew what DI or IoC were. Most didn't even know what static typing was either. Or even type inference. This is not really a good sign of a healthy community.
kevinpet超过 12 年前
His recommended solution prevents parallelizing your unit tests. Dependency injection is not just testable globals. It's about declaratively defining what global-ish things your class depends on.<p>You also don't need to provide the date as a parameter to your methods. You can make the class depend on a clock. A clock is a natural thing for a service to depend on, and clearly indicates to outside users that the class needs to know about time.<p>If it isn't a service, and you're updating some sort of entity, then why should the entity figure out the current time itself? The model should just record that I updated such and such field at such and such time. If it's the current time, that's someone else's responsibility.
评论 #5017596 未加载
vii超过 12 年前
Dependency injection subjugates readability and simplicity of data flow for a narrowly defined notion of testability. Even in languages like Java, it's possible to substitute class definitions for mocked ones without having to thread a weird sort of test monad through code that otherwise serves a purpose.<p>I pretty fundamentally disagree with making production code more complex to make unit tests easier to write. Make the tests more complex instead and think about the natural units for testing. Maybe the natural unit for testing isn't exactly one class.<p>This is counter to the conclusion of the article: it's true one shouldn't force Java idioms on Ruby but also if the Java idiom doesn't translate well, maybe it's a bad programming idiom in general.
评论 #5019571 未加载
评论 #5017407 未加载
Eduard超过 12 年前
"In languages less open than Ruby, [...]."... It's better to not use a metric so vague and ambiguous as "open" to introduce your opinion post.
adamjernst超过 12 年前
Amen. Same goes for Objective-C: dependency injection simply isn't needed when you can swizzle out any method (including +alloc) in tests.
jfb超过 12 年前
"Design patterns are missing language features."
评论 #5017275 未加载
tel超过 12 年前
Languages shape the way that you think:<p>And so this is just killer to my mind. Time.now is an impure effect and should be isolated so that testing can occur easily in the pure code.
tarr11超过 12 年前
I'm not as familar with Java, but in C# you can use "shims" to accomplish this same result, without DI.<p><a href="http://www.peterprovost.org/blog/2012/04/25/visual-studio-11-fakes-part-2/" rel="nofollow">http://www.peterprovost.org/blog/2012/04/25/visual-studio-11...</a><p><a href="http://msdn.microsoft.com/en-us/library/hh549176.aspx" rel="nofollow">http://msdn.microsoft.com/en-us/library/hh549176.aspx</a>
评论 #5017263 未加载
deafbybeheading超过 12 年前
The thing is, unit tests are another "use case" for a given piece of code. Many people here are saying, "I wouldn't do monkey patching in production, but it's not really a problem for stubbing in test code." And what happens when you want to make different use of that code in production? Sure, "YAGNI, rewrite as necessary" and so on, but ruthlessly applying YAGNI leads to code so inflexible that you need to rewrite a whole component to make a change in one class (or start playing with monkey patching in production code, but I have not seen anyone advocate doing that liberally).
mattrepl超过 12 年前
Adding function parameters solely for testing purposes is bad. However, poorly-designed functions are often difficult to unit test.<p>I don't know the entire backstory, but it appears someone wrote a <i>publish!</i> method that took a <i>publish_time</i> argument that was only intended to be used in testing. The problem is that the original code didn't properly support some <i>publish_time</i> values.<p>This is a good example of the library vs. framework distinction. Frameworks favor opinion over composition.
ExpiredLink超过 12 年前
"Dependency Injection" Considered Harmful: <a href="http://www.natpryce.com/articles/000783.html" rel="nofollow">http://www.natpryce.com/articles/000783.html</a>
powermockr超过 12 年前
"If your Java code has Date date = new Date(); buried in its guts, how do you set it to a known value you can then compare against in your tests? Well, you don't."<p>You don't if you aren't testing your code properly. If you are, one option is PowerMock.<p>whenNew(Date.class).withNoArguments().thenReturn(someDateWeKnowAbout);
mdpm超过 12 年前
When looking at code, I like to separate design-time concerns from runtime ones. Tests are a design-time affair, and if you are substantially altering the run-time composition of your application to accommodate such things, the approach is likely wrong.
shadowmint超过 12 年前
I can't be bothered talking about DI much, because it's a non-issue. Use it if it's appropriate. You use ruby? Big deal. You're not special; if there's a situation where DI is helpful, use it. If not don't. This isn't a complicated idea.<p>I was mildly interested in the idea that 'language shapes the way we think'.<p>My initial response was: WAT? O_o<p>After all, there are some pretty detailed criticisms of the idea (<a href="http://www.quora.com/What-are-the-main-criticisms-of-Whorfs-theory-of-linguistic-determinism-and-relativity" rel="nofollow">http://www.quora.com/What-are-the-main-criticisms-of-Whorfs-...</a>), and that's just with actual languages that we think and talk in, not programming languages.<p>After thinking about it for a bit, there's some basis for this idea, perhaps.<p>You see, the central idea of whorfianism is basically:<p>- Your language affects the type of mental constructs you use.<p>- People with different mental constructs behave differently.<p>So for example (classic example), if you have a language with no concept of sub-day time units, you'll end up with a society that isn't fussed about punctuality. "Turn up in the afternoon"; ok, I'll do that. No need to ask "what time?" because your virtual model doesn't break your calendar up into hour sized blocks; just into day sized ones.<p>It's a believable theory. There's a great book about this topic (<a href="http://en.wikipedia.org/wiki/A_Man_Without_Words" rel="nofollow">http://en.wikipedia.org/wiki/A_Man_Without_Words</a>) which broadly speaking supports the idea that language is basically a set of representative symbols we use to model concepts.<p>The issues up for debate is really, to what extent does language influence behaviour, compared to, for example, other factors. That's a <i>much</i> harder question to answer (and as yet unresolved I believe).<p><i>Now</i><p>The idea that a programming language can do that?<p>Well.... it's not totally rubbish. I mean, software doesn't exist in the real world; to work on it at all, we have to create a mental model of it.<p>So it's conceivable that our language provides us with symbols to conceptualise the code we work on, and so we'll behave in a different way if we have different models.<p>For example, if you have a deep understanding of assembly and lower level programming languages, your model will have more <i>stuff</i> in it, compared to the model of someone who only knows a high level language, whose model will drop down to 'black box' components when it gets down to a certain level.<p>...and I can totally believe that makes a difference to how you write code.<p>This applies, however, only to <i>concepts</i> (aka. words, aka. representative mental symbols) and <i>not to programming languages</i>.<p>See, this is the issue; your <i>domain knowledge</i> helps inform how you generalize and problem solve. That's what ruby is. It's a knowledge domain.<p>I'm extremely dubious that it provides <i>novel concepts</i> that you don't get from any other programming language. Perhaps in that its a dynamic language it gives a few different ones, to say, that a C++ programmer might have, but broadly speaking:<p><i>You are not a unique and special snowflake because you use ruby</i><p>...or python. or C++. Or scala. Or C. Using a different programming language <i>DOES NOT</i> change the way you think.<p>Learning new words and concepts in your existing language (ie. the one you <i>speak</i>) does that. And sure, using a language with new concepts will teach you those new concepts. Like DI for example, that's a <i>concept</i>.<p>...but "I'm a ruby programmer"?<p>Just go away. You're an idiot.
michaelochurch超过 12 年前
At the risk of being offensive, I've always felt that this "design patterns" cargo cult is Revenge of the Not-Nerds. The people who couldn't hack the harder CS classes because of all the math are striking back with something designed to be as incomprehensible to us as mathematics was to them.<p>Take the Visitor pattern. I mean, really? I already know how to work with trees. Lisp is all about trees. Ocaml lets us build tagged-union types and pattern match to our hearts' content. Do we really need to dickshit the program with <i>Visitor</i>? WTF does that even <i>mean</i>? Who is visiting and why? Is this the French meaning, where to "visit" someone is patronize a prostitute? (In French, you "pay a visit to" someone, or <i>rendre visite à quelqu'un</i>. You don't "visit" your sister.)<p>The design patterns cargo cult is horrible. It has such a "how the other half programs" smell about it that I cannot shake the belief that it was designed to make us Smart People pay for something. Anyway, how can it be "best practices" if I can't REPL the code and make function calls and see how the fucking thing works? If you can't interact with the damn thing, you can't really start to understand it, because it's almost impossible to understand code until you know what you're looking at. IDEs just give people a false sense of security about that.<p>Personally, I like functional programming because it has <i>two</i> design patterns: Noun and Verb. Nouns are immutable data. Verbs are referentially transparent functions. Want side effects? You can have them. Those are a separate class of Verbs: Scheme denotes them with a bang (e.g. set!) and Haskell has them in the IO monad. Real-world functional programming isn't about intolerantly eschewing mutable state, but about <i>managing</i> it.<p>Now, I'll admit that mature codebases will often benefit from some solution to the Adjective problem, which is what object-orientation (locally interpreted methods instead of globally-defined functions) tries to solve. OO, at its roots, isn't a bad thing. Nor is it incompatible with FP. The Alan Kay vision was: <i>when complexity is necessary</i>, encapsulate it behind a simpler interface. That's good stuff. He was not saying, "Go out and build gigantic, ill-defined God objects written by 39 commodity programmers, and use OO jargon to convince yourselves that you're not making a hash of the whole thing." No, he was not.
评论 #5017451 未加载
评论 #5017668 未加载
评论 #5018680 未加载
评论 #5019070 未加载
评论 #5017636 未加载
martinced超过 12 年前
It's an anti-pattern. It's a workaround for a serious language defect.<p>I've written my own DI back in the days (way before Guice existed) but...<p>Using DI is just a glorified way to have "globals".<p>One better solution in TFA's example is to pass a high-order function whose "functionality" is to provide the time.<p>Wanna <i>unit</i> test? Pass in a modified function giving back the time you want.<p>That's just one way to do it.<p>DI is a fugly beast disguised as "good practice" but it really ain't. It complicates the code and is basically a hack allowing to more or less be able to test non-functional code. Really sad.<p>-- <i>"Patterns means 'I've run out of language"</i>
评论 #5017227 未加载
评论 #5017178 未加载
评论 #5017244 未加载
评论 #5017199 未加载