5 ms·
Well, pure DI also has practical downsides. E.g. anything that wants to log something has to be able to access the logging configuration — so it needs to be pas
by dom0 9y ago
Well, pure DI also has practical downsides. E.g. anything that wants to log something has to be able to access the logging configuration — so it needs to be passed into everything.
- breck 9y agoI just pass an "app" object down the tree, and the dependents can do app.logSomething("foo"). So basically just move all globals in the descendants to methods on the root app node.
- matthewmacleod 9y agoThis is also where I always end up with using DI, and it feels like cheating. Basically pretending that these globals aren't.
- breck 9y agoInteresting. How else could DI be done? I'm in the camp that 0 params to a fn are the best, followed by 1, occasionally 2, and almost never ever 3.
- SolarNet 9y agoBut they aren't! If someone comes along and wants to run two copies of the app that share some object they can. And in a super straightforward way. As a maintenance programmer, I love these sorts of things. It's the difference between weeks of work and days.
- weberc2 9y agoI disagree. Nothing gets messy faster than a big bag of state that gets passed all over for no reason. This is no better than the global issue. The correct solution is to inject what you need and build convenience functions to reduce boilerplate. I have serious doubts about your weeks/days quip; my experience is diametrically opposite.
- SolarNet 9y agoTo be fair I more meant a giant tree of state where each module is responsible for managing the state for other modules it communicates with (for example a module will take it's bag of state for most every method; in an object oriented language - a luxury I don't often have - this bag of state is called an object (and methods that don't take it would be called static methods)). Not a flat state object (obviously terrible).
- weberc2 9y agoFair enough, but my beef wasn't about the shape of the large amount of state or its organization, but rather that the whole thing is passed into everything when most things aren't needed. This makes it difficult to reason about what the true dependencies of a function are.
- SolarNet 9y agoYea, but passing a large object around is what I was talking about. Still not great, but when you deal with a 200k LoC library and all of it's API functions take that single large object, it's way better than having to mess with the global state of it. Like in one of those cases I can add naive multi-threading (two copies of the large state object), my own custom functions, and split out functionality for reuse by other systems. In the other I can't without understanding and likely rewriting the whole thing.
- deleted 9y ago[deleted]
- weitzj 9y agoBut aren't you cluttering your "app" object then? Sure, your interfaces are pretty stable now since you pass "app" around. But now "app" leaks all kinds of functions to all callees, which don't really need all functions declared in "app". If on the other hand your "app" object is quite lean, and follows the behavior of maybe Context in Android, then ignore my above comment, and I would agree with you.
- weberc2 9y agoYou solve that with constructors that inject the logger one time, as a matter of convenience. This has all the convenience of a global without the hazards.
- paulddraper 9y agoLogging can be different, and that's okay. It is non-interactive side-effect and irrelevant to the function of your code. It's just for diagnostics, like a debugger. If that is not true, I wouldn't call it "logging" for the proposes here. You could inject a logger, but I don't see a reason to. https://softwareengineering.stackexchange.com/questions/275957/is-logging-next-to-an-implementation-a-srp-violation https://softwareengineering.stackexchange.com/questions/2759...
- sagichmal 9y agoLogging is absolutely not irrelevant to the function of your code. It's a cornerstone of system observability along with instrumentation (metrics) and tracing. It deserves to be unit-tested, same as anything else. Loggers absolutely deserve to be injected and treated the same as any other first-order dependency.
- paulddraper 9y agoEh. You can use gdb for tracing too. I don't see any hard in gloably DI'ed system observability.
- paulddraper 9y ago*harm
- chubot 9y agoI would say that logging and memory allocation are special cases. They're things that need to be done by literally ALL your code. I agree it would be completely silly to change the signatures for a bunch of methods just to print something. But sometimes you absolutely DO want to inject dependencies for things. The Lua VM is parameterized by the allocator it uses, which is useful in practice. And I would distinguish between logging and program output. Logging can be an exception, because it doesn't need to tested. You probably don't want your unit tests to fail if your debug logs change. But maybe in some situations you have some really critical logging, and you want to test that part. Then you can parameterize that code by the logger. Although honestly I would usually just write a shell scripts and invoke "diff". So yeah I don't agree with absolutely pure DI or pure functional programming. But I definitely think the ideas there will help you structure better programs.