I can't really think of a good example that other people haven't mentioned, but I have an anti-enlightenment piece of software, spring framework.
Spring actively hindered my ability to understand the simple concept that is dependency injection, and I know I'm not alone. Many a Java developer think that you need a big complicated framework to pass dependencies into your modules, instead of having your modules fetch them for themselves.
This isn't a criticism of spring per se, it's fine, it provides value for people, but I think it can lead people to build software that is more complicated and less portable than it needs to be.
I've had my short stint at using Spring. Often I was dropped into a project where it's already setup and working. When something breaks or I want to extend/modify what was working, I hit a wall in terms of discoverability: how it's been working all this time, and how to find a suitable level of documentation to help me. There are reams of documentation for Spring, but nothing of the kind that'll help me if I'm lost. So, from my perspective, it's write-only framework; it's hard to reason back.
I spent a lot of time with spring, and became a bit of an expert on it, I'm able to debug and understand most issues that come up with it.
Still, I agree wholeheartedly, there is too much magic, it's very difficult for someone to come into a spring codebase without a lot of background experience, and understand how things tie together, and I don't think it's really time well spent acquiring that experience.
> Many a Java developer think that you need a big complicated framework to pass dependencies into your modules, instead of having your modules fetch them for themselves.
Not sure what you mean by "module" here, but DI means that the dependencies are defined externally to the actual business logic, which kinda contradicts this "fetch them for themselves".
I think the problem with Spring is that it has too many features, too many different ways to do the same thing, too much configurability. Historically, Spring jumped on most hypes, tried to basically support everything. We ended up with this complexity monster, but this also made it popular. Spring is the Oracle DB of the backend development in the sense that you won't ever get fired for choosing it, it's the safe choice which will support in some way everything you might need to do.
In the context of Java, by "module", I really mean "class" or "object".
> but DI means that the dependencies are defined externally to the actual business logic, which kinda contradicts this "fetch them for themselves".
Yes, that is what I am saying, DI is an alternative to patterns like the singleton class.
For example if you have a singleton for a database connection, and a bunch of services that use it.
The services have a direct reference to the singleton, they "fetch the db connection for themselves".
Compared to DI, a singleton makes it more difficult to mock the connection for tests, also running tests in parallel is more difficult.
If you get a requirement to make the app multi-tenant, refactoring the application to talk to multiple databases is more difficult.
I thought that you were hinting that DI can be done (esp. on a smaller scale) often manually without any complex container magic.
Like in a smaller app, construct all your service instances in the main method while passing the dependencies into constructors and voilà - you have a DI based application without a container / any framework.
> I thought that you were hinting that DI can be done (esp. on a smaller scale) often manually without any complex container magic.
I also agree with this, and at any scale.
Actually I think the larger scale the codebase, the more it would benefit from manual DI, obviously at that point, the wiring code grows and you would probably break it down into multiple methods.
Dependency injection is a basic tool of writing robust, testable code. The alternative is strict hard-wiring of the dependencies, which deprives you of places your code can be tested.
But do not confuse "dependency injection" with "massive heavyweight opaque framework with a billion bells and whistles that breaks constantly". Dependency injection includes things like passing in a handle to the SQL database instead of it being a global variable, which your test suite uses to switch between various test instances of the database instead of the target code being hard coded to some variable, or even hard coded with its own connection credentials.
If you're not using dependency injection you are almost by definition using a lot of global variables. I'm as happy or happier than the next programmer to be a contrarian, but, no, the collective wisdom on those is dead on. They're deadly and should be avoided. Dependency injection is the biggest tool for that.
Not having dependency injection creates more problems than it solves.
However, I'm not sure that most "frameworks" for it aren't creating more problems than they solve. Probably one of the classic examples of lopsided accounting, in this case looking at the benefits but neglecting the costs. Anything looks good if you do that. But a lot of "frameworks" seem to bring along a suite of middling, sort of convenient benefits at the cost of massive opacity, unreliability, and the bad kind of magic. Not a good trade for a lot of programs.
Dependency injection adds a lot of complexity even without a framework, and it has nothing to do with global variables, you can use global variables with dependency injection, and you don't have to use dependency injection to avoid global variables.
Dependency injection has to do with coupling. It is a weak coupling technique, and the point is to make components interchangeable. That's also the reason why it is considered good for testability, because it makes mocking easier: just replace the functional component with a test component. It sounds nice, but of course it has downsides, lots of them.
First is that is simply more code. Instead of calling B from A, you have to wrap B in an interface, instantiate it, pass it to A, and make the call. On small components, it can easily double the amount of code, or more. Of course, mechanically, more code means more development time, more things that can go wrong, more maintenance costs, etc...
You also lose track of what happens. When you call B from A, to see what is happening, it is obvious just by reading the code of A that you should look at B. Using dependency injection, there is no way to know what is called by just looking at A, you need to figure out what is instantiated first, which is not always an easy task.
Of course, there is performance too. Dispatching is expensive, typically requiring two or more memory reads to call a function. And all these little objects can take up valuable space in L1 cache.
Now, there is the argument of testability, ok, testability is good, but in many cases, there are ways of testing things even with a strongly coupled architecture. For instance, if you have a configuration file reader, you don't need to abstract it to write tests, just have test configuration files that are read by the reader. It saves you an abstraction and a bunch of dedicated test code.
Dependency injection has a place. Unix file descriptors are a kind of dependency injection and very few complain about them. It is just that it is an expensive pattern that should be used wisely and not all over the place.
> Instead of calling B from A, you have to wrap B in an interface, instantiate it, pass it to A, and make the call.
There is a place for interfaces, but not all dependencies need to be interfaces. The database handle mentioned in the previous comment is a good example of where dependency injection can be useful without the need for interfaces. In your configuration example, a string path to the configuration file might be the useful dependency.
One might about as sensibly say the same of functions being able to take arguments. If this is meant to illustrate the damage working with Spring does to understanding the concept, then it's an excellent, if mildly horrifying, illustration.
One elementary need DI (or perhaps IoC more generally) provides is the ability to mock certain parts of your application for automated tests. While I'm not a fan of mocking too much and prefer higher-level tests (integration/component), mocking is still quite often needed. Is there some alternative to IoC/DI?
Are you pro DI as in "Dependency Injection", or pro DI as in "Dependency Inversion Principle"?
DIP is a good way to build software. When injecting dependencies becomes so complex you need a framework or need a separate concept of DI (sans P) then I think something has gone wrong, incidental complexity has won.
I mean the original statemement from the perspective that only certain languages/environments (Java, etc.) propose DI as a solution. E.g. in my current language of choice, C++, DI is nowhere to be found.
Totally agree. Spring and actually everything in that realm is plain horrible. DI is awesome on it's own (especially if you do hexagonal architecture), but spring hides behaviour and brings in undocumented changes on updates and things like that (same for any spring related project and hibernate). That's my biggest problem with it.
Spring actively hindered my ability to understand the simple concept that is dependency injection, and I know I'm not alone. Many a Java developer think that you need a big complicated framework to pass dependencies into your modules, instead of having your modules fetch them for themselves.
This isn't a criticism of spring per se, it's fine, it provides value for people, but I think it can lead people to build software that is more complicated and less portable than it needs to be.