Edit: a-ok, "cowboys" is part of name of that guy's consulting business
Edit: a-ok, "cowboys" is part of name of that guy's consulting business
but in the long run, the problem is also the business logic. What disappears is the knowledge of the hacks that were applied at the business logic level. And this is much harder than to recover from the code. So I guess one has to make the difference between business logic maintenance and the actual COBOL maintenance.
But having rewrote some huge COBOL app in Java, I can say that Java is better structured, at a framework complexity cost.
I suspect that part of the reason that this works well, is that Java and Kotlin isn't that dissimilar. Doing automated conversion between Java and Scheme for example, would be a lot harder to get right.
J2EE usually means GC pauses (hell), XML (hell again) and dependency injection (hell also). Thankfully, hells tend to pay well if someone is willing to schlep the muck around quickly and reliably.
JVM (IBM's, Oracle's) and CLR runtimes are battle-tested and much more widely deployed to run civilization than most non-enterprise developers realize.
Also, I should probably look that guy up as I'm in North Texas at the moment.
I just had to go through an open source project that uses guice, in order to try to trace a particular code path, since the user group was less than helpful.
Trying to figure what was running was an absolute nightmare of nested magic spread out through a score of files. Meaning as soon as I had a clue, I'd lose it as I had to retrace back through all those files.
I'm sure it's clear as crystal to the people who wrote it, but DI can be obtuse as hell if you have to figure what's actually running on your own.
e.g. instead of
void foo() {
SingletonLogger::logError("no cheese");
}
you'd write void foo(ILogger logger) {
logger.logError("no cheese");
}
Now it's much easier to unit test `foo` with a mock logger object.Is there more to it than that? Why are there big, complicated frameworks for it?
hand rolling DI, yep brilliant, generally quite easy to work your way through because it's easy to see where classes are being used.
Hand rolling can get complicated when your applications get BIG and you want to do fancy things like having modular code (whether you ever use the module somewhere else or not) or lazy loading or multiple scopes.
Then you use a framework and you find your dependencies take dependencies, your wireup code is auto-magical, you inject dependencies into private properties, you want multiple implementations of particular interfaces but want to ensure only the right one gets used in each circumstance.
Then multiple people start getting their fingers into the pie and the DI/IoC explodes into config files throughout the application, and the only way to understand what's being wired up without a map, is to "find usages", pin files, and cry
(I'm not bitter or anything ;) DI/IoC can work wonders for handling complexity, it can also increase your complexity if you aren't very careful)
Another easy way to find out what is injected at runtime is to attach a debugger and set a breakpoint.
Once I finally discovered a hint about which files governed the behaviour I was investigating I was finally able to isolate the tests to begin the debug cycle.
Sometimes it's just complicated.
@GetMeALogger(andCallIt=loggy)
void foo() {
loggy.logError("no cheese");
}
Where @GetMeALogger does some magic at compile time to pipe in some kind of logger class based on various config options. Then to unit test it, you use different config than production.Spaghetti code would not pass code review, so why would you allow spaghetti configuration.
In the code bases I work in I know if I need to know what Logger is injected I need to look at the LoggerConfiguration class to see which Logger implementations are used in production, staging and dev.
And even if I don't know the name of the LoggerConfiguration file, I have an IDE that can get me from injected property to configuration in one click.
That would be a fun text adventure....
This can depend on several things like configuration properties, environment variables, libraries loaded, feature flags enabled, etc
edit: also, you don't always want to manually pass the argument to every function. At application startup you typically want to build a full graph of objects with all the dependencies "wired up". The big complicated frameworks (Spring for example) read in a bunch of configuration, resolve all dependencies, and then construct the graph for you.
This - in general - is the problem with programming.