Guilty as charged! I hate using Java because everything written in java seems to blend into the same indistinguishable swamp of classes with meaningless names, full of methods that constantly find new and interesting ways to obscure what your program is actually trying to do. Debugging very large Java codebases feels like living through Terry Gilliam's 1985 film Brazil.
I think the problem is cultural, not technological. It seems like there's a lot of people in the Java community who still think OO is a great idea. Who think Bob Martin's awful, muddled code examples in Clean Code are something to aspire towards. People who claim the path of enlightenment requires them to replace concrete classes with interfaces - even when those interfaces only have 1 implementation.
Anyone who writes class names like FactoryBuilderFactoryImpl and claims to walk in the light is at best a scoundrel, and at worst a follower of a religion so dark we don't name it in polite company.
This is what makes IntelliJ so impressive. It takes a master craftsman to forge something beautiful from such an unholy material. VS Code pulls off the same feat on top of election. In each case I can't tell whether to be horrified or impressed! Either way, I'm a huge fan.
What is the most painful for me with all this, other than it being 99% self-inflicted and not caused (you could argue it's encouraged by it, but the ultimate cause is the culture) by the language, is the fact that it has infected Kotlin code. Kotlin was built to increase expressive power of the language, doing away with multiple limitations of Java and offering lots of modern-ish features on top of it. The community looks split in half: the Kotlin community tries to get the most out of Kotlin, and the Android community that does anything in their power to make Kotlin back into Java, writing code as if the limitations were still in place and new features didn't exist. I know that the churn and "production readiness" of things on Android generally favors a more conservative approach, but it's still too much. I cry tears of blood every other code review I'm forced to do.
If there's a single, definitive resource that I could point my coworkers to and eventually turn it into enforced guidelines, the author can count on a serious donation from me.
I don’t know any good resources unfortunately. I feel like we need a “motherfuckingwebsite” equivalent for this - “just use a motherfucking function”. I want to link it to whoever insisted on adding a useless TextDecoder class in javascript that you have to instantiate, instead of just calling textDecode(…, “utf8”) using a global function like the rest of the standard library.
I think part of the problem is that most people who hate enterprise java just learn a different language, set their resume on fire and start over somewhere better. That’s certainly what I did. I’m writing rust at the moment, and thankfully the lack of classes and distance from the JVM seems to keep most of this nonsense out. But having all the doubters leave makes the problem within the java ecosystem worse.
I for one would rather punch the person who proposes such a global function as the only mechanism for conversion, because charset conversion is a reasonable thing to do on chunked partial inputs, and maintaining the state for conversion yourself is actually quite painful. Wrapping a stream converter into a one-shot function is much easier than the reverse, wrapping a one-shot function in a stream converter.
Lol true
Don't forget all the getters and setters merely updating/reading a variable
Python says "explicit is better than implicit" but Java goes too far with it, and in the most verbose/inflexible ways possible
AS3 and Flex was practically an attempt by these people to take over the language. Thankfully that failed.
TS is great by comparison!
Take his views on "clean code" for instance. Or really any conception of "clean code." Clean code is bullshit. I guarantee you can take anything Uncle Bub or other notable programmers say about clean code, apply them to a tee at your job, and be told that your code isn't clean or "feels icky" by whomever joined the team before you did. No description of clean code that I've read has ever been truly helpful in my career. The only thing you can really do is decide what you think is clean code and for you and your team to reach a level of agreed disagreement so that everyone can get their job done. One person's descriptive variable name is another person's "that's too long I can't read with all these confusing names", and one person's set of short purposeful functions is another person's "I can't tell what's happening cuz I have to jump between all these functions." At the end of the day, you barely have time to write "clean code", because your boss wants features rolled out ASAP.
Uncle Bub is also one of those guys who thinks good code doesn't need comments because it's self descriptive. This is one of the worst ideas to ever have met the software industry. No one's code is self descriptive. It's all a bunch of gobbledygook because it's meant to be run by a computer and just understandable enough for a human. It wouldn't kill us to just write some documenting comments detailing the intention behind code, but sadly most programmers either are too lazy or believe that if they need to add comments then that necessarily means their code smells. The result is that nobody knows anything about any given software project except for those who have been on the project the longest, and even they often don't know because... surprise... nobody wrote anything down! Just like with "clean code", it should be left up to teams how they want to add comments to code, and how much you comment your code shouldn't be influenced by memes from other programmers.
Don't even get me started on FrAgile. It's just a way to dupe programmers into taking on more work and doing the job of middle management for them.
Why don't you write comments describing what the code does?
> and even they often don't know because... surprise... nobody wrote anything down!
Well, read the code
We used to see the same thing with companies that had moved C engineers over to Java. Lots of weirdly overcomplicated C constructs. Meanwhile the newbs who only knew Java were writing FactoryFactoryFactoryImpls. :facepalm
It doesn’t have to be that way (see also Guice).
But the Uncle Bob effect is sadly real.
Foundationdb has official bindings in C, Python, go, Ruby and Java. The real bindings are in C, and all other languages’ bindings are well written, idiomatic wrappers around the same C library exposing the same functionality. The Java bindings need over twice as many lines of code as the Ruby and Python bindings to achieve the same thing.
Even if this style of Java is only 10% less productive than that of idiomatic Kotlin, Go or Python, you will probably break even on the investment of migrating languages after mere months. I think that undersells it. The productivity difference is probably much higher. Especially for large projects.
Improving your personal and teams long term productivity is just about the highest value work you can do.
I disagree. We might understand what I meant by "style"[1] differently. As I think of it, it's comprised of things that have an actual, measurable impact on the effort required to develop the codebase(s) over time. It's not about tabs vs. spaces, snake_case vs. camelCase, or anything even remotely like that. I'm not trying to establish a company-wide set of guidelines for the sake of it - I believe that relatively minor things (in the scope of a single project) can lead to significant savings at the scale of tens of projects and five years of a maintenance window.
As for the guidelines themselves: I don't care what they are, exactly, as long as a) they're there; b) they reduce said effort; and c) they're followed.
At the same time, I've seen the best code of my entire life formed from his concepts. Code that will last decades, far outlasting the UIs that feed it data or the databases that will store it.
I think the difference is all on whether the developers who wrote it understood that the "concepts" are not meant to be put into code on a 1 for 1 basis. For example, making a AddToDoUseCasePresenterInteractor class is literally taking the concept and making it 1 to 1 in the code. On the other hand, writing domain appropriate code, minimizing accidental complexity, and recognizing the clean code concepts as emerging from groups of classes and methods and packages in a code base leads to really clean, testable, maintainable, and FAST TO WRITE code.
I think the single biggest improvement for java programmers is to group all the classes related to a use case together in the same package - which means STOP MAKING "controller", "service", "model", etc packages where every different unrelated except by use is just dumped. If you're working on a part of the code base you should just have to change classes in one single folder. A new feature should just be a new folder. That change alone speeds up teams by huge factors.
Usually there would either be poor isolation between them or they'd be so well isolated that I'd wonder why they were even in the same project. In the latter, they'd often be difficult to maintain because of a web of dependencies pulled in by the little isolated subfeatures.
I prefer the separation by type, tbh. It also has the upside that it naturally encourages the developer to follow the same patterns within that particular package.
This is a neat idea. Have you done this in practice, and how does it work over a long time frame?
One of the big advantages of separate packages is purely for references: The model package has no reference to service or database code, so it's not possible to include SQL or other hidden service calls in it -- at least not without adding a new dependency which makes it blatantly obvious you're doing something wrong.
On the other hand, if your features are self-contained, and you have good unit test coverage of all the logic, then I guess it doesn't really matter as much what the structure is. The fact it's unit tested forces it to be loosely coupled, and testability is one of the main reasons to organize code into layers in the first place.
Makes it super easy to split out microservices when the monolith gets big. Just keep from injecting one Service class into another, rely on the Resource or Client instead.
But there are other solutions. You could probably cook up some linters or some other compile time enforcer.
You can still have effective layers, even classes if you like them. But there's little reason they can't live next to each other on disk in the same project even.
Actually writing demonic technical debt on purpose in one sitting would be quite an accomplishment too.
That describes just about every codebase I've worked with that relies on object-oriented patterns, which is basically every codebase. This is particularly bad with Java, but as you mentioned, this is a cultural problem and not so much a language problem. I like Java the language, but what kept me away from it was every Java codebase I've seen. Layers upon layers of needless abstraction, overly abstract names, and so much code is meant to describe things rather than a sequence of data changing.
It's not that OO is completely wrong, but it's a meme that we as programmers are refusing to shake. It's like people are still getting taught OO in college courses by programmers who haven't worked professionally in decades, and those students are still going into the real world thinking that everything's gotta be object oriented. And usually what OO ends up meaning is having classes and inheritance and "this" and mutability, as opposed to having objects that pass each other information. The latter doesn't need classes, or inheritance, or any of the other similar features in programming languages. But if you've got to write to a file, then you've gotta make a class that wraps the file system functions, right? /s
I’ll never forget working for a tiny startup under an ex Google CTO in 2013 who wrote plain Java code and chose simple libraries. Saying it was a breath of fresh air is an understatement.
Code is culture and culture is code.
If you're coding in Java, you've better think OO is a great idea. It's an object oriented language. And despite having loosely bolted on FP paradigms, that's not really going to change.
Although I feel most of the criticism against OO is actually more like a critique of the FactoryBuilderFactoryImpl-style application of design patterns, which is something else and really unfashionable today in Java.
There's plenty of room in Java for nice, clean code. All "java is OO" means in practice is that your code needs to be in classes. Some things that work great in java:
- Separates out value types from everything else. Value types should usually be tiny, and have public fields. They should not contain references to any other data, and any methods should be simple. ("getTempInCelcius()" is ok, "updateFromDatabase" is not.)
- Express saving / loading / processing work as (ideally) pure functions which operate on that data. You can use the class keyword + static functions to make a module / namespace in java. Use it.
- Use interfaces sparingly. Try not to use inheritance at all.
- (Controversial): If you find yourself with 18 different classes instantiated at runtime, where all those classes have exactly 1 instance (and probably all hold references to each other), you're doing it wrong. Use fewer, larger "controller" style objects (ideally 1), arranged in a tree (no references should point up the tree). If your classes get big, move utility code out into pure functions or short lived utility classes in adjacent files.
- Don't use factories. If you need to, use value types for configuration data.
Is this still "OO"? Depends what you mean by OO. I've heard this style of programming referred to as "data oriented programming", or something like that. Done well, data and data processing often end up in separate files. (Which is the opposite how OO is usually done). Features can often land in separate folders. Performance usually ends up better, because you have less pointer chasing and indirection. You often get much clearer separation of concerns between classes. And its usually much easier to write unit tests for code like this - since almost all classes can be instantiated and tested directly.
A lot of modern game development uses patterns like this, coded in C++. Most C code looks like this too. I've also read and written plenty of javascript / typescript which follows this pattern. Its very simple in JS/TS because you can use object literals (with interface definitions in TS) for your value types. In rust, your large "controller" style classes with a million methods can have all those methods spread out throughout different files in your project. (Ideally grouped by feature.)
Overall the move is toward having data classes (basically records) that are light on logic, as well as logic classes that are light on data. I don't think pure functions are necessary or in many cases even desirable in Java, as it largely lacks the tools required for this not to be crippling; although I will concede that any state changes typically ought to remain local.
Factories can be pretty good for separation of concerns, although in many cases it's been superseded by dependency injection frameworks in modern java. I still reach for it every one in a while though.
Also fwiw, game development is a bit of a weird case. It usually reaches for design patterns like ECS. This is in part a data locality optimization, since you can just iterate through all components in a "straight line", but mostly it's for the sake of malloc, which generally doesn't deal well with billions of objects being allocated and deallocated over and over randomly with growing fragmentation as a result. There are many types of programs this or other game dev patterns aren't suitable for.
I have to say I already expected the comments to be good when I read the title but this nugget of pure gold - I would PAY to read comments like this!
I think having proper sum-types with pattern matching would have also made it a much better language.
https://softwareengineering.stackexchange.com/questions/1495...
Immutable objects often require you to construct a new object to store an updated value and garbage collect the now unused previous object. So a lot of early Java code was written with mutable objects to avoid performance issues.
The Java Bean spec was written in 1997:
https://blog.joda.org/2014/11/the-javabeans-specification.ht...
Aside from immutable instances, it would be nice if 'final' was the default, as well.
Awesome quote ... LOL.
https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...
I'd be interested in reading about it in more detail.
"Rust highly rewards data-oriented design with simple, understandable ownership semantics, and this is great news because this is a really good fit for game development. I suspect this is also true generally, not just for game development! (but what do I know?)"
1) Subclass universality isn't great for cognitive comprehension
2) Subtyping component of subclassing has no compiler enforcement
Okay, for the first point, consider the universal NAND gates. You can build any circuit that uses not, or, and etc logic gates by converting it into only nand gates. This is great for manufacturing (I guess) where you can 'compile' a straightforward series of logic gates into just nand gates and then you only need to product one kind of gate in your hardware. However, imagine if you needed to program this way (say with an imaginary nand (!&) boolean operator):
today || tomorrow => (today !& today) !& (tomorrow !& tomorrow)
Yeah ... just rolls off the tongue.Now, subclassing (and interfacing) provides an existential (to borrow from logic) property. That is, there exists some class such that the following methods exist (and maybe there's also some default implementations spread out amongst every super class in the inheritance hierarchy, but focus on the simple case).
Existential is also universal. You can use it to simulate a forall property (think generics), an or property, or an existential property. The problem is that now you're converting what would be a straightforward structure in a more fully featured language (like unions, or ADT, or Sum types, or discriminated unions) of This thing OR That thing into something much more complicated. It might be an infinite number of things which delegate logic that should exist right here into some other class someplace else where you cannot see it without difficulty or maybe cannot see it at all.
Or you can just cast the super class into a sub class ... which isn't great either.
Regardless, you also have to hope that nobody goes off and implements another case that shouldn't exist because subclassing is open where an OR property in something like discriminated unions is closed. Knowing you have all of the cases considered nigh impossible.
Now, this is good when you straight up want an existential property. It does happen that you get a thing and then you call a method on that thing and there really are an infinite number of things that it could potentially be both now and in the future such that you want support for that behavior. However, I assert that this requires much more complicated and careful coding and isn't applicable for most of the work that ends up needing to occur.
Part two is a bit more simple. When you subclass you're also declaring a subtype. The problem is that there's no compiler support or assistance to ensure that all subclasses are actually valid subtypes of the superclass. But it's a property that you get whether or not it's true.
So at any point in time you can have some object where you aren't supposed to know it's actual class, but for which if you don't know it's actual class you'll end up writing incorrect code. A superficial example can be had with exception (a whole other topic that will otherwise not be covered here). Imagine a Storage class which is just a key-value store. The CloudStorage class can throw NetworkExceptions, the InMemoryStorage class can throw OutOfMemoryExeptions, and the FileStorage class can throw FileNotFoundExceptions. Code that handles just Storage doesn't know which exceptions it might have to make sure it catches. The subclass isn't necessarily a subtype. [Of course you can open up a different discussion here about the appropriate way to handle exceptions, but I hope the simplified example here makes clear what the issue is. A more complex and realistic example can be constructed to show the same issue in a way that completely bypasses exceptions.]
Clean Code makes just as much of a mess in .net land too.
Java is not the only language in existence and the OO is a great idea as well as many other paradigms when not being overused. Problem is with the programmers who learn one language / paradigm and would fight tooth and nails to solve all world problems with it no matter how poor for particular case.
Functions + data (immutable when practical) is all you need for 90%+ of programming, and you should only reach for objects when necessary to manage some well-encapsulated but tricky state. Your default orientation should certainly not be towards objects.
My default orientation should be what my experience tells me is the best for particular case.
I'm sure that's true. You don't have to write classes with names like DoubleFactoryFactory; nobody does that in other languages. It's a tribal thing.
What's amusing (to me) is that Java isn't actually object-oriented, because many of the core datatypes aren't objects.