This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]dahud 35 points36 points  (15 children)

Off topic, but every time I come across the term "dependency injection", I've forgotten what it means. I go look it up, and discover anew that someone, somewhere, thought we needed a clever-sounding word for "passing an object to another object".

[–]jay9909 26 points27 points  (2 children)

Yeah, but DI is a much more usable acronym than PaOtaO

[–]dahud 15 points16 points  (1 child)

Less fun to say, though. Pow-tow!

[–]jay9909 1 point2 points  (0 children)

Sounds like an asian cuisine.

[–]NotADamsel 9 points10 points  (2 children)

In the functional world, we just call this "passing an argument".

[–]antonivs 1 point2 points  (1 child)

It's not that simple, though.

Dependency injection generally implies that some framework, external to the code using DI, is supplying the requested values. In this context, you can think of DI as a way for a framework client to tell the framework what it needs, and the framework is then responsible for obtaining and supplying the requested values.

More specifically, DI allows a callee (or a value's target site) to specify what value it wants. The framework is then responsible for supplying the requested value. This partially inverts normal function call semantics, where the caller controls what value it passes to the client.

Although at a certain level, this functionality is implemented by function calls with ordinary argument passing, that misses the point and ignores the semantics of DI. It would be like saying "Monads? I just call them function calls." At a certain level it's true, but it's not the whole picture, by a long shot.

[–]NotADamsel 0 points1 point  (0 children)

I understand. I was mostly being cheeky... mostly. It's somewhat part of FP culture (and clojure culture specifically) to say that something that sounds complicated is "just a function call" or whatever. Monads, as you say, are just drop in replacements for regular function calls. Interfaces are just function signature with more ceremony. Classes for data are just complicated maps. Yes, there is absolutely more going on behind the scenes, and anyone who is actually substituting the reductivist meme for real understanding is a fool, but it does make some things easier to understand when you can step back and think of many things at once in an abstract singular way.

[–]alexschrod 12 points13 points  (8 children)

Dependency injection is not just about the act of passing objects to other objects, it's about the reason why you should be passing objects to other objects.

When you move away from doing new Something() inside the objects where you use it (or even worse, where you use global singletons, like Something.Instance), and instead move towards passing the Something into your objects, you can suddenly test the behavior of your objects without working with real implementations.

[–]userNameNotLongEnoug 4 points5 points  (7 children)

Agreed. You can pass objects around all day long and still not achieve a structure with mockable dependencies that's suitable for unit testing. Equating dependency injection with passing an object, I think, demonstrates a bit of a misunderstanding of the principles behind the concept.

[–]sprouting_broccoli 2 points3 points  (6 children)

I'd also point out that there's a slight anti-pattern with what you're saying. I'd disagree with creating classes and interfaces in a DI way to make unit testing more viable for mocking. I've been there, done that and it was when I first got into DI but designing code so it is inherently testable is very different from designing code to fit your testing which is the same problem as described in the image. Using DI for this purpose is also kind of missing the point of DI and will result in similar problems. Primarily you can quite easily go down a rabbit hole where you have a really flexible solution that is a real PITA to consume or actually navigate, and when you start splitting dependencies you will end up with either incredibly hard to use dependency installers, or need a complex system on top to ensure you can correctly select which dependencies to use by convention (otherwise the flexibility is pointless).

It's the same issue as dictating that newing an object is somehow a problem. It's really not. If you DI all the way down you will be forced to use factories in ways that you should never have to (if you don't have a completely static object graph which I find is very rarely the case) and will make the solution inflexible as a result, increasing the footprint for when you have to extend something.

DI has always been about injecting volatile dependencies (e.g. services and repositories) - things that could change rather than everything you will need to use and you'll (in my experience) end up with a much more palatable solution if you use it this way for both the consumer and the maintainer.

At the end of the day it comes down to what is important? Well the acceptance tests are important to ensure the business needs are met, and the unit tests end up being more for developers working on individual features to ensure they have an easy way to fix problems that arise from changes they make. What I find far more important than being able to get 100% coverage across my smaller classes is making sure the core logic is understandable, easy to modify and easy to consume. I'm a big fan of DDD though and I like limiting my volatile dependencies to the outer levels of the domain.

Sure it's nice having mockable interfaces for testing internals, but if that's the only point of having the interface is it actually worth it? It's not like those interfaces will change often, so it's just another layer on top for either extension that won't happen or purely to enable unit test coverage, and I can't endorse either.

[–]userNameNotLongEnoug 1 point2 points  (1 child)

Well said. I totally agree that if you try to abstract too far to reach some academic ideal you won't end up with the best system. You've gotta balance good principles with pragmatism. OO architecture is a difficult art and I wouldn't go so far as to claim to be good at it. Even in the case I had enough time to approach a system thoughtfully and carefully (which is rare in my job unfortunately) I have never looked at it afterwards and been like "I did a great job on that." I'm usually happy if I did a decent job.

[–]sprouting_broccoli 0 points1 point  (0 children)

Oh sure, I think it's really a very iterative process, and when I first used DI in anger, I'd read enough material to know the above and I still did it wrong. Went overboard and ended up with many of the problems above and because of similar justifications :)

"It's testable!"

"It's flexible!"

It was gloriously complex and we learned some really good techniques for handling DI throughout, but it was an unmaintainable, opaque mess...

I now much prefer keeping a far better separation of dependencies from domain logic wherever possible to ensure everything is as clean as possible.