I've been working primarily in Go for the past five years, including some extremely complex projects, and I have never once wished I had dependency injection. It has been wonderful. I have used dependency injection - previously I worked on a C# project for years, and that used DI - but I adore Go's simplicity and I never want to use anything else (except for JS for UI, via Electron or Wails for desktop).
Edit: If we're talking about dependency injection in the general sense (separation of concerns, modularization, loose coupling), then yeah I agree that's kind of critical to writing good, maintainable software. When I hear "dependency injection" I think of frameworks such as Unity, and that is what I was specifically talking about - I am very happy with the fact that I have felt zero need to use any framework like that over the last five years.
Go programmer here: What does Go’s simplicity have to do with dependency injection? What does a language itself have to do with dependency injection?
Reading your post and not being personally familiar with your work, I do wonder, perhaps your “extremely complex projects” wouldn’t be so extremely complex if you practiced dependency injection?
How do you unit test your extremely complex projects if your business logic carries the additional responsibility of creating objects?
(I think Go takes all mediocre language features together and makes an even more mediocre language TBH, take error handling for example, or generic programming (which I agree should be used sparingly, but is super useful if you need it))
Btw. why don't interfaces work + "as argument" (whether it's a constructor in an OOP context or a function parameter)? I think interfaces are exactly built for such a use-case (without all the boilerplate that's necessary with dependency injection as well as factories).
What are you talking about? At least in Java and PHP you can absolutely declare constructor and function parameters as interfaces. As you say that's exactly what they're for.
I still haven't really understood the use (and use case) of "dependency injection" (and it feels to me I read now everything about dependency injection I could find), to me it seems to be yet another ProblemFactory.
Single responsibility principle: is your GetData() function responsible for getting data? Or is it responsible for creating a new database connection and also using that to go get the data?
Start naming your functions by what they really do. When you see the word “and” in your function name, you know your function is responsible for too much.
Dependency injection is the difference between CreateDatabaseConnectionAndGetData() and GetData(connection ConnectionType).
In the first example, that function will always connect to the specific db that you hard coded in it. It probably has to also read in a config file to get the connection details. Maybe you should name it ReadConfigAndCreateDatabaseConnectionAndGetData()?
In the second example, I can pass in a MySQL connection or PostgreSQL connection, or some dummy connection for testing.
Keep all that nasty dirty untestable code in one place and spare your business logic from owning all of that.
Generally speaking the use case is writing tests. If your tests just call all the dependencies and new directly then it's harder to write tests for your specific component while avoiding setting up a whole bunch of stuff (to make all those other classes work). By requiring all the dependencies to be provided to the class, you can swap them out at test time for something else that's easier to work with.
That said, IMO it's a symptom of problems in language design. Using DI is only necessary because languages like C# don't make it easy to mock out new or classes used directly, so we resort to wrapping everything in interfaces and factories to avoid those features and replace them with ones that are easier to mock. If the language was designed such that those features were easy to replace during testing then DI probably wouldn't be a thing.