Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Best Practices for Exception Handling

by Ovid (Cardinal)
on Jan 28, 2003 at 23:04 UTC ( [id://230799]=perlquestion: print w/replies, xml ) Need Help??

Ovid has asked for the wisdom of the Perl Monks concerning the following question:

I didn't program Java for very long, so I really never quite understood the best way to handle exceptions. (The one thing that I miss in Perl is compile-time checking of whether or not the exceptions are caught, but that's a side issue). Recently, I was struggling to get an API just write (sic) and the simplest way to do it seemed to be the following:

my $data = $self->__validate_added_data; if ( UNIVERSAL::isa( $data, 'Foo::Form' ) ) { # add the data } else { # process the error }

In other words, if I get back a valid Form object, I can add the data to the database. Otherwise, I have a hashref that contains the form error information. This error information is user error -- not program error. As a result, I don't want to die or warn, yet variants of those are typical Perl error handling mechanisms. Further, I have a method that returns two fundamentally different types of data: and object or a hashref. I finally started using the Error module and now use try/catch blocks to handle the types of errors that I need.

try { my $data = $self->__validate_added_data; # add the data } catch FormValidationError with { # process the error };

Combining this with Test::Exception has made my tests simpler and my code more self-documenting. However, I'm not entirely sure what are "best practices" for exception handling in this manner. Can anyone offer their wisdom?

Cheers,
Ovid

New address of my CGI Course.
Silence is Evil (feel free to copy and distribute widely - note copyright text)

Replies are listed 'Best First'.
Re: Best Practices for Exception Handling
by thraxil (Prior) on Jan 28, 2003 at 23:28 UTC

    Matt Sergeant points out (pdf) that Error.pm's try/catch is implemented with a function prototype of & that can create nastly, unexpected closures. his recommendation is to use Error.pm but use eval instead of the try/catch syntactic sugar.

    anders pearson

Re: Best Practices for Exception Handling (no isa)
by tye (Sage) on Jan 29, 2003 at 03:59 UTC

    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

      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, "<read.me" ) or die "Can't read read.me: $!\n";
        versus
        if( ! open( FILE, "<read.me" ) ) { # Deal with the absense of this file }
        which begs the question, what should be done with this:
        open( FILE, "<read.me" );
        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
Re: Best Practices for Exception Handling
by autarch (Hermit) on Jan 28, 2003 at 23:53 UTC
    Also take a look at Exception::Class, which somewhat overlaps with Error.pm. The two can be used together pretty easily, however.

      I'd add another vote for Exception::Class. It allows exceptions classes to be declared quickly and easily and has become my fave since discovering its existance.

      I tend to have a single module that declares all my exceptions. So for my Foo application I'd have something like:

      package Foo::Exceptions; use strict; use warnings; use Exception::Class ( 'Foo::Exception::DBI' => { description => 'DBI Error', # error => $DBI::errstr, }, 'Foo::Exception::DBI::Dupe' => { description => 'Duplicate key', isa => 'Foo::Exception::DBI', # error => name of duplicate key, }, 'Foo::Exception::Dupe' => { description => 'Duplicate entry', fields => [ 'name' ], # error => value of duplicate field }, # etc. );

      Comments are because Exception::Class objects have a default "error" field and I like to document what that should be used for where I declare the class.

      Throw exceptions like this:

      Foo::Exception::DBI->throw($DBI::errstr) Foo::Exception::Dupe->throw(name => $name, error => $value);

      Because of the closure issues raised by thraxil, I use the eval / if idiom instead of any of the modules that provide extra try/catch syntax.

      eval {$foo->something_that_might_die}; if (ref($@)) { if ($@->isa('Foo::Exception::Dupe') ) { warn "duplicate entry ", $@->name, " ($@)\n"; undef $@; }; }; die $@ if $@;
Re: Best Practices for Exception Handling
by pg (Canon) on Jan 29, 2003 at 05:56 UTC
    Here we actually have a mixture of two things, error handling and event handling. When error handling is about the purpose, event handling is more like a kind of method. They are mixed, because in some languages, it is possible for you to handle error as a type of event.

    For error handling, there are actually two main types of methods:

    1. In the first kind of method, there is no (visual) "jump" in the control flow of your program.

      For example the try catch block, when everything is okay, it just continue with the try block, otherwise continue with one of the catch blocks, depends on the type of error;
    2. Second method is to handle errors as events. In this case, there would be a jump of your control flow, and it would be redicrected to the event handler.

      For example, in languages like basic, pl*sql, pro*c, you can define on error goto at the beginning of your program, and specify the error handler. Whenever an error is detected, the error handler will be triggered. But those are very basic, and they are still deeply different from the modern event handling. The problem of of this kind of error handling is that, the programers only have a very limited freedom and method to define what is error and who should care what error.

      The modern error handling is totally based on OO design. A typical example is the event handling in Java. It provides the entire infrastructure to define event, event producer, event consumer, event listener (there is a subtle difference between event consumer and event listener, although most of the programmers like to mix the two into one, actually event listener is the channel thru which event consumer receives the event), and of course we talk about this here, because we can define error as event.

    As which method is the best, I would say the Java event handling infrastructure is absolutely the best. As that methodology fully satisfies OO principle. In this method three objects are clearly extracted: error, error producer, error consumer. The way/tool to catch error is also extracted as an object: event listener.

    In this way, the structure of your application is crystal clear, and you can easily break the application into small managable pieces, and those pieces can be easily designed, developed and maintained by multiple persons/teams, which fully satisfies the modern software design and project management principles.

    All your code can be easily reused. As everything is object, you can easily start with generic classes and gradually go down to more and more specific subclasses, and reuse whatever you defined at generic levels, at any more specific levels.

    For example, you can have an Error class, which covers all errors, then have a subclass called MalFormedURLError, and you can define attribute error_code, method set_error_code/get_error_code in Error class, thus being inherited in MalFormedURLError class.

    try catch block is a kind of very primitive way of error handling. It does not provide much benefit, in terms of reusing code and OO design.

    Well you can reuse some code, for example, to put your error handling code in a function, and call this function from more than one catch blocks. This is a kind of reuse, but very primitive.

    However, whether you can do it in a modern event handling way, is not really just your choice, it also depends on whether your language supports it.

    Perl is based on c, and unfortunately c and c++ does not provide strong support for event handling (although the base is there, but it does not go much higher). Java did a lot of work on its own, and changed the entire event handling into a kind of art.

    But we still see some event handling in Perl, for example Tk is event driven. Another example would be signal handling.

    Another thing worths to mention is that, lots of c/c++ programmers actually use an alternative way to mimic modern event handling to a certain level, that is to heavily utilize the concept of call-back function, thus allow the user of a package to define/overwrite the default error handling behavior provided in libraries/packages.

      I'm happy in my understanding of the utility of a hierarchy of error classes. I'm also happy with my understanding of event-based models.

      However, I'm reading the above as you saying that an event-based model for handling errors is better than throw/catch in the general case.

      I can understand this if you have an event-based application since there is an obvious fit - but not in the general case. Am I missing something? Misunderstanding what you said?

      Example?

        I Agree.

        But you might have missed ;-) a tiny thing in my post, I actually said that whether you could use event-driven was not just the programmer's choice, but really depended on whether your language supported it. So we are on the same page.

        At the same time, I want to stress that, if you are using things support event-driven, like Java, you should go ahead use it. It might sounds scary at the beginning, but one should try to utilize the best he can grab. After created your first event listener in Java, you would find it is actually nothing difficult.

      Perhaps some who want to know more about error handling in general might find Common Lisp's take on it interesting, for which I refer them to a paper of Kent Pitman

        Kent Pitman's paper has moved to a new home URL:

        http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html

        This paper was originally written as an HTML document, exactly as shown below. Any final reformatting that was done for hardcopy publication in LaTeX may have been lost in this version. Any new text that has been added for this annotated version appears bracketed and in color green; such text is intended to help clarify the historical context as time passes.
        --Kent Pitman, 28-Feb-2002.
Re: Best Practices for Exception Handling
by kabel (Chaplain) on Jan 29, 2003 at 06:30 UTC
    there was an article on perl.com about that. perhaps it is of some use.

    it utilizes (amongst others) the Error.pm try/catch approach together with Fatal.pm.
Re: Best Practices for Exception Handling
by Anonymous Monk on Jan 29, 2003 at 00:17 UTC
    I've always used perl's EVAL command: my $val;

    eval {
     my $val = $self->do_command();
    };

    warn " do_command failed with the following error \n\n@_" if ("@_" ne "");

    - adam

      my method is similar, but different...

      ## let's say bar is a method in foo sub bar { my( $self, @args )= @_; eval { $self->method( @args ) }; $@ and $self->raise_error( @_ ) and return undef; } ## called like: my $foo= foo->new(); my $result= $foo->bar( @args ) or warn $foo->Error();

      the method sets an error and returns undef, allowing the caller to deal with the error as it sees fit. of course, you might want to handle a method that can return 0 by using defined

      ~Particle *accelerates*

        of course, you might want to handle a method that can return 0 by using defined
        Or use the trick that some other modules use: return "0E0" or "0 but true" for zero, as a string. These are both true and 0, and do not produce warnings when converted into a number, the former because it's a normal floating point format, the latter because it's a hardcoded exception in perl.

        Now I come to think of it: you can use any normal format for zero, as long as it's not "0", for example "0.0".

        Personally I think the following little trick/modification makes for cleaner code... (and I think that raise_error makes more sense if it contains the error message returned...)
        sub bar { my( $self, @args )= @_; eval { $self->method( @args ); 1} or $self->raise_error( $@, @_ ) and return undef; }
        If you can contrive to make raise_error() return undef you can make it even cleaner
        sub bar { my( $self, @args )= @_; eval { $self->method( @args ); 1} or return $self->raise_error( $@, @_ ); }

        --- demerphq
        my friends call me, usually because I'm late....

        Cool.. that's the way I like to handle errors too; but I always wonder if it's the *best* way. This is an interesting thread - Ovid++

        Jon

      warn " do_command failed with the following error \n\n@_" if ("@_" ne "");

      Of course you meant $@, right? ;-)

      Jon

Re: Best Practices for Exception Handling
by Anonymous Monk on Jan 28, 2003 at 23:35 UTC
    "Exception handling" and "BestPractices" seems like a contradiction to me.
    Unless you are developing real-time systems. And who writes real-time code
    in Perl?
    I guess I am just too old school.

      I tend to agree. A lot of people with a lot more experience than I have rave about how wonderful exception handling is, but they've failed to communicate to me why it is valuable to be able to throw an exception in one place and catch it someplace else rather than handling the problem (assuming it can be handled by any means other than spitting an error message and bailing) in the same block of code where it is detected. It seems to me that having the exception and the handling thereof separated by arbitrarily many lines of code is asking for headaches in terms of code maintenance. Rather than throwing the exception in the first place, wouldn't it be better to handle it en situ? (Sure, sometimes a number of spots can share the same code -- so call a subroutine then, but at least the call is right there.)

      As I said, I have a good deal less experience than a lot of the people who rave about exception handling, so I'm probably missing something...

       --jonadab

        Some reasons I like exceptions:

        1. Robustness. I can forget to check for an returned error value. I cannot forget to check for an exception.

        2. Brevity. I prefer:

          $o->foo->bar->fribble->ni

          to

          $o->foo or return(ERROR_FOO); $o->bar or return(ERROR_BAR); $o->fribble or return(ERROR_FRIBBLE); $o->ni or return(ERROR_NI);
        3. Clarity. With exception based code the "normal" flow of control is more explicit because it is not obscured by error handling code. I think that the first of the two code examples above shows the intent of the code more directly than the second does.

        4. Separation of concerns. The error condition and the error handler are different ideas.

          • You may want an error to be handled in different ways depending on the context.
          • You may also not know how the error should be handled at the point it occurs.
          • You may not know how the error should be handled at the time you write the code.

          With the return-error-code style you end up having to either:

          • propogate error conditions to where the decision on how they should be handled can be made.
          • propogating error handlers down to where the errors may occur

          Both options rapidly become messy if there are many levels of code between the error condition and the error handler.

        5. No confusion between return values and error conditions.

        There are probably some more ;-)

        I believe that the idea is that you can't always do something on the spot.

        For example, suppose I am developing a flashcard application that reads flashcards from a file and displays them in a pretty window. To accomplish this, I decide to write a couple of classes that do the actual file reading and parsing, etc., in order to keep that separate from the gui code.

        So what happens if, for example, the gui asks one of those objects to open a non-existant file? An elegant way out of the problem is for the object to raise an exception. The gui can then choose to catch that, and pop up a dialog, or whatever.

        It is definitely not the *only* way to solve the problem, though. You could also, for example, have every method return error codes. Or a hash containing an error code plus whatever other value it wants to return. Exceptions do provide a very clean way to do it, though.

        Just my $.02,
        -Dan

        Java Boutique has an interesting article about exceptions that you might find useful.

        What I like about exceptions is fairly straightforward. If I have a GUI (A) which issues a message to some object (B) which in turn sends a message requesting data from object (C), what happens if C determines that the data is bad due to user input (and not, say, a programming error)? There's probably no point in issuing a warning in the error log (because it's not an error from a programming standpoint) and there's no point in killing the application because a user typed something stupid (an all too common event, though).

        In the above scenario, C throws in exception and B catches it. B then checks the exception to see if anything unusual needs to be done. If the exception is caused by failing to select a required option, no big deal. If the exception is caused by a bad password, perhaps it is a big deal. B can decide whether or not to log the exception and do further processing. B, at this point, rethrows the exception and A catches it. Because A is simply the GUI, A doesn't worry about logging the exception. A simply has to have some method of displaying the exception to the end user in a meaningful way.

        Thus, with an exception based system, particularly if it's multi-tiered, you have full control of the error processing rather than simple die and warns. Further, uncaught exceptions will kill the program when encountered during execution, thus making them morely likely to be reported in testing and feedback -- which in turns means they are more likely to be handled appropriately and leads to more robust code.

        Cheers,
        Ovid

        New address of my CGI Course.
        Silence is Evil (feel free to copy and distribute widely - note copyright text)

        Handling the error in place and consulting the calling code for advice on handling it are not necessarily mutually exclusive, for which I point you to the paper referenced in another node of mine in this discussion which (i.e. the paper) describes how things are handled in Common Lisp.

Re: Best Practices for Exception Handling
by yosefm (Friar) on Jul 11, 2003 at 14:43 UTC
    Another way of treating an error or warning as an event (which I have only learned a while ago and found useful) is to define handlers for it using:

    local $SIG{__WARN__} = \&HandlerSub; local $SIG{__DIE__} = \&OtherHandler;

    But the problem with it is that you really must pay attention to your use of warn and die, as well as conditions when something not written by you might die.

      There are problems when you mix the signal handling style with other modules that use exception handling, or use it in persistent environments like mod_perl so (in my opinion) it is best avoided.

      Alternative Exception Handling Techniques in the mod_perl guide has more info on this. Also see the documentation on $^S (in perlvar) and die.

      It's often simpler just to override CORE::GLOBAL::die and CORE::GLOBAL::warn.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://230799]
Approved by valdez
Front-paged by Enlil
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (5)
As of 2024-03-19 07:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found