The Artima Developer Community
Sponsored Link

Cool Tools and Other Stuff
Have Generics Killed Java?
by Eric Armstrong
July 22, 2010
Summary
In which I argue that (a) Generics have done egregious harm to both the elegance and readability of the Java language and, (b) they prove by example that static type checking is a linguistic dead-end. Are you persuaded? Do you agree? Read on...

Advertisement

I confess. I'm a Ruby hacker, at heart. Ruby's Perl-isms can't go away fast enough to suit me, but what's left after removing them is a thing of utter beauty and precision. Not to mention power.

But long before I became enamored of Ruby, I was a Java hacker. I've recently come back to Java, and was rather surprised by the strength of my reaction--mostly as a result of generics.

I admit to being mildly annoyed by all the semicolons, braces, and parentheses that the compiler forces me to supply. It makes me feel like a slave, working for a not-very-bright master. I mean, the compiler could be smart enough to know what's missing. It just isn't. It prefers to give me an error, instead.

And of course, I miss Ruby's functional coding style, closures, and the flexibility provided by duck typing, open classes, and dynamic hooks--things that give the language it's expressiveness and power.

But what's really killing me is generics.

Generics Considered Harmful

Don't get me wrong. I continue to admire Generics as a tour-de-force of implementation genius. Getting them to work in a backwards-compatible way was nothing less than brilliant.

At the same time, I decry their very existence. They are the ultimate condemnation of static type checking--because their very addition has virtually destroyed both the readability and elegance of the language.

To make my case, let's start with a quasi-mathematical definition of "elegance". In my view, elegance is a function of power and simplicity. Power is measured by how much you can do. Simplicity is the inverse of the number of characters it takes to achieve the result. So more power with fewer characters equals "elegance", in my book.

Of course, by that measure APL is a very elegant language! And it is. But it's famously unreadable. Somewhere, there is a quote from a very smart guy who spent 4 days figuring out what a 4 line program did-- a program that he himself had written a few months earlier. That has to set some kind of standard for unreadability.

So "elegance" has to be tempered with the equally important notion of "readability". Readability is also a function of the number of characters on the page, but it has a bell curve: Too few characters, and the code is indecipherable. But with too many characters, the code is effectively obfuscated. It becomes impossible to see the forest for the trees. At that point, the code is obscure.

Between "indecipherable" and "obscure", lies the domain of "readable". That realm is the sweet spot. It's one that Java has been pretty darn good about occupying.

Until generics.

With the advent of generics, elegance has obviously been impaired (more characters, same power). At the same time, I claim that generics have pushed the language into the realm of obscurity.

That is an entirely subjective assessment, I admit. People have different levels of tolerance for that kind of thing. I mean, there will always be folks who prefer

SALES MINUS COSTS DIVIDED BY YEAR
to
(sales - costs) / years

To each their own. But there is no doubt that with the advent of generics, Java code is less readable (and therefore more obscure) than it once was.

The result is a two-fold loss--a loss of both elegance and readability--that to my mind is nothing short of a travesty. It represents the virtual destruction of a good language at the hands of something that undoubtedly seemed like a good idea at the time.

When I look at code I've written using generics, and mentally strip out the generic portions, I can't say that the code is any better or worse than it was before. Generics just don't seem to be adding any real value.

Of course, generics create greater "type safety" at compile time. And that, I think, is the ultimate condemnation of static type checking: They show how much work you really have to do to get it. And what is your return for that investment? Not much, really. You get a small percentage increase in the number of real errors found at compile-time.

But to get that marginal increase, you spend more time adding required syntax, watching the compiler find silly omissions and typos, and making changes solely to satisfy the compiler. In return, you get code that is less readable.

If all that work produced code that was truly bulletproof, it could easily be worth the investment of effort. But it doesn't. You still have to do an enormous amount of testing to get any real quality. And by the time you've done all that testing, static type checking winds up being superfluous.

Sure, in some super-critical applications like the Mars mission or a stock exchange, the tradeoff may be acceptable. Even a tiny improvement in quality is worth a significant investment, because the cost of a defect is so high. But for day to day programming, the tradeoff just isn't worth it, in my book.

An Example is Worth 1,000 Words

Enough with theory. Let's look at an example.

I'm a big fan of the "foreach" concept. A phrase like this one clearly communicates the code's intent:

foreach item in List {
  ...
}

In Ruby, the equivalent mechanism is even cooler, once you get used it:

List.each { |item|
  ...
}

It's cooler, because "each" is a method that can be applied to any collection.

Sidebar: Going Crazy with Functional Expressions Since every block is an expression, and every expression returns a value, if you want to go nuts, you could even apply a dot-operator to the block:

List.each { |i| ... }.doSomething()

Crazy, huh? You wouldn't do that often, but there are times...

Want to iterate over a Map, processing each key/value pair in turn? Here's the syntax:

Map.each { |key, value|
  ...
}

And now here's the equivalent expression in Java, with generics:

for(Map.Entry<K, V> e : map.entrySet()) {
  ...
}

Sorry, but that's an expression that only a mother could love. Without generics, it's not too bad. With generics and the other syntactic elements to required to make it legal, the statement verges on unreadable.

In general, the trick to reading such loops is to translate the litte colon to "in" and, once you see the colon, to mentally convert the "for" in the expression to "for each". So with a simple, "unsafe" loop, when you see this:

for (item : List) {
}

You read:

for (each) item (in) List ...

That's not bad. But lets look at the Map example again. This time, adding the statements needed to get values to work on.

For comparison, in Ruby we would write:

map = getInfo();
map.each { |key, value|
  ...
}

But in generified Java, we need this:

map = getInfo();
for(Map.Entry<K, V> e : map.entrySet()) {
    String key = e.getKey();
    Object item = e.getValue();
    ...
}

With the generic declarations added in, plus the need to invoke a method on the map, and all of the parentheses and angle brackets that are required, the colon is hard to see. It is effectively lost amid the noise. You have to peer at the expression carefully to discern its intent--especially when you come across it in the context of a larger program.

In fact, it was after copying that very expression from the web that I became disenchanted with generified Java. For me, Java's huge advantage from day one has always been it's readability. (The Web interface library didn't hurt, either. But this is about the language.)

With Java, there was only one way to do anything. There were no #ifdefs or home-grown macros, so no matter whose code you read, you always knew exactly what it was doing. So it easy to read. In consequence it was (relatively) easy to learn.

Ruby is more like Perl, in that regard. There are so many ways to do things that, to read anyone else's code fluently, you first have to master the particular set of idioms they employed.

So score one for Java in the realm of readability. Until generics. A subtle syntax element like ":" can't stand up against the deadly incursion of ubiquitous type-declaration syntax. To even begin to compete, it would have to employ COBOL-like syntax--in all caps, at that:

for(Map.Entry<K, V> e IN map.entrySet()) {
   ...
}

That, at least, would be a little closer to readable. But of course it would be even less elegant.

But wait, there's more

Things get even a little worse if we want to process the map in an ordered way, and we assume that the original map isn't necessarily ordered.

Here's the Ruby expression:

map = getInfo();
map.sort.each { |key, value|
  ...
}

Pretty cool, huh? You merely invoke an intermediate sort() method to get the result you want. It's even more readable because parentheses on method invocations are only required when they are absolutely necessary.

Here is the equivalent operation in generified Java:

Map<String,Object> map = getInfo();
Map<String,Object> sortedMap = new TreeMap<String,Object>(map);
for(Map.Entry<K, V> e : sortedMap.entrySet()) {
    String key = e.getKey();
Object value = e.getValue();
...
}

Readable? I think not. It takes more than a quick glance to tell what that code is doing.

Anticipating the Counter Arguments

Generics force the programmer to do at something the computer is entirely capable of doing at run time (type verification). And if we have learned anything at all about computers, it's to let them do what they're good at!

When you add generics to the requirements for parentheses and semicolons (other things the compiler could easily supply), the scales tip quickly away from readability and elegance.

Of course, runtime type checking makes your code more "unsafe", so you have to do more testing. But when you're not spending all of your time adding syntactic "sugar" to make the compiler swallow your code, you have a lot more time to make sure your code is doing the right thing.

Note: That's right. I just called generics "syntactic sugar". In this case, the sugar makes the code more palatable to the compiler, not the coder. You have to pour it on, but you don't get to enjoy it.

Equally horrendous, to some, is the fact that runtime type checking slows things down. But computers are getting faster, the number of programs that needs to be written is exploding, and the number of programmers available to write them is shrinking in comparison.

Given that state of affairs, it's hard to see the point of sacrificing readability and elegance, all in the same breath.

Talk Back!

Have an opinion? Readers have already posted 62 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Eric Armstrong adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Eric Armstrong has been programming and writing professionally since before there were personal computers. His production experience includes artificial intelligence (AI) programs, system libraries, real-time programs, and business applications in a variety of languages. He works as a writer and software consultant in the San Francisco Bay Area. He wrote The JBuilder2 Bible and authored the Java/XML programming tutorial available at http://java.sun.com. Eric is also involved in efforts to design knowledge-based collaboration systems.

This weblog entry is Copyright © 2010 Eric Armstrong. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use