6 ms·
Dependency Injection has a fancy name that makes some developers uncomfortable, but it's really just all about making the code easier to test. Basically everyth
by surgical_fire 1y ago
Dependency Injection has a fancy name that makes some developers uncomfortable, but it's really just all about making the code easier to test. Basically everything that the class depends upon has to be informed during construction.
This can be done manually, but becomes a chore super fast - and will be a very annoying thing to maintain as soon as you change the constructor of something widely used in your project to accept a new parameter.
Frameworks typically just streamline this process, and offers some flexibility at times - for example, when you happen to have different implementations of the same thing. I find it funny that people rally against those Frameworks so often.
To make things more concrete, let's say you have a method that gets the current date, and has some logic there (for example, it checks if today is EOM to do something). In Java, you could do `Instance.now()` to do this.
This will be a pain in the ass to test, you might need to test, for example a date when there's a DST change, or February 28th in a leap year, etc. With DI you can instead inject an `InstantSource` to your code, and on testing you can just mock the dependency to have a predictable date on each test.
- ffsm8 1y agoYou're talking from the perspective of Java, which has been designed from the ground up with dependency injection in mind. Dependency injection is the inversion of control pattern at the heart, which is something like oxygen to a Java dev. In other languages, these issues are solved differently. From my perspective as someone whoes day job has been roughly 60+% Java for over 10 yrs now... I think I agree with the central message of the article. Unless you're currently in Java world, you're probably better off without it. These patterns work and will on paper reduce complexity - but if comes at the cost of a massively increased mental overhead if you actually need to address a bug that touches more then a miniscule amount of code. /Edit: and I'd like to mention that the article actually only dislikes the frameworks, not the pattern itself
- mattmanser 1y agoDI wasn't around when Java (or .Net) came out. DI is a fairly new thing too, relatively speaking, like after ORMs and anonymous methods. Like after Java 7 I think. Or maybe 8? Not a Java person myself. I know in .net, it was only really the switch to .net core where it became an integral part of the frameworks. In MVC 5 you had to add a third party DI container. So how can it have been designed for it from the ground up? In fact, if you're saying 10 years, that's roughly when DI became popular. You're wrong about other languages not needing it, yes statically typed languagess need it for unit testing, but you don't seem to realize that from a practical perspective DI solves a lot of the problems around request lifetimes too. And from an architectural context it solves a lot of the problem of how to stop bad developers overly coupling their services. Before DI people often used static methods, so you'd have a real mess of heavily interdependent services. It can still happen now but.its nowhere near as bad as the mess of programming in the 2000s. DI helped reduce coupling and spaghetti code. DI also forces you to 'declare' your dependencies, so it's easy to see when a class has got out of control. Edit: I could keep on adding, but one final thing. Java and .Net are actually quite cumbersome to use DI, and Go is actually easier. Because Go has implicit interfaces, but older languages don't and it would really help reduce boiler plate DI code. A lot of interfaces in Java/C# only exist to allow DI tow work, and are otherwise a pointless waste of time/code.
- ollysb 1y agoThe Spring framework was released in 2003 when Java was at v1.4. From memory it wasn't the first DI framework either.
- layer8 1y agoThe term was coined in 2004: https://www.martinfowler.com/articles/injection.html https://www.martinfowler.com/articles/injection.html It’s not correct that Java was designed for it, unless you want to call class loading dependency injection. It’s merely that Java’s reflection mechanism happened to enable DI frameworks. The earlier Java concept was service locaters (also discussed in the article linked above).
- deleted 1y ago[deleted]
- thiht 1y agoOr don’t use a DI framework, and DI just becomes a fancy name for "creating instances" and "passing parameters". That’s what we do in Go and there’s no way I would EVER use a DI framework again. I’d rather be unemployed than work with Spring.
- surgical_fire 1y ago[flagged]
- thiht 1y agoNo need to be aggressive, I just disagree that DI frameworks streamline anything, they just make things more opaque and hard to trace. > will be a very annoying thing to maintain as soon as you change the constructor of something widely used in your project to accept a new parameter. That for example is just not true. You add a new parameter to inject and it breaks the injection points? Yeah that’s expected, and suitable. I want to know where my changes have any impact, that’s the point of typing things. A lot of things deemed "more maintainable" really aren’t. Never has a DI framework made anything simpler.
- surgical_fire 1y ago> That for example is just not true. You add a new parameter to inject and it breaks the injection points? Perhaps you never worked in a sufficiently large codebase? It is very annoying when you need to add a dependency and suddenly you have to touch 50+ injection points because that thing is widely used. Been there, done that, and by God I wished I had Dagger or Spring or anything really to lend me a hand. DI frameworks are a tool like any other. When properly used in the correct context they can be helpful.
- culturedsystems 1y ago"It is very annoying when you need to add a dependency and suddenly you have to touch 50+ injection points because that thing is widely used" You don't have to update the injection points, because the injection points don't know the concrete details of what's being injected. That's literally the whole point of dependency injection. Edited to add: Say you have a class A, and this is a dependency of classes B, C, etc. Using dependency injection, classes B and C are passed instances of A, they don't construct it themselves. So if you add a dependency to A, you have to change the place that constructs A, of course, but you don't have to change B and C, because they have nothing to do with the construction of A.
- IshKebab 1y ago> Dependency Injection has a fancy name that makes some developers uncomfortable, but it's really just all about making the code easier to test. It's not just a fancy name. I'd argue it's a confusing name. The "$25 name for a 5c concept" quote is brilliant. The name makes it sound like it's some super complicated thing that is difficult to learn, which makes it harder to understand. I would say "dynamic programming" suffers the same problem. Maybe "monads". How about we rename it? "Generic dependencies" or "Non hard-coded dependencies" or even "dependency parameters"?
- kevmo314 1y ago“Global variables that can pass code review”
- layer8 1y agoThe name is confusing because originally [0] it was exclusively about dynamic injection by frameworks. Somehow it morphed into just meaning suitable constructor parameters, at least in some language communities. I like “dependency parameters”. Dependencies in that sense are usually what is called service objects, so “service parameters” might be even clearer. [0] https://www.martinfowler.com/articles/injection.html#FormsOfDependencyInjection https://www.martinfowler.com/articles/injection.html#FormsOf...
- akoboldfrying 1y agoI third "dependency parameters". It concisely describes what the actual thing is, and isn't intimidating. And yes, even though some languages/frameworks allow deps to be "passed in" via mechanisms that aren't technically parameters (like member variables that are just somehow magically initialised due to annotations), doing that only obfuscates control flow and should be avoided IMHO.
- ulrikrasmussen 1y agoI think the "injection" is referring to a particular style of frameworks which rely on breaking language abstractions and modifying otherwise private fields to provide dependencies at runtime. I really, really, REALLY dislike that approach and therefore also dislike the name "dependency injection". Why not call it "dependency resolution"? The only problem frameworks solve is to connect a provider of thing X with all users of thing X, possibly repeating this process until all dependencies have been resolved. It also makes it more clear that this process is not about initialization and lifecycling, it is only about connecting interfaces to implementations and instantiating them. Edit: The only DI framework I have used and actually kind of like is Dagger 2, which resolves all dependencies at compile time. It also allows a style of use where implementation code does not have to know about the framework at all - all dependency resolution modules can be written separately. All other runtime DI frameworks I have used I have hated with a passion because they add so much cognitive overhead by imposing complex lifecycle constraints. Your objects are not initialized when the constructor has finished running because you have to somehow wait for the DI framework to diddle the bits, and good luck debugging when this doesn't work as expected.
- kazinator 1y agoIt's not just about testing. When any code constructs its own object, and that object is actually an abstraction of which we have many implementations, that code becomes stupidly inflexible. For instance, some code which prints stuff, but doesn't take the output stream as a parameter, instead hard-coding to a standard output stream. That leaves fewer options for testing also, as a secondary problem.
- layer8 1y agoThat’s just parameterization, though. It’s overkill to call every parameter (or even most parameters) a “dependency”.
- kazinator 1y agoSo even "dependency parametrization" would be better than "injection". Since what we want to parametrize are objects held by composition, maybe "composition parametrization". "To promote better flexibility and code reuse and to make testing easier, parametrize object composition."
- loevborg 1y agoWhy is it a pain to inject dependencies manually? I think this is because people assume for some reason that a class isn't allowed to instantiate its own dependencies. If you lift that assumption, the problem kind of goes away. James Shore calls this Parameterless Instantiation: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#instantiation https://www.jamesshore.com/v2/projects/nullables/testing-wit... It means that in most cases, you just call a static factory method like create() rather than the constructor. This method will default to using Instance.now() but also gives you a way to provide your own now() for tests (or other non-standard situations). At the top of the call stack, you call App.create() and boom - you have a whole tree of dependencies.
- pdpi 1y agoIf the class instantiates its own dependencies then, by definition, you're not injecting those dependencies, so you're not doing dependency injection at all!
- loevborg 1y agoThat seems too dogmatic. Does the fact that Foo has a static function that knows how to create its dependencies disqualify the class from being a case of DI? class Foo { private nowFn: () => Date; constructor(nowFn: () => Date) { this.nowFn = nowFn; } doSomething() { console.log(this.nowFn()); } static create(opts: { nowFn?: () => Date } = {}) { return new Foo(opts.nowFn ?? (() => new Date())); } }
- physPop 1y agoYes because injecting nowFn is trivial and not a case for DI. Consider a database handle, or network socket, or http response payload. Clearly each class shouldn't be making its own version of those.
- cyanydeez 1y agoYou're nitpicking for no good reason. You can create global handles to each of those items, and let them instantiate with the class or override them with a create function. Dependency injection boils down the question of whether or not you can dynamically change a dependency at runtime.
- layer8 1y ago> Frameworks typically just streamline this process, and offers some flexibility at times - for example, when you happen to have different implementations of the same thing. The very purpose of DI is to allow using a different implementation of the same thing in the first place. You shouldn’t need a framework to achieve that. And my personal experience happens to match that.
- shadowgovt 1y agoI frequently find DI pattern to show up in Java... But I also frequently find that Java gives me all the handcuffs of systems languages with few of the benefits of more flexible duck-typing languages. If you can't monkey-patch the getDate function with a mock in a testing context because your language won't allow it, that's a language smell, not a pattern smell.
- akoboldfrying 1y ago> If you can't monkey-patch the getDate function with a mock in a testing context because your language won't allow it, that's a language smell, not a pattern smell. Not so fast. Constraints like "no monkeypatching allowed" are part of what make it possible to reason about code at an abstract level, i.e., without having to understand in detail every control path that could possibly have run before. Allowing monkeypatching at the language level means discarding that useful reasoning tool. I'm not saying that "no monkeypatching allowed" is always ideal, but it is a tradeoff. (Consider why so many languages have something like a "const" modifier, which purely restricts what you can do with the object in question. The restriction reduces what you can do with it, but increases what you know about it.)
- shadowgovt 1y agoIn practice, we accomplish that by not monkeypatching the production code. But for unit testing? Go nuts.
- akoboldfrying 1y agoSure, a policy like that helps, but relying on programmer discipline like this only scales so far. In a large enough code base, if something can be done, someone will have done it somewhere. (If you have linter rules or static analysis that can detect monkeypatching and runs on every commit (or push to main, or whatever), you're good.)
- arjvik 1y agoYou CAN in fact monkeypatch getDate - look at a Mockito add-on known as PowerMockito! While it's impossible to mock it out in the normal JVM "happy path," the JVM is powerful enough to let you mess with classloading and edit the bytecode at load-time to mock out even system classes. (Disclaimer: have not used PowerMockito in ages, am not confident it works with the new module system.)
- zzo38computer 1y ago> but it's really just all about making the code easier to test. Basically everything that the class depends upon has to be informed during construction. It is useful for more than testing (although, depending on the kind of tests being made, it might not always be useful for all kind of tests). It also allows you to avoid a program having too many dependencies that you might not need (although this can also cause a problem, it could perhaps be avoided by providing optional dependencies, and macros (or whatever other facility is appropriate in the programming language you are using) to use them), and allows more easily for the caller to specify customized methods for some things (which is useful in many programs, e.g. if you want customized X.509 certificate validation in a program, or customized handling of displaying/requesting/manipulation of text, or use of a virtual file system). In a C program, you can use a FILE object for I/O. Instead of using fopen or standard I/O, a library could accept a FILE object that you had previously provided, which might or might not be an actual file, so it does not need to deal with file names. > This will be a pain in the ass to test, you might need to test, for example a date when there's a DST change, or February 28th in a leap year, etc. I think that better operating system design with capability-based security would help with this and other problems, although having dependency injection can also be helpful for other purposes too. Capability-based security is useful for many things. Not only it helps with testing, but also helps to work around a problem if a program does not work properly on a leap year, you can tell that specific program that the current date is actually a different date, and it can also be used for security, etc. (With my idea, it also allows a program to execute in a deterministic way, which also helps with testing and other things, including resist fingerprinting.)
- jen20 1y agoIf it becomes a chore to instantiate your dependency tree with `new` or whatever in the root of your app, it's a good indication that the dependency tree _is too complex_. You _should_ feel the pain of that, to align incentives for simplification. Using an IoC container is endemic in the Java ecosystem, but unheard of in the Go ecosystem - and it's not hard to see which of them favours simplicity!
- surgical_fire 1y ago> instantiate your dependency tree with `new` or whatever in the root of your app Then you are doing dependency injection, just replacing the benefits of a proper framework by instantiating everything at once the root of your app. Whatever floats your boat, I guess. Thankfully I don't need to work on your code.
- jen20 1y agoIndeed, you get the benefits of dependency injection, without the complexity and shit show of a truth-obscuring framework. I think we can both agree that it’s good we don’t need to work with each other though.
- surgical_fire 1y ago> truth-obscuring framework. That you think that a DI framework obscures anything is just a developer smell. Having worked with multiple of those, it was always pretty clear what was being instantiated and how.
- jen20 1y agoWell in that case, every Java DI framework I’ve encountered has that smell. Spring, Guice, all of them. Same in C#. Perhaps it’s an enterprise (in the pejorative) thing. It is interesting to note that these frameworks are effectively non-existent in Go, Rust, Python - even Typescript, and no one complains you can’t build “real things” in any of those languages.
- JyB 1y agoRegarding 'frameworks'. Golang already ships with a framework natively because of the design of the language. Therefore the point is moot in that specific context. Hence the post.