Executive Summary:

  We should go to a pure return-based mechanism for error signalling,
  or a pure exception-based one.  We can't do the former.  Therefore
  we should do the latter.

Author's Note:

  I'm a pragmatist.  I'll keep using return-based error signalling
  for some purposes, just like everyone else.  I'm considering this
  as a topic for a possible "philosophy" RFC; I'm publishing it for
  review by *-errors (should anyone care to) to help me decide what
  to do about it.

Chaim Frenkel wrote:
> 
> As for legacy. I strongly urge that Modules _never_ die.
> It is extremely rude.

The contract between a module and its client is beyond the scope
of RFC 88.  However, I take it from your strong stance that you
wrap every ++$i in an eval and handle $@ in case the CPU throws
an integer overflow, oh, and you handle errors while handling
errors too, ad infinitum.  There, you see, is the core problem:
modules or any other code cannot *guarantee* to "_never_" die.

> The fact that something went wrong, doesn't mean that my 100 hour
> complex calcuation should be terminated. The fact that I couldn't
> send an email message may or may not be of importance in the
> scheme of things.

What would happen if the email module gets an overflow on a ++$i
(or anything else dies, no matter what it calls or does)?  Because
you are ignoring possible problems with email (instead of properly
handling them by ignoring them [sic]), your 100 hour calculation is
toast even though the email didn't matter to you.  You just got lucky.
You should have written (contents of catch block optional):

    try { that_email_thing(); }
    catch { print "Email not sent.\n$@\nContinuing anyway...\n"; }

Unless a module can guarantee to *never* throw (and it can't,
on pragmatic CPUs) the client should always be prepared to cope
with unwinding, for some value of cope.  What ticks off the script
kiddies is that this makes real programming harder, and there's
nothing they can do about it.  It ticked me off too, when I was
learning to cope with it.  And fundamentally you can never completely
cope with this situation (because of what's known as the "final
arbitrator" problem, that is, what happens if the final arbitrator
fails), you can only try--pardon the pun--to ameliorate it.  That's
why servers try to page sysadmins, the ultimate final arbitrator ;-)

Right.  Once you have to be prepared to cope with unwinding anyway
(that is, if you are actually interested in writing robust code),
you might as well use the coping mechanism to pick off *only* those
failure modes you are willing to cope with (by actually attempting
to handle or deliberately ignore the failure).  Here are the actual
code differences:

    Fragile way (return-code based error signalling):

                sub foo { return ERROR_IO }

                my $rc = foo();
                if ($rc == ERROR_IO) { ... }
                else { return $rc; }

    Robust way (exception based error signalling):

                sub foo { throw Error::IO }

                try { foo(); }
                catch Error::IO { ... }

Where's $rc? Why, it's in $@, of course. <Insert epiphany here.>

So, what do you, the programmer, have to learn?

        |    Old Way           |     New Way         |
        |----------------------+---------------------|
        |                      |                     |
        | return ERROR_IO      |  throw Error::IO    |
        |                      |                     |
        | my $rc = foo();      |  try { foo(); }     |
        |                      |                     |
        | if ($rc == ERROR_IO) |  catch Error::IO    |
        | else { return $rc; } |                     |
        |______________________|_____________________|

Without exception-based error signalling one bad else return $rc or
any other botched error handling can easily bring the system to its
knees, because it is continuing with bad data even though it isn't
prepared to.  With exception-based error signalling it actually
takes less bytes of code to do it right, it's more easily read, and
if you don't explicitly handle an error (for example, because of a
botched return) then the error (even if the error is botching the
return) goes through, instead of your code continuing with bad data.

A major corrollory benefit of all this is that once people make the
transition to exception-based error handling, the reliability of all
software goes up, because you end up with whole systems that are
layered to cope with failure, instead of whole systems that depend
on no-one botching an else return $rc or a ++$i, not even one.

> And if throw becomes the standard, then you are forcing _all_
> programs to accept exception handling.

And if you don't, you force all programs to handle return codes,
even if they don't want to, and oh yeah, even if they don't want
to but they do want to be robust, they still have to say

                defined $rc or return undef;

after every function they invoke, and they have to wrap every
function they invoke in eval { } and possibly handle $@ without
dieing too (which is what you forgot to do in your 100 hour
calculation).

How much would you pay for this--but wait there's more!  Once
you seperate normal API returns from API failures (using the
out-of-band $@ channel, shall we say), you can cleanly collect
debugging information on failures, without any extra cooperation
on the API client's behalf.

I've seen a lot of glamorous junk in this "industry" over the last
30 years.  Exception-based error handling isn't glamorous.  On the
other hand, it isn't junk either.  I have it from reliable sources
at lunch today that this fall the CS department at the University of
Alberta is adding the concepts considered in this message to their
second-year programming course, which means this stuff is going
from the fringe category to the "best practices" category.  Perhaps
Perl 6 should catch a ride.

In spite of all this, I really don't care what your module does.
If after evaluating it against my build/buy criteria I decide to
use it, I'm going to both wrap my interface to it in trys, *and*
test its return codes (if any).  I just want a way to make that
easier for me.

Structured exception handling is the right way to do errors.
All that RFC 88 does is make it take less code to do it right.

Of course, that's just my opinion, I could be wrong.

[Hmm, I was less convinced before I wrote this, than I am now, of
just that which I argue for herein.  Thanks, Chiam, for helping me
make this stuff clearer to me.]

Yours, &c, Tony Olekshy

Reply via email to