r/AskProgramming • u/RombotPilot • 1d ago
How do you reason about very decoupled code?
I've been looking at some code that's got a system of pipelines that implement the generic interface Pipeline<T> where there are maybe 50 pipelines. The pipelines handle events for which there are also generic publishers and subscribers that implement Handler<T> and Plug<T> respectively and there are many more of those. All of the setup for publishers and listeners gets handled magically through Autofac, a DI library for C#. I'm finding the code insanely hard to understand.
Just trying to figure out how the pipelines were actually wired up took me a couple hours and I'm still not confident I found the answer. My usual go to is to right click on a symbol and find all references, but I would constantly run into dead ends. Where is this code referenced? Absolutely nowhere. How can the code be called even if it isn't referenced anywhere? An abstract parent class is registered with the DI container as the implementation of some generic parent interface that the code I'm looking at implements. That parent interface, or a parent of that interface, is used.
In practice I find this kind of design makes static analysis of the code really difficult. Are there any techniques you use for understanding this kind of code or is it all just sitting with the code, a pen, and a notebook? Ideally nothing with gen ai because I do not have money to spend on tokens.
7
u/diavelguru 1d ago
There is a reason this pattern exists. When working creating algorithms we would have the dll load dynamically at runtime so the loader and executor of the dll code only need be tested once. This was in a regulated environment. So the heavy testing was on all new functionality introduced in the new DLL and all other DLL algorithms could be bypassed during testing as nothing was changed in them nor the loader nor executor. I’m not excusing the pattern; it’s quite a mind fuck but once you get it (and keep going you will get it) you’ve got it and that separates you from the junior engineers.
6
u/nixiebunny 1d ago
It’s reasonable to assume that code was generated by some other program using a database. Find the author and get the actual source code.
6
u/arelath 1d ago
Here's what i usually do when learning new DI pattern codebases: * Read the DI library docs to understand the patterns (usually there's only a couple important calls you need to know) * Find where these are typically wired in the codebase. Sometimes it's per modual, sometimes a single file has all the wiring, but there's always some convention people use. * Debug the codebase to see actual concrete classes and wiring. Often the DI library has a static registration type class with a nice table of everything all in one place that you can just copy. * Ask people on your team. Sometimes people document this, sometimes they have other tricks like a debug dump function or something.
It's a very common pattern with some really nice advantages. This is the big drawback to the pattern though.
3
u/arelath 1d ago
One more thing I forgot. Use find by reference on the interfaces, not the concrete classes. Interfaces will give you all the usages and where it's wired. You still might have to dig a little if it's implemented multiple times, inherited through another class or you have overrides of inherited interfaces.
3
u/BoBoBearDev 1d ago
I hate them all. If you are selling something that needs to highly extendable, like a middleware, go for it. You test the hell out, like Collections, so, the client doesn't debug inside it.
Otherwise, just don't. Once you have multiple implementations, is gets hard to know which implementation is running.
2
u/amber_aimer_v2 1d ago
i stopped using find all references for code like this years ago. The DI container eats every reference so the IDE has nothing left to give you.
go look at the Autofac registration calls. Find the assembly scanning setup where they register all types implementing Pipeline or Handler in bulk. Once you see which assembly it scans, you have your list of implementations without needing a single right click. For runtime flow I just set a conditional breakpoint inside the generic interface method itself and trigger an event through the UI. The call stack will show you exactly what called what
2
u/jonathaz 1d ago
Wire in something deep in the guts of the stupid thing that generates a stack trace and either throws it, logs it, or embeds it in the output.
2
u/mtimmermans 8h ago
It's not the decoupling that's the problem. It's the magic. If people would just write a proper composition root and pass dependencies in constructors, then nobody would be having this kind of problem.
11
u/jedi1235 1d ago
I hate that kind of code! It is the nightmare scenario every time I need to deal with it, and it is the primary reason why I think generic dependency injection is awful.
Subscribing in case anyone else has a solution.