[m-dev.] checked exceptions.

Michael Richter ttmrichter at gmail.com
Wed Feb 13 13:19:50 AEDT 2013


Let me put in a negative vote here.  I think checked exceptions are evil.
They were one of these things that *sounded* really good in theory but have
turned out in practice to make things worse—by far—than had they never
existed.

When I have checked exceptions I have to check the exception signature of *each
and every* predicate or function I call and do one of the following:

   1. Add the exception to my signature.  The *whole point* of exceptions
   is to decouple error detection from error handling.  They're intended to
   eliminate that whole thing in languages without them where you do something
   like if this error else if that error else if this other error else if
   yadda yadda yadda ad nauseum with each and every function call as you
   encounter conditions you can't deal with yourself but which something
   higher-up might be able to handle.  Thus the default for most functions
   when exceptions enter the picture is to just pass them on up the chain to
   the right handler (which is often going to be the mainline code just
   shutting down cleanly with an error message).  Being forced to add every
   two-bit trigger-pumping moron's exceptions into the predicate/function
   signature just clutters the interface with no real benefit.  Further, if
   there's any changed in the exceptions downstream, *every predicate or
   function* in the chain above it has a commensurate signature change; one
   that has the capacity for essentially rewriting and recompiling your entire
   application because someone corrected a typo.  (And this is all also beside
   the problem that it's all unwieldy anyway: if each function calls two other
   functions and each function has one possible exception declared, how long
   before you have a combinatorial explosion in your interface size?)
   2. Catch-and-convert the exception.  This is the common "solution" to
   the ever-expanding exception signature problem.  try { ... } catch
   (Exception e) { throw MyGenericException(e); } is the typical pattern.
   Ever see those never-ending exception stack traces in Java code?  This is
   why. Because people are forced to either handle or add each and every
   subordinate method's exception down the stack, and because adding each
   exception to the signature of each and every method is just unworkable, the
   "solution" is to catch any exception and wrap it in your own exception.
   This actually *worsens* the ability of upstream to handle the exception
   because you're now removing information that could be required to handle
   things properly.  Consider this situation: at the low levels of your code
   opening the file fails because it doesn't exist.  Some kind of exception
   (file_not_found, say) is thrown.  If you're five layers of
   function/predicate calls down, and if each function/predicate call does
   this catch-and-convert, mainline code has to unwrap five exception type
   variables to get to the file_not_found exception instead of, you know, just
   catching file_not_found.  catch some_generic_exception, then
   unwrap(some_generic_exception) then unwrap(unwrapped_generic_exception)
   then unwrap(unwrapped_unwrapped_generic_exception) and so on.  Not only are
   you recreating the whole if this error else if that error else if that
   other error situation, you're making it worse by putting all of it in a
   single place.  And you're cluttering up all the intervening
   functions/predicates with boilerplate catch-and-convert code to boot.

Now let's look at composition.  Checked exceptions completely and utterly
break composition.  The *whole point*, for example, of higher-order code is
to decouple execution patterns from the specifics of execution.  Let's
consider a simple situation of mapping a function/predicate against a
list.  What exceptions should be declared on that mapping function?  Well,
I might do division in the passed-in function, so I'll need to catch divide
by zero errors.  So now my list mapping code has to declare divide by zero
as a possible exception, even though there's *nothing* in its actual code
that can do this.  Now what happens if I'm mapping a function provided by
some third-party that declares some_random_exception in its interface?  Now
my map has to somehow know about this third-party exception in advance and
declare it in its interface.  This is both clearly impossible and
ridiculous.  So what do I have to do instead?  Oh, right.  I'm back at
catch-and-convert and I've just, once again, missed the entire point of how
exceptions are supposed to simplify and ease error handling.  Somewhere in
this process I've just made composition, arguably the entire *point* of a
good modern, declarative language, more difficult for no good reason.
(Because most exceptions just get logged or used to generate an error
message and a clean shutdown anyway.)

Now all this doesn't mean I'm opposed to the notion of having some way to
check exception throwing and handling.  I just don't think it should be
part of the language.  I'm going to invoke something better (IMO): external
tooling.

Consider Erlang.  Erlang is a dynamically typed language with an
*external*static type checker (using an unusual kind of typing called
"success
typing", however).  I think a static analysis *tool* would be a better fit
for checking exceptions.  This tool would read the source code and simply
make reports on exception handling, spotting chains of code where
exceptions bubble up to the main predicate and out without being handled.
This would give you the purported benefits of checked exceptions without
the liabilities of cluttering up a code base with endless ceremony (which
lazy programmers *will* just turn into empty catch clauses anyway!).

-- 
"Perhaps people don't believe this, but throughout all of the discussions
of entering China our focus has really been what's best for the Chinese
people. It's not been about our revenue or profit or whatnot."
--Sergey Brin, demonstrating the emptiness of the "don't be evil" mantra.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/developers/attachments/20130213/3bbfc0d7/attachment.html>


More information about the developers mailing list