The issues are still there. You can't just "go to definition" of the class being injected into yours, even if there is only one. You get the Interface you expect (because hey you have to depend on Interfaces because of something something unit-testing), and then see what implements that interface. And no, it will not just point to your single implementation, it'll find the test implementation too.
But where that "thing" gets instantiated is still a mystery and depends on config-file configured life-cycles, the bootstrapping of your application, whether the dependency gets loaded from a DLL, etc. It's black-box elephants all the way to the start of your application. And all that you see at the start is something vague like: var myApp = MyDIFramework.getInstance(MyAppClass); Your constructors, and where they get called from is in a never-ending abyss of thick and unreadable framework code that is miles away from your actual app. Sacrificed at the alter of job-creation, unit-testing and evangelist's talk-resume padding.
Yes, I can? At least Rider can jump to the only implementation, no questions asked.
> And no, it will not just point to your single implementation, it'll find the test implementation too.
It will, but is it a problem to click the correct class from a list of two options?
var calculator = Substitute.For<ICalculator>();This situation isn't unique when using DI (although admittedly DI does make using interfaces more common). However, that's what the "go to implementation" menu option is for.
For a console app, you're right that a DI framework adds a lot of complexity. But for a web app, you've already got all that framework code managing controller construction. If you've got the black box anyways, might as well embrace it.
Yes, the comments about "$25 name for a 5c concept" ring true when you're looking at a toy example with constructor(logger) { .. }.
Then you look at an enterprise app with 10 years of history, with tests requiring 30 mocks, using a custom DI framework that only 2 people understand, with multiple versions of the same service, and it feels like you've entered another world where it's straight up impossible to debug code.
I remember trying to effectively reverse-engineer a codebase (code available but nobody knew how it worked) with a lot of DI and it was fairly painful.
Maybe it was possible back then and I just didn't know how ¯\_(ツ)_/¯
In a Spring application there are a lot of (effective) singletons, the "which implementation of the variable that implements Foo is it" becomes also less of a question.
In any case, we use Spring on a daily basis, and what you describe is not a real issue for us.
Also, what I think is also important to differentiate between: dependency injection, and programming against interfaces.
Interfaces are good, and there was a while where infant DI and mocking frameworks didn't work without them, so that folks created an interface for every class and only ever used the interface in the dependent classes. But the need for interfaces has been heavily misunderstood and overstated. Most dependencies can just be classes, and that means you can in fact click right into the implementation, not because the IDE understands DI, but because it understands the language (Java).
Don't hate DI for the gotten-out-of-control "programming against interfaces".
It's technically a flaw of using generic interfaces, rather than DI. But the latter basically always implies the former.
If there are multiple implementations it gives a list to navigate to. If there’s 1 it goes straight to it. Don’t know about IntelliJ but rider and vs do this. And if the solution is indexed this is fast.
Edit: upon rereading I realize your point was about reading code, not writing it, so I guess that could be a different use case...
There's nothing wrong with using an IDE most of the time, but building dependence on one such that you can't do anything without it is absolute folly.