The 'ignoreslist' exception handling proposal.
Reinier Zwitserloot
reinier at zwitserloot.com
Wed May 20 09:22:38 PDT 2009
Remi,
While charsets are imported from outside the system, there are a few
that are guaranteed by the JVM specs. A 'JVM' that throws an
UnsupportedEncodingException on:
new String(new byte[] { 65, 66, 67}, "US-ASCII");
Isn't a JVM at all, because the spec says that US-ASCII *MUST* be
present.
You said it: If its a developer mistake, then it should be a runtime
exception. Mistyping 'US-ASCII' is a developer mistake.
Also, Integer.parseInt(SomeUserInput)'s NumberFormatException is
clearly NOT a developer mistake, yet that is a runtime exception.
Grabbing a static file out of your deployed jar file via
Class.getClassLoader().getResourceAsStream(), and reading this file
caused an issue, you get a checked IOException, eventhough this is
clearly the developer's error in writing his deploy script. Said
differently, if reading local resources ought to fail with a checked
exception, then every attempt to load a class should be a checked
exception as well (after all, classes are just local resources as well!)
So, yes, java's exception hierarchy is a mess sometimes.
The idea behind making this point is that javac ignores reality and
treats the checked exception system as holy and infallible - that is,
you can NOT tell javac to stop whining about not handling checked
exceptions. There are numerous situations where javac insists you
handle an exception, even though as developer you know there is either
A) a chance akin to the odds of a random OutOfMemoryError that the
exception will actually occur, and is thus not worth the boilerplate
(and due to the near impossibility of it, you have no sane route for
handling the problem, so all you can do is rewrap or log it and fail),
such as UnsupportedEncodingEx, or the IOExceptions falling out of
resources loaded via ClassLoader.getResourceAsStream, or IOExceptions
on FilterXStreams that are backed by a ByteArrayXStream, or
B) You -know- it'll work itself out, such as for example throwing a
checked exception out of a Runnable used as a Thread (all exceptions
falling out of run() are handled by the thread unhandled exception
mechanism, regardless of whether they are checked or not) - usually
due to bad API design where an interface method you're supposed to
override did not have a 'throws Exception' tacked on, and it can no
longer be changed now due to backwards compatibility requirements.
Example: java.lang.Runnable, and much in the HTTPServlet class.
I posit that these situations are both common and not otherwise
solvable.
Therefore, I propose we are given a tool to more easily work around
the failings of the hierarchy. Right now lots of java programmers
would rather 'not have checked exceptions at all' because its so much
hassle. By giving a cheaper way out of the system when it fails, we
can let it stick around for all the times when it does work properly
and catches us forgetting to handle an important exceptional situation.
Being realistic, java programmers tend to -hate- the checked exception
system with a passion. Unfortunately, hate translates into not paying
attention to it. By crying wolf so much, we're trying java programmers
to just let their IDEs fill in the boilerplate, or toss 'throws
Exception' on everything just so it'll compile. This is not a good
thing. Perfectionists would handle everything properly, but that's
just not what's happening.
That's my point; even though the opportunity for abuse is there, when
you take into account that programmers aren't perfectionists and won't
pen out a complete and well thought out try/catch block just to handle
something they -think- is almost impossibly unlikely to happen, with
ignore lists java code will be better on average.
Compouding this already sizable problem:
Out of the box, eclipse 'quickfixes' an uncaught exception by writing
a catch block that prints the exception to standard error and then
just continues silently. This is a bit like ON ERROR RESUME NEXT in
visual basic. At the risk of peeving off eclipse developers: That's
just retarded. I fixed the template in my eclipse, but how many
programmers do that themselves? When I let eclipse quickfix an
UnsupportedEncodingException, or an IOException on closing an
InputStream, I usually just let this stand:
try {
inputStream.close();
} catch ( IOException e ) {
//TODO Template catch block
throw new RuntimeException(e);
}
eventhough that is so clearly inferior to just sneakyThrowing the
IOException up the chain.
less contentious alternative: add a sneakyThrow static method to
Throwable in java core, and work with IDE vendors to replace the
default quickfix for uncaught checked exceptions to:
try {
inputStream.close();
} catch ( IOException e ) {
Throwable.sneakyThrow(e);
}
--Reinier Zwitserloot
On May 20, 2009, at 17:02, Rémi Forax wrote:
> Reiner, the problem is not new String(byte, Charset) but
> the fact that Charset.forName() doesn't use a checked exception.
>
> The philosophy behind exception is:
> - If it's a developer mistake, it's a runtime exception
> - If it's an error that comes form outside the Java platform,
> i.e an error that can not be predicted without some elements from
> outside
> the Java platform, it's a checked exception.
>
> Depending on the installation, some Charsets are available or not,
> this is clearly an error that comes form outside of Java world so
> it should be handled with a checked exception.
>
> Ok, it's a mistake
> but I don't understand how it's related to your proposal ?
>
> Rémi
>
> Reinier Zwitserloot a écrit :
>> 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 this point:
>>
>>
>> #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 method.
>>
>> 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 questionable.
>>
>> 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 bad code.
>>
>>
>> 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.
>>
>> --Reinier Zwitserloot
>> Like it? Tip it!
>> http://tipit.to
>>
>>
>>
>>
>>
>
More information about the coin-dev
mailing list