The 'ignoreslist' exception handling proposal.
reinier at zwitserloot.com
Wed May 20 06:26:07 PDT 2009
I'm more convinced than ever that java really needs this change,
sooner rather than later.
My arguments in favour:
1. Ruslan totally misunderstood my proposal, and also appears to
misunderstand checked exceptions on the JVM level in general. This is
in my experience an extremely common problem amongst java programmers,
and this is very bad news for trying to interop javac-produced class
files with scalac, groovy, JRuby, Python, and other languages' class
files. By adding the concept of sneakyThrow to java, we teach java
programmers that checked exceptions are only a debugging aid offered
by javac, and NOT a guarantee by the JVM. This isn't a philosophical
change at all: It's realism. In today's JVM world, you just cannot
rely on that as a java programmer, and yet most java programmers do.
NB: Ruslan and everyone else that's confused - I'll send an
explanatory email in a few minutes to explain how it all works.
2. Joe Darcy, project coin, and sun routinely defend choices for the
java platform by stating that java is designed for the realistic,
human (e.g. fallible) programmer. We defend not adding operator
overloading by stating that people will abuse it (FWIW, I agree with
this mentality). And yet java (the language, not the VM!)'s current
method of handling checked exceptions is geared towards the mythical
perfectionist programmer! That makes no sense at all. I'll back up
#1: The JVM cannot, and does not, guarantee that the exceptions that
fall out of a method call are only Errors, RuntimeExceptions, and any
exception types declared by that method. That's simply not how the JVM
works, and yet most java programmers erroneously think that it is.
Being realistic for a moment, sneakyThrows occur all the time; you can
have a class file mismatch (compiled against a different version of
the class file vs. the version of that class file at runtime), you can
be using a class compiled by scala, groovy, JRuby, Jython, or any
other non-java programming language, or someone used a sneakyThrow
Realistically, then, this isnt a philosophical change at all. We're
just acknowledging something that already happened.
#2: People get exception hierarchies wrong. All the time. The new
String(bytes, encoding) constructor is flawed, for example. The right
solution would be to have a Charset class and appropriate constructor
that uses it (which string now has, since java 1.6, but the point to
take home here is that the java core team it self, with all the
lessons they knew by the time java 1.5 was developed, and with all
that reviewed, still screwed it up), and preferably also a
Charset.UTF8 constant, which java still doesn't have. Right now we
have the weird situation that new String(bytes, "foo"); throws a
checked exception, but new String(bytes, Charset.forName("foo")),
which seems semantically equivalent, throws an unchecked exception.
That cannot be the right design; we have it today because of the need
for backwards compatibility. We must acknowledge that java's exception
handling isn't perfect.
There isn't even a proper definition of what constitutes a good
situation for a checked exception, and what doesn't. Some people say
that checked exceptions are appropriate only for legitimate
alternative return values, where it would be a clear bug if a caller
doesn't handle this code, in virtually all imaginable scenarios, such
as an InsufficientBalanceException for a banking app's
transferFundsFromPersonToPerson(Person A, Person B) method. In this
view, IOException is mistyped (because there are whole hosts of
situations where IOExceptions are either extremely unlikely, or
likely, but there's nothing you can do - it is truly a program error
and not an alternative return scenario; the only viable action is
quitting, which an unchecked exception can do just as well). Then
there's the view that a checked exception is appropriate anytime an
exception is remotely likely to occur. In this view, Integer's
parseInt's NumberFormatException is misclassed, because obviously non-
numeric input is likely, and yet NFEx is unchecked.
There is, in fact, no philosophy about checked and unchecked
exceptions that would classify the various exceptions in the java core
libraries the way they are in real life. It's a grab bag; some are
checked, some are unchecked, and there's only a vague philosophy
behind the choices.
InputStream.close() throws a checked exception which most programmers
find very questionable. Almost certainly a mistake, in practice (even
if in theory it makes sense and is nicely symmetric with
OutputStream's close, which does entirely appropriately throw
IOException - at least as appopriate as write()'s throws clause).
Proper use of the new String(bytes, encoding) constructor (where the
input encoding is actually a variable and not a string literal) throws
a checked exception, but Integer.parseInt() doesn't. yet, both are
trying to parse a string where some forms are legal and some forms
aren't. There's no reason for the dichotomy here; it's random.
SQLException has only recently seen some work to make it a more usable
construct, though the current situation amongst JDBC drivers remains
troublesome. There are also many many mistakes that the entire
exception hierarchy concept can make happen. Fine case in point:
new String(bytes, "UUTF-8"); throws an UnsupportedEncodingException,
which is a subclass of IOException. What, exactly, does this error
have to do with I/O? Nothing whatsoever. The idea that
UnsupportedEncodingException is a subclass of IOException is very
We can also look at the other extreme and complain about the insane
amount of exceptions that can fall out of reflection related calls.
Bottomline: Let's be realistic - exception handling in java isn't
perfect, and it never will be. Trying to protect people from abusing
the throwing of exceptions by forcing rigid checked exception handling
is a failed experiment, and the fact that just about every JVM
language other than java itself doesn't have checked exceptions at all
means we need a way to explicitly say to javac: I know better than you
do, in this instance. Realistically, the number of situations where
javac's advice is just wrong is formidable, and its making us write
Mark, your proposal does indeed attempt to deal with this issue in a
less philosophically drastic fashion, but I think the end result just
isn't as good. Consider again Ruslan's typoed UUTF-8 example:
If we wrap it, we're just hiding the cause behind a generic
InternalError, AssertionError, or RuntimeException. I've seen them
all. There's actually a decent unchecked alternative available
(UnsupportedEncodingError) but that's the exception (heh, heh) rather
than the rule. Sure, there's getCause(), but 'cause hell' is already
causing stack traces nearing a hundred pages long, and a continuing
unsolved problem in java land is finding the one cause in the massive
onslaught that helps you get to real issue at hand. In this situation,
the rewrap is needless noise in the exception chain.
Without either your or my proposal, we get the worst of both worlds:
We rethrow, adding noise to the runtime information, and we also add 4
to 5 lines of noise to the code with a semantically pointless try/
catch block that rethrows the UnsupportedEncodingEx into something else.
Like it? Tip it!
More information about the coin-dev