Sunday, March 27, 2011

More on Java assert

Java's assert is a mixed blessing.

It's nice syntactic sugar. I'd rather write:

assert x == 0 : "expected x = 0, got " + x;

than:

if (x != 0)
    throw new AssertionError("expected x = 0, got " + x);

You could make your own assert so you could write:

verify(x == 0, "expected x = 0, got " + x);

But there is are subtle but important differences. With your own function, the error message is always constructed, whether the error is thrown or not. Whereas with assert, as with writing out the if, the error message will only be constructed if the assert fails. That doesn't matter if the message is just a literal string (or there's no message) but if the message requires, for example, calling toString on a complex object you could take a significant performance hit. There's also the lesser overhead of calling a function. It's possible that the JVM JIT optimizer will in-line the function so it becomes equivalent to writing out the "if" and therefore eliminate the overhead, but I'm not sure if it's "safe" to count on this.

The downside of assert is that you have to remember to enable it. (My blog post on this is one of my most visited. Google for "enable java assert" and it's currently the third result.) One way to avoid this is to write a test to confirm that asserts are enabled. I did this after I got burnt by disabled assertions a second time.

I just ran into another problem with this. We were looking at using NativeJ but it doesn't appear to have any way to pass the -ea option since it uses the java dll rather than the actual java executable.

I discovered that there is a way to enable assertions from within the code:

ClassLoader.getSystemClassLoader()
     .setPackageAssertionStatus("suneido", true);

It only applies to classes loaded after this, but I have it as the first statement of my main method which should be the first class loaded.

Another wrinkle to assert is that the exception it throws is AssertionError which is an Error. The Java exception hierarchy is:

Throwable
    Error
    Exception
        RuntimeException

Only RuntimeException's can be thrown without declaring them. "Normal" code is only supposed to catch Exception, not Error. But in Suneido, I don't want an assertion failure to "crash" the system, I want to be able to catch them. But to do that, I have to catch Throwable, which is not the recommended practice. I could use two catch's - one for Exception and one for AssertionError. But apart from the extra code, I wonder if there aren't other Error's that I would want to catch.

One way around the AssertionError issue would again be to write my own assert function so I could throw whatever exception I wanted (e.g. a RuntimeException). But as I explained above, there are issues with that.

Yet another option is to use a javac (the Java compiler) annotation processor (like fa.jar) that converts assert to if-throw. But that seems a little scary.

Of course, a lot of this is due to wanting to keep the assertions enabled, even in production. I'm a believer in this since so many problems don't show up till the software gets used for real. However, it does mean you have to be a little careful to make the asserts fast enough for production.

No comments: