> In Java, as in any language, you can over-design, or you can under-design, or you can design just what you need
One important thing to note with any language is that the culture matters - a lot. For instance, Tcl and Tk let people easily create GUI's, but there was no UI culture. Most of the main books didn't dedicate even a page to how to make a good looking, functional GUI. You can produce some godawful messy code in Ruby if you put your mind to it, but there's a culture of not trying to be too clever, and also of writing stuff that's easy(ish) to read later.
Since I'm not really a Java guy at all, I don't know enough to comment in an authoritative way, but I sneak a glance from time to time, and my guess is that there may be a bit of a culture of over-designing things.
I almost made a similar point but didn't want to be too long-winded. But you're exactly right on. Certain idioms develop in every language. In Java there's a culture of favoring verbosity over cleverness. In fact, I think you'll find some Java programmers who hate the cleverness that is often common in other more terse languages. And they'll throw "clever" around as an insult much the way "verbose" is used against Java.
I think overall these cultures don't indicate too much of an underlying problem and aren't too harmful, except when assimilating developers from other language. But to your point, these barriers to entry likely exist for nearly every language (and if not, other barriers do), so on balance, it's largely a wash.
It's also quite the mystery how this culture happened, given that it's the norm to use big libraries for dependency injection, AOP, transactions management, protocol wiring, data binding and so on, big and complex libraries that many times rely on bytecode manipulation at runtime to workaround the verbosity problem or other deficiencies in the language itself.
I think that the Java ecosystem being so massive and Java being so popular, it also attracted a lot of mediocrity, since the usual bell curve applies. That said, I know Java developers that definitely don't like verbosity and that don't follow the usual "best practices" as some kind of dogma. In the end, it all depends on the work environment you end up in.
> It's also quite the mystery how this culture happened,
(I assume that you are not being sarcastic - my sarcasm detector did not go off. In case I missed the sarcasm, apologies).
It is not at all a mystery, if you had been there in the late '90s / early 2000s : Java was actively marketed to management as a language that's usable by mediocre programmers to produce usable results ; "See, unlike C/C++, there are no buffer overflows, no dangling pointers, no undefined behavior, no platform dependencies, no memory leaks - your projects now are in bad shape because of these, but when you switch to Java, all of these problems will be a thing of the past". And those same people who bought the marketing hype, were also mostly believers in the "programmer is a replaceable cog" religion, which is still prevalent today in many places (not just Java), and during the .com days was actually somewhat truthy in the sense that people switched jobs so fast that you had to build your culture in such a way that people are replaceable, because programmer turnover at many companies was exceeding 20%/year.
Today's Java culture is rooted in those days, and it has an underlying "make it painful to not be stupid-simple" directive, and "make it easy to replace the programmer" requirement. Though these are often satisfied, my impression is that overall, it does not make projects more likely to deliver (on time or at all), or higher quality. But it is what it is.
Your story is about how Java culture developed its anti-intellectualism. I think there's some truth in it.
But i think there's another significant factor. Java came as a successor, of sorts, to C++. Early Java programmers were former C++ programmers, and its designers had C++ experience (as well Smalltalk, Lisp, etc experience). As a result, many features of Java are responses to pain felt in C++. Feeling the pain of memory leaks and use-after frees? Garbage collection. Feeling the pain of segfaults and incomprehensible pointer chains? No pointer arithmetic (the party line for years was that Java had no pointers, only references). Feeling the pain of baffling symbol use in APIs? No operator overloading. Feeling the pain of aeons-long build times? Compilation to bytecode, with linking left to runtime.
I believe this pain response carried over into the culture. The pain in C++ didn't just come from the language, but from the fact that people did clever things with it (template metaprogramming had been invented - discovered? summoned? - in 1994). There was a strong association between cleverness and pain. Cleverness was therefore taboo in the brave new pain-free world of Java.
There was another point in the grandparent, and in the original post, about Java culture's tradition of verbosity. I think this is also a post-C++ thing. In C++, it takes a lot of writing to get anything done. In Java, it takes a bit less. You feel like you're moving faster when you write Java. So, as an ex-C++ programmer who has the endurance to write a lod of code, and is feeling the exhilaration of moving fast, what do you do? Write as much code as possible?
There has certainly been a huge shift back towards concision in Java, but it's not something that's completely permeated the community. There are people today still learning Java from books written by people who learned in the '90s and who internalised that logorrheic style. We'll probably never move entirely beyond it.
> It's also quite the mystery how this culture happened
Maybe the language didn't have powerful tools in its core? What if getters/setters weren't mandatory, like in Scala, wouldn't we write more tiny expressive classes to hold intermediate values?
Think about this example of Dependency Injection: Why did we have to use DI? Because using statics as a dependency injector (like in Play) prevents from unit testing. How could we have solved it? By allowing inheritance override on statics. We don't have it, therefore we have Spring.
Even the Servlet API is criticized for being inexpressive [1]. They've done it so wrong that every single framework reimplements url routing (Struts, Spring MVC...) resulting in those horrible /myserviceaction.do patterns. In turn, those patterns deny RESTful apis, prompting for yet-another-framework for REST resources. We just became Enterprise [2].
I personally have no wonder why we became verbose. But it's understandable, since Java was built using community processes.
Because it's hard to add layers of abstraction later in Java, experienced developers learn to add a bunch of extra ones early so there'll be there if you need them?
No. It is inexperienced programmers that add abstraction everywhere.
Abstraction has a huge mental cost (I ignore the execution speed cost, because it is mostly irrelevant these days for most projects).
You need more than one case to abstract over. Abstracting "just in case" is, much more often than not, very leaky - that is, requiring you to keep in mind the details of both the abstraction and the abstracted - and requires revision when you actually need it.
Abstraction is not magic, not cost free, although Java culture is to assume that it is both. (Same criticism for C++, BTW; C culture tends to err the other way -- which, in my experience, is the better error to make)
>In Java there's a culture of favoring verbosity over cleverness.
I've been programming in Java (gulp) all my professional life of about a decade. I can totally attest to this.
Just couple of days ago in one of the code reviews I gave a lengthy explanation for using StringUtils.equals("foo", someVariable) over "foo".equals(someVariable). I could have instead just said what you've stated; prefer verbosity over cleverness :)
I am java programmer and liked java culture. And I see nothing wrong with "foo".equals(someVariable).
If code review ends with lengthy explanation over why not use "foo".equals(someVariable) or some other maintenance/readability/effectivity/other important consideration non-affecting issue, I see something wrong with code review.
And there's the culture I was talking about! I'm not a fan of stuff that's too weird and magical, but "foo".equals(bar) is shorter and just as clear to me, if not more so.
"foo".equals(bar) is definitely clear and shorter. However it is "clever" in the sense that it circumvents NPE with some sort of trick. Those who are new to Java don't get why and how it avoids NPE. To make matters worse they may even reverse the arguments for better readability, bar.equals("foo") which is a ticking NPE time bomb!
StringUtils.equals(a, b) on the other hand clearly states in its interface document that it is null safe and there's no NPE trap.
There, I said it :). That was my lengthy argument for using StringUtils.equals!
Of course, StringUtils is not in the core API; the first idiom "foo".equals(bar) works in Java everywhere, StringUtils.equals("foo", bar) will only work in projects that pull in org.apache.commons.
Personally I prefer using facilities in the core API over third party APIs, even if the third party API is included anyway for some essential function.
I've been coding in Java for over 10 years. If I ran across pretty much any project that wasn't including Apache commons I'd be startled. I get your point, but some libraries are in such common use that for all practical purposes I consider them part of the language API.
Understanding that there is the possibility of an NPE and reading enough of StringUtils contract to understand that it deals with requires approximately the same level of "cleverness" as understanding the "foo".equals(bar) behavior.
Code reviews should focus on more important things than this, IMO.
But the problem with your use of StringUtils, IMHO, is that it hides intent. If bar is expected to potentially be null, test for null. If bar should not be null, throw an NPE, and the fix the calling code.
Making code that shouldn't need to be "null safe" masks errors, and ultimately makes them harder to track down.
As you may have guessed from my previous comment, I hate it when people write "foo".equals(bar). This kind of a clever trick is an accident waiting to happen unless you're really, really ok with null being a valid value for bar (and more often than not, you're not ok with that). If null is not a valid value for bar, then it's better to catch it and deal with it as early as possible.
By the way, nobody should ever use "foo".equals(someVariable) anyway. If anything, it should be someVariable.equals("foo"),
unless you really know what you're doing and are ok with null being a valid value of someVariable.
> but there's a culture of not trying to be too clever
I love Ruby dearly, but I can't agree with this. There's a lot of being "too clever" in Ruby land. Not in terms of writing fiendishly convoluted but brilliant code (though we do have a nice subset of "code as art", like _Why's Camping micro-framework), but in terms of caring too much about elegance over things like speed and memory usage, until it comes back to bite us.
My pet peeve at the moment is Bundler and Rubygems, which while they are fantastic productivity tools when you start out, has the serious issue that the way they grow the load path means that the time to require a file grows exponentially with the number of gems. 100k+ failed stat calls on startup just to try to be "too clever" about not making people specify more clearly exactly what they are loading is no fun.
Many of these "too clever" things then starts to dictate app design. E.g. if you want to make a command line app out of a large tool that depends on a long list of gems and Bundler, you better think long and hard about making sure you defer all loading as much as you possibly can, or alternatively start up the app in the background and make a small command line app that talks to it over a socket...
For my part, I have a longer list of other Ruby practices I've come to detest than most, probably, as I'm working on a "as static and ahead of time as possible" Ruby compiler, and there's a lot of Ruby idioms that massively complicates things because Ruby developers are used to the malleability of the language, and so will often alter fairly central parts of the language even when there may be conceptually simpler ways of achieving the same that just doesn't look quite as clean.
Again Rubygems is a great example: It replaces "require". That's one of the means it uses to munge load paths. On the surface it was a great thing: it let Rubygems let people pretend Rubygems wasn't there, and yank out gems in favour of manually installing the files if they wanted to. But it came at the cost of contributing to the load path/stat mess I mentioned above.
Some of these things are in fact great no matter what, and exactly why we love our languages. But some of them come with ugly warts that we grow blind to because we understand the underlying reasons for why things have been done the way they have.
I think all languages grow these kind of warts. To me, Java has more of them than most... Java from beginning seems to have grown a culture of ceremony and abstraction that due to the language easily (but not necessarily) ends up with very verbose code.
One important thing to note with any language is that the culture matters - a lot. For instance, Tcl and Tk let people easily create GUI's, but there was no UI culture. Most of the main books didn't dedicate even a page to how to make a good looking, functional GUI. You can produce some godawful messy code in Ruby if you put your mind to it, but there's a culture of not trying to be too clever, and also of writing stuff that's easy(ish) to read later.
Since I'm not really a Java guy at all, I don't know enough to comment in an authoritative way, but I sneak a glance from time to time, and my guess is that there may be a bit of a culture of over-designing things.