> intercept and rewrite code at execution time
I think "execution time" is one of the big issues, a lot of these patterns evolved from application server middleware like glassfish that would introspect and run packages and often provide services at runtime, sun and IBM were busy selling licenses and support for these applications servers so these abstractions were great for their bottom line.
Take away the execution time requirement and injects mocks/fakes/stubs at compile time suddenly you don't need interfaces to make mocking easier, static methods are easy to mock and you probably don't need DI/IOC.
You're right that Java had some long class names to start with, but I think it was mostly Spring that introduced the PerhapsOverlyDetailedClassNamingConvention.
Spring's introduction and subsequent wide adoption was a big driver of TDD's growth, which encouraged all those interfaces and mocks.
You can have a bean. Great. You can have a bean that's a singleton. Also great. Then you discover that you can have two different beans that are singletons, and you find that they have one thing in common: how the single instance is created and accessed.
Each such singleton bean will have about five lines of code that deal with the single instance, and these five lines will be largely similar.
So the Enterprise Java Architect sees that there is something to be abstracted, goes to town, and now you can have seven lines of code configuring the abstraction, so that you can avoid writing the five lines of boilerplate.
Unfortunately, the seven lines of configuration will be largely similar for different singleton beans...
Yeah, as a (mostly, currently) java programmer this is where the whole "framework" ecosystem loses me.
Dependency injection - absolutely. Really useful, I can have a setup method that reads config from the environment or wherever, sets up various things and passes them in (you need an SSL context associated to these CAs? Great. And you need one that needs mutual authentication? OK then, here's one I made earlier) . Makes testing easier, and makes it obvious where the setup is being done and how this stuff hangs together.
But 'full' IoC with a framework injecting stuff at runtime? Why? This just obfuscates as far as I can tell, and works around the language, making it harder to conceptualise and reason about.
(Also yes, I don't subscribe to interface-itis).
As it stands, the necessity to relax members to `public` all over the place drives a giant bulldozer through any remaining delusions that Java is a language where actual OOP can be achieved.
But I agree that Java could use better unit test and mocking support.
Mocks usually over-specify implementations by setting up expectations of a specific implementation conversation rather than an outcome. They're painful to debug after refactoring implementation - they end up write-only - and they generally inhibit refactoring of the mocked API - often mocked instances of APIs outnumber production invocations.
I try to encourage people to replace control flow with data flow where possible; messages, command objects rather than method calls; iterators, streams and consumers composed together, rather than loops. Data flow can normally be trivially redirected into a container, and if the data objects are simple inert immutable tuples, they're trivial to construct as inputs or assert against on outputs.
Fakes are good too, better than mocks in most situations, since they are easier to refactor.
Although, IME, integration tests, while slow and often brittle (especially if you have any async components and define failure conditions in terms of timeouts), have a significant upside in permitting large factoring while still being able to test a substantial amount of end result functionality (i.e. the stuff that matters, not implementation details).
Fake: A replacement module that behaves like a production module, but with certain simplifications, for example in-process vs. using rpcs, or simply cleaning up the filesystem after usage. For example, https://github.com/tk0miya/testing.postgresql. "automatically setups a postgresql instance in a temporary directory, and destroys it after testing".
Furthermore, these are external dependencies that can't be run in-process. If a dependency can be run in-process (aka library), there is no justification to ever mock it. I've even seen codebases that mock their own class B in order to test class A. Run the production code already. Ban mocking libraries.
Unit testing with mocks is another form. One does not replace the other.
The argument for static typing is often so that refactoring is easier and ide's work better.
I always thought it was funny that the core reason for needing fancy ide's and large rename and refactor capabilities was that static typing and over abstraction in the first place.
it's funny since in theory TDD should reduce API surface as it is supposed to make people think about API before implementation. But then , TDD and DDD intersect and it's not much TDD anymore when the domain model has to be implemented...
But mostly unit testing. The commitment to isolated testable units of code is what generates abstractions of dubious value.
Don't use a class where you could use an interface: easier to mock. Don't use a static method where you could use an instance method on a collaborator: easier to inject, replace and mock. Your object graph getting too hard to compose, or other design smells? Don't worry, we have tools to handle the complexity, so you don't have to try and reduce it.