This is one of those criticisms that sounds right if you know a little bit about the topic, and is certainly clever enough in and of itself to make the reader think that it's making a good point.
But it's not making a good point. It's not even close. I wish people would stop voting it up.
- "In Java, you can forget about doing it in the cleanest or the best way, because that is impossible." -- cleanest in any language? Or cleanest for Java? Of course the "cleanest" way for Java is possible, and it does matter -- there is a huge difference between "clean" Java and unclean, and anyone who says otherwise is a Java pretender. And arguing about "cleanest for any language" is just a proxy for a language flamewar.
- "And nobody can glare at you and demand to know why you used 576 classes when you should have used 50, because in Java doing it with only 50 classes is probably impossible." -- The "Har-har-so-many-classes-and-don't-even-get-me-started-on-variable-names" argument. In Java, as in any language, you can over-design, or you can under-design, or you can design just what you need. The number of classes may end up higher than in other languages, but this argument is silly and tired, and yes, length is one of the tradeoffs of Java. An IDE (gasp!) should make that largely irrelevant.
- "The code was ridiculously verbose, of course, but that was not my fault. It was all out of my hands." -- This word "verbose" keeps being thrown around as an insult, but nobody brings up the tradeoffs. Verbosity has a purpose, just like brevity and terseness do. And of course it's never out of the programmers' hands. Once given a language, it's all relative.
There's more, but I'm arguing with a person who believes that a question about stdin and stdout is a proper gateway for measuring skill toward any programming problem ever.
I took his post as a fairly standard "worse is better" argument. Many newer or more fashionable languages enable some very elegant programming styles, but this comes with a concern about the elegance of one's code, which can easily result in the programmer spending more time thinking about elegance than about functionality. For example... should I use map or reduce here, or maybe an iterator, or... oh fuck it, I'll use a for loop. It turns out that the old-school for loop works just as well despite earning you precisely zero style points.
There's actually something quite liberating about languages that deny you any clever solutions. Just write code that works and don't worry about whether you could have used <fashionable programming concept X> instead. I found Go to be quite refreshing for the same reason - the standard library is pretty small and the language lacks anything particularly magical, even some stuff that Java has like generics. The end result, however, is code that is very easy to read and write for anyone who learned imperative programming in the last 30 years.
> Many newer or more fashionable languages enable some very elegant programming styles, but this comes with a concern about the elegance of one's code, which can easily result in the programmer spending more time thinking about elegance than about functionality.
That's a common anti-intellectual fallacy about why people code in higher level languages. High level languages are attractive because they let you write code faster and make your code easier to maintain, and not because they're "fashionable".
>There's actually something quite liberating about languages that deny you any clever solutions
To me, "deny[ing] clever solutions" translates to "this code is going to be a pain in the ass to write because I'm going to waste time fighting the language to get what I want." Programmers learn to avoid unnecessarily complicated code as they gain more experience. Mature programmers should have the freedom to exercise their judgement about what kind of code is appropriate.
> That's a common anti-intellectual fallacy about why people code in higher level languages. High level languages are attractive because they let you write code faster and make your code easier to maintain, and not because they're "fashionable".
Writing code faster is not my problem. The problem is building systems (that work) faster and this need sometimes requires using higher-level abstractions that maybe allow for more modularity, that maybe are more composable, or maybe are safer to use (and I'm specifically thinking here of concurrency, parallelism, asynchronous I/O, etc...). Higher-level abstractions is my goal when learning a new language (this or gaining access to another platform).
"clever solutions" is indeed an unwarranted euphemism that people use. But we are ending up having such discussions, precisely because we aren't defining well what we are talking about. Writing less code is a subjective problem. Not being able to build and/or work with a certain abstraction is an objective problem.
>High level languages are attractive because they let you write code faster and make your code easier to maintain, and not because they're "fashionable".
Maybe, maybe not. But at least 2 of the 3 points that you made are not true of JavaScript. Also, the whole "write code faster" has always been perplexing to me. The speed at which you write code is never significantly different between any two mainstream languages to really matter in the end, especially since the lifetime of a piece of software is dominated by maintenance.
And I don't know what you mean by "High level language". Java is a high level language.
>To me, "deny[ing] clever solutions" translates to "this code is going to be a pain in the ass to write because I'm going to waste time fighting the language to get what I want."
"Also, the whole "write code faster" has always been perplexing to me. The speed at which you write code is never significantly different between any two mainstream languages to really matter in the end, especially since the lifetime of a piece of software is dominated by maintenance."
One of the very few bits of relatively solid software engineering that we have is that line count for a given task does matter. Fewer lines written by the programmer to do the same thing strongly tends to yield higher productivity. (Note the "by the programmer" clause; lines autogenerated... well... correctly autogenerated tend not to count against the programmer, which is an important part of doing Java, or so I hear.)
And remember, if this were not true, we'd be programming entirely differently; why do anything but assembler if line count doesn't matter? You might be tempted to thing that's some sort of rhetorical exaggeration, since it sort of sounds like one, but it's not; it's a very serious point. If line counts are actually irrelevant, then we'd never be bothering with high-level languages, which up until fairly recently have the primary purpose of doing a whole bunch of things which, in the end, reduce line count.
(Slowly but surely we're starting to see the mainstream introduction and use of languages that also focus on helping you maintain invariants, but that has still historically speaking been a sideline task and niche products.)
> Note the "by the programmer" clause; lines autogenerated... well... correctly autogenerated tend not to count against the programmer, which is an important part of doing Java, or so I hear.
Sorry, but unless you have an architect dissect the problem to exaustion and freeze the architecture after it, no piece of software creates the correct autogenerated code, and those lines still have to be changed by the programmer. Several times.
And if you have an architect dissect the problem to exaustion and freeze the architecture after it, that's already a bigger problem than dealing with all that autogenerated code. No win.
In theory, I have a citation. There have been actual studies done that show roughly equal productivity in several languages as measured by lines of those languages. However, I can't google them up through all the noise of people complaining about line counts being used for various things. And I phrased it as "one of the very few bits of relatively solid software engineering" on purpose... that phrase isn't really high praise. You can quibble all day about the precise details, not least of which is the age of the studies in question.
Still, I do stick by my original point... if you think lines of code are irrelevent, it becomes very difficult to understand the current landscape of language popularity. A language in which simply reading a line from a file is a multi-dozen line travesty is harder to use than a language in which it's two or three, and that extends on through the rest of the language. I know when I go from a language where certain patterns are easy into a higher B&D language where the right thing is a lot more work, I have to fight the urge to not do the lot-more-work, and this higher level "how expensive is it to use the correct pattern?" is a far more important, if harder to describe, consideration across non-trivial code bases.
1) How do you count "expressions"? is (b+sqrt(b * b - 4 * a * c))/(2 * a) one expression or 14?
2) Assuming reasonable coding style and reasonable definition for what an "expression" is, the variance of the measurement "expressions per line" will be very small - thus, "number of expressions" and "number of lines" are statistically equivalent as far as descriptive power goes.
I don't have a citation, although I do remember this conclusion mentioned in PeopleWare - specifically, that "number of bugs per line" tends to be a low variance statistic per person, with the programming language playing a minor role. I might be wrong though.
But I can offer my personal related experience ("anecdata"?) - when you ask multiple people to estimate project complexity using a "time to complete" measure, you get widely varying results that are hard to reason about. However, when you ask them to estimate "lines of code", you get much more consistent results, and meaningful arguments when two people try to reach an agreement. YMMV.
I feel like you probably haven't coded IO in a language like c# (in earlier versions anyways) or Java if you think I'm playing semantics.
Expressions are distinct from Compositions, and both influence LOC. I wouldn't suspect that Java software is of generally lower quality than Ruby code on average for example even though in Java you might see a Reader around a Buffer around a Stream instead of Ruby's `open`.
I guess what I'm getting at is what you might loosely call boiler-plate. Java has a lot more boiler plate. Which could easily result in 2X higher LOC. Having worked with more Ruby than the average bear I feel very confident being skeptical of the assertion that Ruby libraries are generally of higher quality/fewer bugs.
I think your last anecdote is more getting into Five Whys territory, and it's probably reasonable to expect a greater degree of consensus then.
Final note: Scala is typically less verbose than Ruby by a fair margin (at least if you leave off imports). Idiomatically usage is also Functional to a significant degree in a way that no Ruby library I've ever seen comes close to. So does that automatically mean that Scala is the superior language? (Well of course it is ;-D, but is that the reason?)
The question is simple, and it's about math and statistics.
How do you count lines? On unix, "wc -l"; if you insist, sloccount, but "wc -l" is a good approximation.
How do you count expressions? The fact it will take you a few paragraphs to answer (you haven't, btw) indicates that it's a poor thing to measure and try to reason about.
I've done some IO code in C# (mostly WCF, bot not just), and I still think you are playing with semantics as far as statistics is concerned.
Figure out an objective, automatable way to count your "expressions" or "compositions" or "code points" or "functional points" or whatever you want to call it. Run it on a code base, and compute the Pearson r coefficient of correlation. It's likely to be >95%, which means one is an excellent approximation of the other.
And I have no idea what you were trying to say about Scala. I wasn't saying "terser is automatically better". I was saying, (and I'm quoting myself here: "number of bugs per line" tends to be a low variance statistic per person, with the programming language playing a minor role"). Note "per person"?
So backwards first I guess. "per person". Ok. But given the range of programmers I guess that's not an incredible surprise. Yes the person is more important than the language. I'd buy that.
I guess "expression" seems semi-obvious to me since it's a standard rule in SBT. Variable assignments, return values and function bodies might get close.
val a = 1 + 1
That would be an expression. Instantiating a DTO with a dozen fields, using keyword arguments and newlines between for clarity would be a single expression to me.
An if/else with a simple switch for the return value would be an expression for example. A more complex else case might have nested expressions though.
It takes some charity I suppose; one of those "I know it when I see it" things. I don't do a lot of Math based programming though. It's all business rules, DTOs, serialization, etc. So maybe not something that could be formalized too easily.
I guess where I'd intuitively disagree (and would be interested in further reading) is that LOC as a measure just doesn't feel like it works for me.
Considering only LOC to implement a task it's likely: Java, Ruby and Scala in that order (from most to fewest). But in my personal experience bugs are probably more like: Ruby, Java, Scala from most to fewest.
Hopefully that helps clarify and not just muddy what I'm trying to express further.
What confuses me is that you appear to be claiming that fewer LOC should correlate strongly with fewer bugs, but then go on to say that terser is not automatically better (in this context (sic?)). Maybe I'm reading more into it than you intend, but I'm left a bit confused.
Which is a confusing use of the term "expression", since it is very well defined when talking about languages - in fact, most formal grammars have a nonterminal called "expr" or "expression" when describing the language.
Your description, though, more closely correlates with what most languages consider a statement.
Regardless, it's just pure statistics - if you calculate it, you'll notice that you have e.g. 1.3 expressions per line, with a standard deviation of 1 expressions per line - which means that over 1000 lines, you'll have, with 95% confidence, 1200-1400 expressions -- it wouldn't matter if you measure LOC or "expressions".
> What confuses me is that you appear to be claiming that fewer LOC should correlate strongly with fewer bugs, but then go on to say that terser is not automatically better (in this context (sic?)). Maybe I'm reading more into it than you intend, but I'm left a bit confused.
What I'm claiming is that, when people actually measured this, they found out that a given programmer tends to have a nearly constant number of bugs per line, regardless of language - that is, person X tends to have (on average) one bug per 100 lines, whether those lines are C, Fortran or Basic - the variance per programmer is way larger than the variance of that programmer per language.
Now, PeopleWare which references those studies (where I read about that) was written 20 years ago or so - so the Java or C++ considered wasn't today's Java/C++, things like Scala and Ruby were not considered. However, I'd be surprised if they significantly change the results - because those studies DID include Lisp, which -- even 20 years ago -- had everything to offer that you can get from Scala today.
So, in a sense - yes, you should write terse programs, regardless of which language you do that in. If you wrote assembly code using Scala syntax, and compiled with a Scala compiler - Scala is not helping you one bit.
No, his point is that you don't have to fight the language. The course in the language is obvious, it's just long and kind of ugly. It's wading, not fighting.
My problem is that the solution in a slightly more expressive language is equally obvious, so I have to fight my own rage at java for its stuttering and clumsy excuse for closures. Fighting the language, on the other hand, is when I try to write a conditional or loop in sh.
> Many newer or more fashionable languages enable some very elegant programming styles, but this comes with a concern about the elegance of one's code, which can easily result in the programmer spending more time thinking about elegance than about functionality.
This isn't a bad tradeoff though. Elegance means generally gains in maintainability, possibly with lesser costs in the actual development. And thinking about elegance in code is the first step to writing better, more maintainable code.
> For example... should I use map or reduce here, or maybe an iterator, or... oh fuck it, I'll use a for loop. It turns out that the old-school for loop works just as well despite earning you precisely zero style points.
I usually stick with for loops because they are clear. Remember:
elegance is about simplicity and clarity. If you sacrifice either of these, you are reducing the elegance of your code.
The "worse is better" argument is in the context of Unix and C and cannot be separated from that context, otherwise it is meaningless.
And a lot of thought went into Unix, as evidenced by its longetivity and long lasting tradition of its phylosophy. To date it's the oldest family of operating systems and at the same time, the most popular. Anybody that thinks the "worse" in the "worse is better" argument is about not carrying, is in for a surprise: http://en.wikipedia.org/wiki/Unix_philosophy
Even in the original comparisson to CLOS/Lisp Machines outlined by Richard Gabriel, he mentions this important difference (versus the MIT/Stanford style): It is slightly better to be simple than correct.
But again, simplicity is not about not carrying about design or the implementation and in fact the "worse is better" approach strongly emphasises on readable/understandable implementations. And simplicity is actually freaking hard to achieve, because simplicity doesn't refer to "easy", being the opposite of entanglement/interwiving: http://www.infoq.com/presentations/Simple-Made-Easy
"Worse is better" can easily be separated from that context, though I would admit that most people do it incorrectly.
"Worse is better" is, ultimately, an argument against perfectionism. Many of the features of Unix could have been implemented in a "better" way, and these ways were known to people working at the time. But it turns out that those "better" options are much more difficult to implement, harder to get right and are ultimately counter-productive to the goal of delivering software that works. We can set up clear, logical arguments as to why doing things the Unix way is worse than doing things another way (e.g. how Lisp Machines would do it), but it turns out that the Unix approach is just more effective. Basically, although we can invent aesthetic or philosophical standards of correctness for programs, actually trying to follow these in the real world is dangerous (beyond a certain point, anyway).
I think that's pretty similar to the OP's argument that, whilst Haskell is clearly a superior language to Java in many respects, writing code properly in Haskell is much harder than doing so in Java because, probably for entirely cultural reasons, a programmer working with Haskell feels a greater need to write the "correct" program rather than the one that just works. Java gives the programmer an excuse to abandon perfectionism, producing code that is "worse" but an outcome that is "better".
I think I know what you're getting at, which is that a comparison between Unix and the monstrous IDE-generated Java bloatware described in the OP is insulting to Unix. On this you are correct. But for "worse is better" to be meaningful, there still has to be some recognition that, yes, Unix really is worse than the ideal. Unix isn't the best thing that could ever possibly exist, it's just the best thing that the people at the time could build, and nobody has ever come up with a better alternative.
I do not agree. "Worse is better" emphasizes on simplicity - and as example, the emphasis on separation of concerns by building components that do one thing and do it well. It's actually easier to design monolithic systems, than it is to build independent components that are interconnected. Unix itself suffered because at places it made compromises to its philosophy - it's a good thing that Plan9 exists, with some of the concepts ending in Unix anyway (e.g. the procfs comes from Plan9). And again, simplicity is not the same thing as easiness.
> Haskell is clearly a superior language to Java in many respects, writing code properly in Haskell is much harder than doing so in Java
I do not agree on your assessment. Haskell is harder to write because ALL the concepts involved are extremely unfamiliar to everybody. Java is learned in school. Java is everywhere. Developers are exposed to Java or Java-like languages.
OOP and class-based design, including all the design patterns in the gang of four, seem easy to you or to most people, because we've been exposed to them ever since we started to learn programming.
Haskell is also great, but it is not clearly superior to Java. That's another point I disagree on, the jury is still out on that one - as language choice is important, but it's less important than everything else combined (libraries, tools, ecosystem and so on).
I think Worse is Better can be used by either side. You seem to be on the "Worse" side, ie. the UNIX/C/Java side, and claim the moral of WIB to be that perfect is the enemy of good. That's a perfectly fair argument.
However, on the "Better" side, ie. the LISP/Haskell side, the moral of WIB is that time-to-market is hugely important. It's not that the "Better" side was bogged-down in philosophical nuance and was chasing an unattainable perfectionism; it's that their solutions took a bit longer to implement. For example, according to Wikipedia C came out in '72 and Scheme came out in '75. Scheme is clearly influenced by philosophy and perfectionism, but it's also a solid language with clear goals.
The problem is that Scheme and C were both trying to solve the 'decent high-level language' problem, but since C came out first, fewer people cared about Scheme when it eventually came out. In the mean time they'd moved on to tackling the 'null pointer dereference in C problem', the 'buffer overflow in C' problem, the 'unterminated strings in C' problem, and so on. Even though Scheme doesn't have these problems, it also doesn't solve them "in C", so it was too difficult to switch to.
Of course, this is a massive simplification and there have been many other high level languages before and since, but it illustrates the other side of the argument: if your system solves a problem, people will work around far more crappiness than you might think.
More modern examples are Web apps (especially in the early days), Flash, Silverlight, etc. and possibly the Web itself.
> The problem is that Scheme and C were both trying to solve the 'decent high-level language' problem, but since C came out first, fewer people cared about Scheme when it eventually came out. In the mean time they'd moved on to tackling the 'null pointer dereference in C problem', the 'buffer overflow in C' problem, the 'unterminated strings in C' problem, and so on. Even though Scheme doesn't have these problems, it also doesn't solve them "in C", so it was too difficult to switch to.
C is quite odd in that the programmer is expected to pay dearly for their mistakes, rather than be protected from them. BTW it wouldn't be as much fun if they were protected.
Regarding Scheme, it has withstood the test of nearly forty years very well.
C is unique because it's really easy to mentally compile C code into assembler. Scheme is more "magical".
The more I learn about assembler, the more I appreciate how C deals with dirty work like calling conventions, register allocation, and computing struct member offsets, while still giving you control of the machine.
On the other hand, some processor primitives like carry bits are annoyingly absent from the C language.
> For example... should I use map or reduce here, or maybe an iterator, or... oh fuck it, I'll use a for loop.
Use the least powerful tool that solves the problem. A map is less powerful than a fold (reduce), so use that if you can. A fold is probably less powerful than a for-loop, so use that if you can. An iterator is probably less powerful than a for-loop, so use that if you need the more streamlined accessing of elements rather than a fine-grained, indexed loop.
But I know how to write a for loop in 15 different languages, and all of those other solutions work differently! If I have a problem that I can solve with a for loop, I should probably just use a for loop and move on to bigger problems, rather than remind myself exactly how iterators work in $LANGUAGE.
But I know how to write a GOTO in 15 different machine code dialects, all of those other solutions work differently! If I can solve with a GOTO, I should probably just use a GOTO and move on to bigger problems, rather than remind myself exactly how loops work in $LANGUAGE.
Goto has a problem, it breaks the structure of the code. For does not have this problem. Thus, you should avoid goto, and there is no reason to avoid for. The fact that you knows how to write both changes what?
Actually Goto doesn't have a problem; And it wouldn't matter in modern languages that deny access to that low-level instruction.
In basic you can use your labels and goto statements to create a hierarchy of dispatching routines. And if you're very disciplined about which global variables you use as flags to control the execution flow, i.e. which goto statement gets selected next, and which variables to hold return values, you could write a decent program.
The danger lies in that it allows the programmer too much range to produce low-quality code. The negative effect includes too much attention to the implementation of abstractions, instead of its usage. The existence of the goto statements can undermine other abstractions offered by the programming language.
Goto doesn't break structure of the code, programmers do. (I guess that's the whole reason we stopped using it: reducing the risk of making crappy software)
Certainly. Use of goto to implement structured programming is structured programming, but if you're implementing control flow structures that are provided by your language anyway why are you bothering to use goto? The result will be slightly less readable and slightly less maintainable. There remain a few places where a commonly used language doesn't implement the control structure that we'd want to use and goto can be a reasonable choice - the most common example is using goto to implement (limited) exception handling in C.
The point Dijkstra was trying to make is that humans are inherently incapable of dealing with that kind of detailed complexity, and still reliably make useful programs. That's why he proposed that goto should be excluded from all higher-level programming languages.
In my comment structured programming refers to using structured syntax to generate goto statements, so you don't have to see them or implement them yourself. It should free the programmer of considering those alternative ways of controlling program flow. Presence of goto statement points to a flaw in the language design.
To answer your question:
Because the basic language I'm referring to, was on my TI84-plus calculator, and it only had an if-statement (no if-else!).
"The point Dijkstra was trying to make is that humans are inherently incapable of dealing with that kind of detailed complexity, and still reliably make useful programs. That's why he proposed that goto should be excluded from all higher-level programming languages."
There is a very simple isomorphism between each of the typical control structures (sequencing, choice, and iteration) and its implementation with gotos. It's an easy mechanical translation, in either direction. I don't think Dijkstra was making any claim that spelling these control structures with goto radically increased the difficulty of programming. The important thing was using reasonable control structures (and only reasonable control structures) in the design of your program. Obviously, having the language do it for you is preferred much like any other mechanical translation - but that's not the key point.
"It should free the programmer of considering those alternative ways of controlling program flow. Presence of goto statement points to a flaw in the language design."
I don't disagree with any of that.
"Because the basic language I'm referring to, was on my TI84-plus calculator, and it only had an if-statement (no if-else!)."
That's still an example of using goto to implement missing control structures, not using goto when the control structure you want is present.
The point is that different languages have different features and even those with similar features may place different emphasis on which to use. By writing code for the 'lowest common denominator' like this, you're missing the advantages of whichever language you're using.
The most obvious symptom would be with libraries; even though GOTOs, loops, recursion and morphisms are equivalent, if most libraries expose APIs in a different style than your code, you'll have to sprinkle translation code all over the place.
It also makes a difference for static analysis, eg. in IDEs and linters. For example, Iterators might give you more precise type hints or unhandled-exception warnings, which makes that style safer (all else being equal).
Of course, the other point is that there are no such 'lowest common denominators'. GOTO certainly isn't, since Python, Java, etc. don't support them. For loops aren't, since languages like Prolog, Haskell, Erlang and Maude don't support them. Recursion isn't supported in COBOL or OpenCL, and took several decades to appear in FORTRAN. Morphisms require first-order functions, which rules out FORTRAN and C, and until very recently C++ and Java. It may or may not be possible to build such features from the other ones, but even so that's clearly working against the language and causing massive incompatibility with everyone else.
Clinging to particular features like this will only blinker us to the possibilities which are out there. In this case, clinging to for loops implies avoidance of at least Erlang, Haskell and Prolog. These languages have a lot to offer, and are/look-to-become the go-to solutions* in the domains of concurrent, high-confidence and logical/deductive code respectively. Clinging to inappropriate concepts dooms us to 'reinventing the square wheel'.
> 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.
Yes, the guy is very clever at putting down things that he didn't really like. The phrase "damning with faint praise" comes to mind, but upon reflection it doesn't really apply in this case. He's really damning it with criticism badly disguised as praise.
Yes, I know that he says that he is negative and his praise comes across badly (he gives an example of him damning his blogging software). I suggest, if he is being sincere, that the problem is his attitude. If he isn't being sincere, then I'd say that he's not as clever as he thinks he is, or he just never had any passion for Java and so he actually hates it as he never mastered it.
In terms of boilerplate code, I'd love to see his exact examples. With annotations, I've found that plenty of Java code is actually not verbose at all. If he churned out thousands of classes when actually he could have reduced the number of classes to 50, then that's an example of poor management and poor workmanship, certainly not a problem with the language.
Here's the real issue: the article conflates a corporate environment that likes to measure productivity in klocs (or from the number of classes you create) with the tool that is being used. He even admits that he could have actually produced better code, just that nobody cared to correct him when he produced crap quality code.
However, when it comes down to it, the real phrase that comes to mind is:
YHBT. YHL. HAND.
The guy is trolling you all.
(Different languages have different failure modes. With Perl, the project might fail because you designed and implemented a pile of shit, but there is a clever workaround for any problem, so you might be able to keep it going long enough to hand it off to someone else, and then when it fails it will be their fault, not yours. With Haskell someone probably should have been fired in the first month for choosing to do it in Haskell.)
The languages he praises so highly are now put down. Correction: in each case, he's dissing the programmers who wrote the crappy code and caused the project to fail, and he's damning the terrible management that didn't have good enough leadership and oversight to prevent the project failure.
>>In terms of boilerplate code, I'd love to see his exact examples.
Seriously, this is likely asking proof regarding the presence of sun during broad day light.
>>With annotations, I've found that plenty of Java code is actually not verbose at all.
Again. Are you really serious?
>>If he churned out thousands of classes when actually he could have reduced the number of classes to 50, then that's an example of poor management and poor workmanship, certainly not a problem with the language.
You certainly haven't ready his book 'Higher Order Perl'.
I find most annotations in Java are workarounds for missing language features. If you need a piece of functionality you're better off working in a language that provides it natively, so that all your tools will understand it.
> Seriously Clever for A 10 year old book on perl basics is probably pushing it.
Why is the age of this book relevant? It's not an overview of the basics. it's a 500+ page exploration of Perl's functional programming features, a topic rarely (if ever) covered in this depth.
Sometimes it is the tool that's to blame, you know.
Even the best master carpenter wouldn't be able to build something as simple as an end table if he were given a pickle to use as a hammer, and an onion to use as a saw.
The tool seemed to be the right tool or at least good enough for the master carpenters of Hadoop, Elasticsearch, Cassandra, Zookeeper, Neo4J etc etc... and Google, Netflix, Twitter etc etc...
That's a terrible analogy. More along the lines of using a sledge hammmer to crack a nut is probably more appropriate here. Java is very powerful but sometimes using it for very simple programs can be overkill
Also, have you heard the saying: A bad workman blames his tools
> There's more, but I'm arguing with a person who believes that a question about stdin and stdout is a proper gateway for measuring skill toward any programming problem ever.
Well, it sure did a good job of identifying a lack of skill of folks on HN, so maybe it isn't quite so crazy after all.
I think the writer of the article is clearly suffering Ahmdal's Condition. Many of the problems he states are clearly personal experiences, and could not be applied to a more broader environment. Just like saying: a whole sport is bad, becuase one team is not performing well.
> There's more, but I'm arguing with a person who believes that a question about stdin and stdout is a proper gateway for measuring skill toward any programming problem ever.
Apparently you missed his second sentence:
> The first question on the quiz is a triviality, just to let the candidate get familiar with the submission and testing system.
The "Har-har-so-many-classes-and-don't-even-get-me-started-on-variable-names" argument is true.
Sure, you can overdesign in any language, but some inner language characteristics of Java lead to it more than other languages do.
Copying input to output is a statement, if you think about it in in a logical, human way: "I want you, program, to perform the action of copying this to that". But Java, unlike some other languages forces you to say: "Make a class, make a main function because YOU need it, Java, and only after please do what I really want and copy that input to the outout".
In python, for instance, I'm in control of whether making a StreamCopier class, a copy_stream() function or just get to the core of what i want and write "print(input())".
Generally speaking, in most dynamic languages if I want to access the to_string() method of something passed to a function, I can pass a list, an int, a Duck object and till it has a to_string() function I'm good to go, or I can even monkey patch to_string() and call it a day.
In (too) many cases Java the language forces you to make instances over instances and implementation of interfaces and the like just to access the data you need in the way a framework or a method wants, not you, and often this is frustating because you see your data "just there" and the language fights against you preventing you to acccess it in an easy way. I need to call to_string() of SomeClass but I have an instance of SlightlyDifferentClassStream? Good luck with that, maybe the only possible way is to create a DifferentClassConverterProxy just to have a DifferentClassTranslator, extend TraslatorStream, feed it with my SlightlyDifferentClassStream and have something compatible with SomeClass.
This is not only true to Java, some of these "problems" arise from it being a statically typed pure OO language (a very good thing on my book when it is not implemented in a dumb way) that is put to shame by a cleaner implementation of the same principles like the one seen in C# that, on top of all, also offers a powerful dynamic programming, lambdas and so on.
Also, Java suffers from an enterprisey background. I consider a language environment to be a very relevant part of a language itself, and Java has promoted the proliferation of ridiculous bahamut frameworks with a freaking large number of classes and instances "just in the case someone needs to extend it".
These are facts, not "Har-har-so-many-classes".
Take a sane implementation of a web framework like Django. It is an opensource project born from the needs of a small group of developers. You don't have "so many classes" if you only write those that you really need to solve of your problems, and the project progresses from there to embrace the everyday, real world needs of a larger group of contributors.
Most Java frameworks I've worked with really give me the tangible perception of a large group of monkey developers programming them following line by line a technical specification bible of thousands of pages written by a council of architects with business people yelling at them "mooooore, mooooore, we need more of all of this so that we can sell to ANYONE!"
But it's not making a good point. It's not even close. I wish people would stop voting it up.
- "In Java, you can forget about doing it in the cleanest or the best way, because that is impossible." -- cleanest in any language? Or cleanest for Java? Of course the "cleanest" way for Java is possible, and it does matter -- there is a huge difference between "clean" Java and unclean, and anyone who says otherwise is a Java pretender. And arguing about "cleanest for any language" is just a proxy for a language flamewar.
- "And nobody can glare at you and demand to know why you used 576 classes when you should have used 50, because in Java doing it with only 50 classes is probably impossible." -- The "Har-har-so-many-classes-and-don't-even-get-me-started-on-variable-names" argument. In Java, as in any language, you can over-design, or you can under-design, or you can design just what you need. The number of classes may end up higher than in other languages, but this argument is silly and tired, and yes, length is one of the tradeoffs of Java. An IDE (gasp!) should make that largely irrelevant.
- "The code was ridiculously verbose, of course, but that was not my fault. It was all out of my hands." -- This word "verbose" keeps being thrown around as an insult, but nobody brings up the tradeoffs. Verbosity has a purpose, just like brevity and terseness do. And of course it's never out of the programmers' hands. Once given a language, it's all relative.
There's more, but I'm arguing with a person who believes that a question about stdin and stdout is a proper gateway for measuring skill toward any programming problem ever.