Monday, November 26, 2007

The Pain of Java after Ruby

Having done several months of Ruby in the past year, I occasionally find myself looking at a page of Java code and thinking to myself, "Wow, that's Java alright."

For instance, within the past few weeks, I needed a URI for testing. I made it a constant (private static final). Since it can throw an exception, I had to put the constant in a static initializer. Since the exception might be thrown, the variable might not be initialized, and so on and so forth. Eventually, you end up with this:


private static final URI SOURCE;

static {
URI uri = null;
try {
uri = new URI("/");
} catch (URISyntaxException e) {
Assert.fail("Couldn't create a URI for testing; please fix the test. This shouldn't happen.");
}
SOURCE = uri;
}


Although I understand the rationale behind each of the steps that gets you here, it's a bit of a boiled frog syndrome. By solving one problem with slightly more complex code, and repeating that pattern a few times, you eventually end up with pretty horrific code. Thanks, Java!

Now, to be fair, there are other ways around this code. For instance, I could easily create this as an instance variable in the test setup code, which in many ways would be a more normal approach, and it'd take all the above code away. That's probably the most obvious resolution for the problem at hand, but there are certainly others.

In any case -- because there are alternate solutions, this isn't really a problem, but having painted yourself into this particular corner it's easy to think to yourself, "Wow, this code would be much nicer in Ruby."

19 comments:

Doug said...

Also easier in Scala:

  val SOURCE = new URI("/")

Well, except for that "static" bit. That requires that Source be located in the "companion module", so if you don't have any other static items it'd be more like:

object MyClassName {
  val SOURCE = new URI("/")
}

On a separate topic, I personally recommend only using all-caps names for constants. That way you have a visual clue as to which references are resolved at run-time and which ones will require recompilation in order to pick up any changes.

Geoffrey Wiseman said...

Your 'separate topic' implies there's a non-all-caps name for a constant, which I'm not seeing.

The constant is SOURCE in this case, which is all-caps.

Doug said...

My point is that SOURCE is not a constant. It's an immutable variable whose value is determined at run-time rather than at compile-time as constants are. Therefore, I would not give it an all-caps name.

Geoffrey Wiseman said...

Ah, fair enough; although it's only NOT a constant because I couldn't make it one within the confines of Java, so that's part of the pain.

That said, if I were to continue forward with that code (as opposed to moving it to a setup function), it's not a bad idea to change the way the variable's named, yeah.

Art Vandalay said...

More like the pain of checked exceptions, really. What would this look like in Ruby?

Geoffrey Wiseman said...

Yes, checked exceptions are the immediate pain here, and everything cascades from there.

The line I wanted to write was:
public static final URI SOURCE = new URI("/")

A similar line in Ruby might look like:
SOURCE=URI::HTTP.new "/"

(I haven't attempted to compile either of the lines above; I apologize if I've made some inadvertent error.)

But, no checked exceptions makes all the difference in the world to this particular example.

Anonymous said...

The intermediate variable is not required:

private static final URI SOURCE;

static {
try {
SOURCE = new URI("/");
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

Yes, your whole problem is with checked Exceptions. You could extract this to a UriUtils class, but the point remains the same.

Geoffrey Wiseman said...

It's true, the intermediate variable is only useful if you plan on trying to recover in some way, if you just want to error out, then throwing an exception is a better way to go.

All of which goes to show that there are a fair number of ways around the problem, but that checked exceptions and a few other language quirks can add up to some ugly code at times.

cb said...

Why not Groovy? I usually don't find Ruby and Java in the same camp...I find that the environment and decision is usually pretty clearly a Java or a LAMP/R stack.

But in a Java/JVM situation, Groovy is my default. I'd need a good reason to ever build an application in plain Java again.

Java has its uses in building frameworks and infrastructure for performance reasons (sounds like the arguments for C made years ago...we've evolved again).

But for application development, Groovy has significant advantages. Have you considered it?

Cheers,
Clinton

Geoffrey Wiseman said...

This latest round of Ruby work was to build a series of REST services. When last I checked, Grails wasn't yet there in terms of building REST services.

One imagines it's possible to build a REST framework in Groovy, but I wasn't anxious to build my own framework, wanted to get on with building the services, so we compared Ruby with Java/Restlet.

I'm given to understand its on the radar for Grails, though, and for non-REST things, I'd consider Groovy.

Still, Groovy's slower yet than Ruby (from what I've been told; haven't attempted to benchmark Groovy), which is pretty slow, so I'd like to see both of them pick up their socks on performance.

cb said...

>> Still, Groovy's slower yet than
>> Ruby (from what I've been told

Do you have a reference for that?

I've heard quite the opposite, and even based on my knowledge of how the runtimes are implemented, I'd be very surprised if that's true at all.

Groovy is compiled to bytecode and run by the HotSpot VM -- the fastest in the business. Yes, the Groovy metaclasses introduce overhead, but I'd still be surprised if it doesn't kick the snot out of Ruby in most general performance benchmarks.

And if Grails isn't smoking the pants off of Rails in server load tests on equal hardware, I'd eat my hat (if I was wearing one). The thread model vs. processes alone, plus the fact that Grails is written in mostly Java under the covers....I don't think Ruby has a hope in that performance challenge.

The one exception I'm aware of is startup time...it definitely does take longer for the JVM to start than for Ruby's MRI to snap through a simple script.

For scripts that definitely matters. But for applications (especially web), it rarely matters enough to even mention.

Cheers,
Clinton

Geoffrey Wiseman said...

Sadly, I don't recall the references I'm drawing upon, so I've done a little poking about to see if perhaps i'm "full of it" on the performance of Groovy vs. Ruby and Grails vs. Rails. It's difficult to discuss 'em in comment form, so I'll fire up another post.

Graeme Rocher said...

Couple of things to note:

a) The benchmarks you refer to in this post were done with Grails 0.4 vs Rails 1.2. Grails is now at Grails 1.0-RC1 and those same benchmarks are between 60% and 300% faster. It is our fault for having out of date benchmarks of course.

b) REST support - Grails 1.0 RC1 features automatic XML or JSON marshalling and restful URL mappings. Grails 1.0 RC2 will support content negotiation. Pretty much all you need for building Restful APIs

Geoffrey Wiseman said...

Ah, that's good news on both points, Graeme; definitely looking forward to getting some practical experience with Groovy on Grails, then.

Ricky Clarkson said...

interface ThrowsException<T>
{
. . T invoke() throws Exception;
}

public static <T> T ignoringAllExceptions(ThrowsException<T> thunk)
{
. . try
. . {
. . . . return thunk.invoke();
. . }
. . catch (Exception e)
. . {
. . . . throw null;
. . }
}

public static final URI uri=ignoringAllExceptions(new ThrowsException<URI>()
{
. . public URI invoke() throws Exception
. . {
. . . . return new URI("/");
. . }
});

Closures as proposed for Java 7 would make this less painful:

public static final URI uri=ignoringAllExceptions({=>new URI("/")});

If reification of generics was implemented, you should be able to choose which exceptions to ignore too, e.g.:

public static final URI uri=ignoring(URISyntaxException.class,{=>new URI("/")});

The above would be improvements, but the real source of the problem is only that try..catch is a statement, not an expression, which makes it far less composable. If it was an expression, you could do:

public static final URI uri=try { new URI("/"); } catch (URISyntaxException e) { throw null; }

Anonymous said...

private static final URI SOURCE = URI.create("/");

Geoffrey Wiseman said...

Ah, cool; while that doesn't deflect the point that Java code can be ugly, it does show that good API design can make that less painful.

I hadn't noticed the existence of a factory method that handled the exception; that's a nice option.

Anonymous said...

The point it makes is that your Java code is ugly.

Seriously though, you've got a bit of a straw man here. You don't show comparable Ruby code. You demonstrate an unfamiliarity with the library your using. You complain about Java's verbosity but you use an unnecessary temp variable (which indicates to me you're not really that familiar with Java initialization.) You make a design decision which you admit is questionable (static vs. instance), but complain about Java rather than your design. Finally you're forced to admit that the only real issue is with checked exceptions in constructors.

Java's got some issues no doubt, this snippet doesn't really demonstrate them.

Geoffrey Wiseman said...

You don't show comparable Ruby code.

I did; it's boring, which is why I didn't include it in the original post, but I did include it in a follow-up comment.

You demonstrate an unfamiliarity with the library your using.

I wouldn't call java.net.URI a library, per se, but I admit, I missed the URI.create() factory method.

Using a factory method to dodge a checked exception on construction is useful here, but it's not an every-day approach to API design in Java.

You complain about Java's verbosity but you use an unnecessary temp variable (which indicates to me you're not really that familiar with Java initialization.)

I am pretty familiar with Java initialization. You can't assign a final variable to the result of a constructor that can throw an exception unless you're willing to cascade the exception, because that would leave the variable uninitialized.

Since, in my code, I wasn't looking to cascade the exception, the temporary variable is another way to handle that.

You make a design decision which you admit is questionable (static vs. instance)

I wouldn't call it questionable. Static finals for constants are a pretty common approach, even in test code.

Instance variables are better-suited to testing primarily to avoid test interactions, but an immutable constant shouldn't be prone to causing test interactions.

Finally you're forced to admit that the only real issue is with checked exceptions in constructors.

This post wasn't an exhaustive list of pains, just one example; I hit a painful bit of code, and it triggered a desire to talk about it.

This particular pain is caused by checked exceptions and their use in constructors, a common approach in Java that can cause some ugly code. Similar code in Ruby doesn't have the same problem. That doesn't invalidate the point, it just makes it more specific.

If you're objecting to the fact that I didn't provide other options, I'm happy to do so. For instance, collection literals: they make some code in Ruby much more legible than the equivalent Java code.

It seems like you're being defensive here, perhaps I'm reading into it. I'm not trying to slam Java. Actually, I like Java quite a bit, probably still more so than Ruby. That doesn't mean I can't occasionally find it a little painful.

Anyway; happy to talk through some of the points in more detail if you like, but if you just wanted to add a snappy rejoinder, then, congrats. ;)