A FactoryFactoryFactory in Production
hackernoon.com
hackernoon.com
Yes Java has a lot of problems but they are mostly ones that have workarounds and aren't fundamentally dealbreakers. A few example issues are assignment isn't final by default, @Override isn't required to override methods, you can't have abstract static methods (sort of can workaround this now get this now with default interface methods). Java is basically just a really un-opinionated language that requires careful practices to wield it properly (see Effective Java by Joshua Bloch) but other than that it isn't a completely terrible language and you can write some really robust, easy to read code with it.
I would also have a lot more time for this article if the author actually took the time to explain the motivation behind the *FactoryFactoryFactory class instead of just discounting it without explanation and using it as the proverbial Straw Man for his article. I'm sure something this unusual must have a very interesting reason as to why it exists (I'm taking him literally here and assuming he isn't just exaggerating for comic purposes).
Java for whatever reason seems to attract these ideas.
I can take a compiled .jar file from the late 90s and in quite a few cases I can run it right now unmodified. I can also link against it and use it in Java code pretty trivially. I can do this with modern tools and modern VMs. Then I can copy the result from an x64 box to and aarch64 box and most of the time it will run the same way. That is just amazing.
Java's strength in the areas of robustness and compatibility means it can sustain a lot of complexity. You can build really insanely complicated towers of Babel in Java and they won't topple over. Java's excellent tooling means they'll even be somewhat maintainable.
This transforms Java's great strengths into its great weakness. Its capability to sustain complexity means that nothing checks the tendency of sophomore programmers to over-engineer everything. In other languages over-engineered rococo monstrosities just fall over, but in Java you can actually get away with it to a shocking degree and still ship working robust code.
Try that kind of over-engineering in C++ and you'll have 20 minute compile times for small projects. (I've seen this, so people still try!) Try it in a dynamic language like JavaScript or Ruby and your complex design pattern monstrosity will become a non-deterministic generator of random runtime bugs. These languages can't sustain code that is riddled with FactoryFactoryFactorySingletonFactoryObserverFactory type stuff. Java can, so the Java ecosystem is where the people who love to over-engineer go.
You've got VM tech like polymorphic inline caches, and the kind of speculative devirtualization and subsequent inlining that you can only get from a JIT that'll flatten these towers of complexity at runtime.
And you have a simple static type system that can easily tooled to flatten it on developer mind space as well.
It's the only language for which I've ever met "architects who didn't code". It wasn't backwards compatibility that made that happen.
I think it may be otherway round. Excellent tooling seems essential for Java to succeed. Since standard JDK could not even compile a project without ant/gradle/maven etc. Java has tools to parse/filter stack traces, of course because few 1000 line vomit for a missing file, or wrong jar means one need tools to extract relevant error from stack trace. A 10 level file hierarchy to read some config data means I can't really code without full blown Java IDE.
I'm sorry, but I think you have to defend that statement.
Certainly, gradle and maven do a LOT to make it easier to BUILD java projects, but compile?
These tools do more to apply some common-sense patterns to project source and dependency management, so that you're only specifying the things you really need to, but, like most compilers, javac just needs to know where the compile-time dependencies are and where the source files are. The SDK is quite able to compile without a build tool.
Continued use of Factory patterns doesn't deserve outright mockery, but does deserve immediate and firm correction. It introduced confusion and complexity and ontology to a place where a nullary lambda would do everything it does, and slice away all the ambiguity.
IMO pattern names are merely for communication purposes, like saying "abstract factory" instead of having to say "I have an interface F for creating instances of interface A, where different implementations of F can be chosen at runtime to create different instances of implementation of A". And also you are not required to call your factory "Factory".
I wouldn't call this a knee-jerk reaction, but rather a well-thought out reaction to a few decades of OOP. When I started programming 25 years ago, I felt OO was a misguided idea but I was a novice and couldn't put my finger on it. Now, it's clear to me that all OO does is add unnecessary complexity and simply cannot model real life at all. I don't see a single problem that it solves better than either procedural or functional paradigms. Those paradigms have a lot less boilerplate, are a lot easier to understand, and can be learned much more easily.
Giving precedence to code over data, IMO, is a huge, irredeemable mistake. That's literally the mistake OOP is built on. Data should be a first class citizen in any system, yet it's a second or third class citizen in OOP. It is hidden in objects, processed only by limited certain methods, and generally hard to work with (you can have data only objects, of course, structs, but then you're back to a procedural paradigm). I find myself spending anywhere from 25-75% of my time in OOP languages writing boilerplate to get the right objects talking to each other and the rest writing actual business logic that matters. In something like Clojure, time spent writing boilerplate is around 5% at most. I'm using Clojure simply as one example. I'm sure there are other functional and procedural languages that are also good examples.
http://extradimensional.space/posts/2017-12-18-Deindustriali...
I don't know what it is about Java that makes people write code and name things like this.
This kind of stuff is just foreign to me, and I just don't understand any of the rationale behind it. I hear about it all the time, but can't fathom how code ends up like this.
People give up on the naming things because the name they're seeing makes sense to them in that moment and they may not realize how confused they'll be when they come back to the code in 3 months. May be a lack of experience in maintaining "old code" or just part of the programming culture they're in.
The cause is lack of abstraction and a very limited(rigid, opposite of "flexible") object system. The programmer must use some specific design patterns as workaround and thus name classes according to the semantics of the pattern applied.
If I'm a new developer on a project, I know what a SprocketFactory does, and I know that I'm going to get SprocketFactorys from a SprocketFactoryFactory, and I know that if I call the SprocketFactoryFactoryFactory, I'm sure as hell gonna get back a SprocketFactoryFactory, which will then give me a SprocketFactory, which will then produce Sprockets.
Human short-term memory only has a size limit of about 4 simultaneous chunks so once you're juggling those levels of indirection any development is going to be glacially slow until you've acquired a great deal of experience with the codebase.
Out of interest, can you describe a concrete case where multiple levels of factory patterns have actually been justified and resulted in simpler code than an alternative?
Anything you figure out how to do in these will itself just become un-figure-outable for the next engineer who handles it.
(One of the most frustrating tasks in my engineering career involved trying to debug why Spring beans loaded by a Spring-Tomcat integration layer, in the initialization of a Tomcat container, weren't discovering XML files loaded at another stage of initialization. When I look back on that year I think I might have indirectly changed jobs because of it. Hah.)
The main reason they're used is to create objects without specifying the class of the object, however there are plenty of other reasons why they're common as well.
You start stretching it when you have FactoryFactory. This presumably would be a factory that creates factories. If I saw this in a code review, I would call it out, as it could most likely be refactored to be simpler. FactoryFactoryFactory is beyond silly.
Patterns are things you can avoid to use, they simplify a prior existing problem. But if engineers don't ask themselves what the code would look like if they wrote it differently, a lot of stupid code will be the result. That can be Boilerplate or it can be Spaghetti.
More communication within the team will help.
I also think on the paradigm issue here is Java architects seem more inclined to have a "ravioli code" problem. There's an ancient Java ideal of componentization [1] that a lot of Java code tries to live up to. Lots of little components boxed into tight containers that only ever interact through often "reconfigurable" sauce: ravioli.
There are some benefits to that ideal, it exists because it has some merits in terms of component testing, in particular. It's just that as with spaghetti code and copypasta code, ravioli code also sometimes winds up in a mess where the ideal meets the real world, and somehow a toddler (or junior developer in this analogy?) has thrown it all over the kitchen and you have no idea how to clean it up without destroying the entire kitchen and starting over... May not be a great analogy. ;)
I'd argue that you are more likely to see Java-style ravioli code in C# projects that use IoC containers of one sort or another. Again, there's nothing particularly wrong with IoC containers [2] as an idea connected to some ideals of component testing, it's just that it's an ideal that often falls down and fails to deliver its advantages in the real world.
[1] That somewhat resembles the Smalltalk ideal of object message passing while the language itself lacks much of the architecture for that. Arguably, a lot of Java's worst problems come from trying to replicate a lot of Smalltalk ideas and design patterns in a language that is nothing at all like Smalltalk and misses some important characteristics of Smalltalk.
[2] IoC Containers are a ravioli concept directly imported to C# from the Java world, with the earliest IoC containers such as Spring.NET being Java ports.
There's a software development culture that believes adding a layer of abstraction is the answer (doesn't matter what the problem is) as long as it has a GoF pattern name (doesn't even need the implement the pattern its named for).
For whatever reason they've adopted Java, but if you're going to throw abstractions on top of abstractions carelessly you're going to create a mess of spaghetti no matter the language or environment.
(I think the preference for Java is just an accident of timing of when the culture arose. The responsibilities of corporate IT groups were exploding and they needed a language for all the software they had to create. Java was there and it was a good choice.)
I'm feeling bad about my laughing and complaining, which is useless, so here's some practical advice on restraint when it comes to using abstractions and patterns:
* Don't use an abstraction to wrap something you don't understand well. You may feel like you've tamed a confusing, complicated API and turned it into something you do understand and can use effectively. But you've actually just rolled up all the things to didn't account for and don't understand and hidden them away from view where they will explode if your software actually sees much use. I realize this isn't always easy to avoid doing, but at least never wrap something in an abstraction because you don't understand it that well. And when you're forced to implement an abstraction on top of an API you don't understand well, go through the API, method (or function or endpoint or whatever) by method, and look at all the options and ask yourself what each of those options and methods are there for.
* When considering using a pattern, make sure to check the section titled something like "When Does This Pattern Apply?" and make sure the abstraction you're considering actually solves the problem.
I agree that Factory* is not Java's fault. However I think Factory phenomenon did not start with corporate IT but by people working on core JDK or big open source projects.
I don't have a problem with the factory pattern. I have a problem with piling abstractions and indirection on top of abstractions and indirection to create spaghetti systems.
I don't know how EventFactoryFactoryFactory is being used but it looks like we're dealing with four levels of abstraction simultaneously, which seem likely to me to be a horrible failure of design. (I'm kind of assuming this isn't an event planning system, or something, and that "event" is an abstraction for something happening.)
type EventFactory = () -> Event
type EventFactoryFactory = () -> () -> Event
which seems fairly innocuous. Meanwhile, Haskell programs have gems like type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)