Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation

Re: Best Practices for Exception Handling (no isa)

by tye (Sage)
on Jan 29, 2003 at 03:59 UTC ( #230855=note: print w/replies, xml ) Need Help??

in reply to Best Practices for Exception Handling

I'm not a big fan of "validating inheritance" (especially in Perl where you can make perfectly functional objects [that simply implement all of the needed methods] that fail the 'isa' test) so rather than using UNIVERSAL::isa(), I'd probably either do something like have all error objects define an isError() method and add

sub UNIVERSAL::isError { 0 }
or have all error objects define a getError() method and test with $data->can("getError").

Better still, have your error objects overload boolean context so that they return "false" and just do

if( $data ) {
which starts heading toward what I've been wanting to write for years now: Error objects that fake all possible method calls to return themselves when called in a "object/method" context. They overload boolean context such that they return "false" and note that they have been "tested". If they get used in any other context or if they get destroyed without having been "tested", then they die with a full error message and stack trace.

Then you don't have to worry about checking for errors. If you don't check for success, then your code automatically dies on failure. If you do check for success but get it wrong, your code again dies on failures. If you do check for success and get it right, nothing dies. And you don't have to check right away so you can write:

# Dies if any method fails: $value= $Registry->SetOption(Delimiter=>"/") ->OpenKey("This")->GetValue("That"); # Doesn't die, no matter which method fails: if( ! $Registry->OpenKey("Foo")->GetValue("Bar") ) { # Create missing bits here }
(: [updated]

                - tye

Replies are listed 'Best First'.
Re^2: Best Practices for Exception Handling (no isa)
by adrianh (Chancellor) on Jan 29, 2003 at 13:53 UTC

    Cute idea!

    However, one of the reasons I prefer exceptions to returning any sort of error object is because they act outside the normal return path.

    The problem with "false == failure" is that it falls down as soon as you have "false" as a legal return value. For example, people often misuse read because it has different meanings for a false return value (0 == EOF, undef == error). People forget to check for the error condition - leading to nasty bugs.

    The same sort of thing applies to any sort of "funny" return value - which in hindsight was why I was uncomfortable with the code I wrote in Test::Exception extension with (evil?) overloading :-)

    With exceptions you don't have to worry about any confusion between legal return values and errors.

    Another advantage of exceptions is that I can separate my error handling code from the error producing code, which makes things like massaging errors between different application tiers so much easier.

      I like exceptions too. But it is a pain to use exceptions for all failures. Exceptions should be "exceptions" and not indications of a "normal" failure (at least, that is the most accepted practice in the areas I find myself working). The line between the two can be subtle.

      Trying to open a file for reading when that file does not exist would not usually throw an exception because that is an expected failure mode. Getting an error when reading from a file (besides "end of file", which is sometimes described as an error) should throw an exception.

      Aside: I find it interesting that you pick errors reading from files for your example. A few months back I was writing about best practices when reading files in Perl. I came to the conclusion that checking for non-end-of-file errors (that is, real errors) when reading from a file handle is just not worthwhile. 1) They are difficult to detect. 2) It is hard to imagine situations where such happens. 3) In the rare cases where it does happen, the best reaction is often to treat it like a premature end of file (except that it would be nice to note that the end of input processing was due to an error and what that error was). So I'd love to see the read primitives of Perl issue a warning if they "fail" for reasons other than end-of-file. But I'm not sure that throwing an exception for that case would be the best choice.

      So I'd be interested in hearing about the "nasty bugs" that you've run into related to read failures.

      So what is an exception and what is a "normal failure"? There are two ends to the spectrum, of course. Perl is close to the "all failures are normal" end, only throwing exceptions for extreme things like compilation failure. Perl 6 sounds like it will be close to the "all failures are exceptions" end.

      The problem with the former is that it is way too easy to forget to check for failure and so failures get ignored when they shouldn't. The problem with the latter is that it can be inconvenient to put catch blocks everywhere you should -- though this "problem" probably leads to better programming.

      So the case is pretty clear that we should go the route of Perl 6 and have all failures be exceptions. I'll probably agree except that this is Perl 5 and so most failures aren't going to throw exceptions, most Perl 5 programmers aren't used to dealing with exceptions, and Perl puts a big emphasis of programmer convenience. So in Perl 5, I want to drive down the middle of the road on this point.

      Which brings us back to: What is an exception and what is a "normal failure"? Take the "trying to open a non-existant file for reading" example. In the general case, it would be inconvenient for Perl 5 to throw an exception for this. In specific cases, you do want to throw an exception for this. The usual idiom for distinguishing the two cases is:

      open( FILE, "<" ) or die "Can't read $!\n";
      if( ! open( FILE, "<" ) ) { # Deal with the absense of this file }
      which begs the question, what should be done with this:
      open( FILE, "<" );
      In Perl 5, the answer is simply that this code is broken and should be fixed to one of the previous two cases. My answer is that Perl 5 is "broken" and should treat this like the first case automatically.

      And that is the point of my design for an error object. It makes it convenient to treat a failure as normal but automatically converts a failure into an exception when it should.

      So part of the point of my error object is to make it easy to generate exceptions for failures. So you should like it. The difference between my approach and yours is that my approach allows each caller to decide (or to not even think about it in which case the decision is made for them) what is normal failure and what is an exception. In your approach, the callee makes that decision for everyone and forces them to convert between the two cases when that decision doesn't match their situation.

      And the problems with converting are: 1) Converting from exception to handled failure is inconvenient in Perl 5. 2) Converting from normal failure to exception usually requires that the caller construct the error message when the callee usually is in a better position to do that. But the error object forces the callee to construct a useful error message while also allowing the caller to add to it (or ignore it).

                      - tye

        My bias lies towards the "all errors are exceptions" end of the spectrum :-) For me it helps more than it hinders, but I understand the distinction that you're making between "normal" and "exceptional" results.

        The nasty bugs caused by read all fall under the general heading of "files that should have been read totally being truncated". One instance was especially amusing since they "just knew" that the problem was with a bit of networking code that was moving the result of the read over the wire. They'd spent a week trying to track down an intermittant networking error, rather than the obvious (in hindsight) intermittant read problem.

        In general I like your idea - think it would make an interesting alternative to Fatal.

        However, the problem of dealing with subroutines that return false values "normally" means that it's not as useful in these cases since you have to make an explicit check for the error condition.

        For me, the inconvenience of the perl5 exception "syntax" is worth the other advantages it gives me.

        For me exception is a language construct - a flow of logic. While what is an error and what is 'normal failure' is defined by a particular program. I would rather expect that you'll find many situations other than error conditions where using exceptions would be beneficial. Of course you can argue that the exception construct is particulary well suited for errors, and not for 'normal failures', it just seems to me like mixing semantical levels when you distinguish between exceptions and 'normal' failures.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://230855]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2021-10-26 02:17 GMT
Find Nodes?
    Voting Booth?
    My first memorable Perl project was:

    Results (90 votes). Check out past polls.