Hacker News new | ask | show | jobs
by bluefirebrand 1569 days ago
This is actually something I've been trying to talk about with a new hire at my job. We are running a nodejs stack, and he comes from a strict OOP C# background.

I've never worked in a strict OOP paradigm, coming from Python and JavaScript mostly myself. I've been puzzled by his insistence that DI is important. He wants us to implement DI containers and take a "code to interfaces not implementations" approach. To me theres not really any value in this approach in JavaScript.

I don't know Go, but I get the impression this stuff is also not as valuable there.

My question is always "what does this get me and what does it cost me?". Outside of strict OOP paradigms like C# and Java, it's pretty unclear what the benefits are to me.

2 comments

It's not. It saves a few lines of code, with the tradeoff that you are handing some critical functionality--the wiring of dependencies--over to a black box. When things don't behave as you expect (which you might not initially notice), there is no code path to follow, you have to figure out what kind of magic is happening in the black box. It's just not worth the one-time cost of writing a few extra lines of code. This is also congruent with the aesthetic of Go in general.
I've found DI pretty useful when building frameworks, it gives you a sort of generic plugin system. Plus you can hide it from consumers if that makes sense. If you're just building applications I'm not sure it's worthwhile.
The argument I always hear is DI makes your code more testable.

Which is probably true in some cases, but I find most of my code is pretty testable without it. Haven't run into any tests I've wanted to build where I couldn't.

I think that's a side-effect of how code must be written to work with dependency injection, to make the injection actually possible, IMO it's not the DI itself that makes it testable.
Are you sure you are not mistaking dependency injection with dependency injection containers?
How do you test the code which determines when to fire the torpedoes, without actually firing the torpedoes every time you run the tests?
Just fire them and do a ROLLBACK
By using a mock?
How do you convince the when-to-fire-logic to act on a mock, if the torpedo implementation is not provided from outside it?

I'm wondering if I have the terminology understood differently to other people, because to me, DI == IoC. DI Frameworks are build on top of the concept that dependencies will be injected, but doing it explicitly in your start-up code is the same thing to me.

No, you understand perfectly well!

This is the same conversation I had with the new hire I mentioned. And as the other comment here mentioned, it's JavaScript. You simply overwrite the object method with the mock at runtime of the test, then restore it after the test finishes running.

Most JS testing frameworks do this under the hood I believe, without any change in how you write your code. It's a fundamental difference in how JavaScript handles things versus something stricter like C#.

It's a dangerous footgun if you use it carelessly, of course. But a useful language feature when applied carefully.

In languages like JS/Python, you can mock imports. Actually, sometimes you can mock things defined in the same file if you set things up right.

Basically, the logic not being inlined into the function is enough to provide this behavior in certain languages.

This is not true for other languages, which is why DI frameworks tend to be more useful there.

In go specifically, I just always throw the dependencies behind an interface and shove them into a struct. Then you just create a struct of mocks for your testing.

JS allows you to override global objects, pretty much nothing is constant or immutable in JS. You can (more or less) replace arbitrary functions in any place, at any time.