Wednesday 24 March 2010

Basic exception handling in Perl

Basic exception handling in Perl

Basic try catch pattern in perl
http://www.perlfoundation.org/perl5/index.cgi?exception_handling

[copy-paste]
The most basic way to handle exceptions in Perl is to use an eval BLOCK and have $@ transmit the error.
eval {                          # try
    ...run the code here....
    1;
} or do {                       # catch
    ...handle the error using $@...
};

Because the eval block ends with a true statement, it will always return true if it succeeds and false otherwise.
$@ can be a simple string, reference or an object, and is "thrown" by die

Dangers of using $@

The more common, but subtly flawed, idiom is to check $@ to see if the eval failed.
# FLAWED!
eval {                           # try
    ...run the code here...
};
if( $@ ) {                       # catch
    ...handle the error using $@...
}

This method is essentially non-atomic and has proven finicky. The problem lies with using $@. As it is global and there are many things which can reset it, either intentionally or otherwise, between the eval block failing and when it is checked.
=====
Other blog entry about error handling

http://perliscope.blogspot.com/2009/11/perl-error-handling.html


=====
[update]
CPAN modules

Try::Tiny # simple and functinal
TryCatch # has more dependencies but is nicer than Try::Tiny

Error
Exception::Class
Other modules are useful for checking exception handling during testing:


========

== EDITED 2010-10-15==
See new discusion in perlmonks

 Best Practices for Exception Handling

        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 ;-)
Re: Re: Best Practices for Exception Handling
by IlyaM (Parson) on Jan 30, 2003 at 09:54 UTC The fact is that when you write code in modular fashion one part of your system cannot always know how to handle errors itself. In such cases the only thing you can do is pass error somewhere else and there are in general two ways to do it: exceptions and return codes. And exceptions is just a more robust way to do it.
Example: say you are implementing business logic for your application which has multiple frontends (CLI, web and GUI). This part of your application encounters an error (let say a database connection error). What should it do? Print HTML page with error? Produce plain text formated error message for CLI? Write something in the log? No, it is not responsiblity of this part of your system to do these things, it is responsiblity of the frontend part to handle this error. So you just raise an exception and let the frontend to handle it.

Do you agree that the logic responsible for asking users for a better input belongs to the user interface part and it is not a part of the business logic? I.e. business logic layer have to callback user interface part when it tries to do the error recovery. So you still have to pass control to the callee to do the error recovery that business logic part cannot do on its own as if you were using exception or return codes style of error handling. To me it looks that the error recovery mechanism is essantially the same in both cases. The only difference is added complexity of callback approach. Let's plot a couple of diagrams. Traditional approach (exceptions or return codes) for case when the business logic part bails with error:
UserInterface BusinessLogic | user input | |------------------------------>| | do some action | |<----------failure-------------| | handle the error | |
If at the last point the user interface part can handle the error it can either ask the business logic part to redo the action or if this error is unrecoverable print diagnostic error or do something else. Now callback approach:
UserInterface BusinessLogic UserInte +rface | user input | |------------------------------>| | do some action | |----------failure------------->| | handle th +e error | |
This diagram clearly show two problems:
  1. This design adds additional requirement on reentrability for at least the user interface and probably for other parts of the system if the error recovery callback calls them.
  2. If error recovery callback can handle the error then program flow is clear. It returns the control to the business logic part which in its turn returns the control to the callee, i.e. back to the user interface part. But what about another case when it cannot handle the error? You still have to return the control back using either exceptions or return codes! Why then bother with callbacks at all?

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( $@, @_ );
}

6 comments:

zby said...

This is interesting - but if we need to assume that $@ can contain something else then the error we are catching - then we also need to abstain from checking $@ in the

} or do { # catch
...handle the error using $@...
};

fragment.

Anonymous said...

Note that AFAICT this blog is just a bunch of copy-paste from popular Perl sites.

NPEREZ said...

Please see the Try::Tiny and TryCatch modules. This is a solved problem.

Pablo Marin-Garcia said...

[to anonymous] yes, many of the entries about perl here are copy paste from popular Perl sites. You need to blame google and the closing of google-notebook ;-). So I am keeping here some interesting notes for myself, and puting the original link I giving to the source more google points ;-).

Pablo Marin-Garcia said...

[to zby] I think that $@ in the catch block will contain the right error message. This is the reason of recommending atomic try/catch with the 'or' in order to prevent $@ gets changed by other 'issues'. The point here is to discourage doing the eval{} and tenting $@ separately.

Pablo Marin-Garcia said...

[to nperez] I wanted to stress that eval{}......if($@) is not a very good practice and that eval{}or do {} would be a better practice. But you are right and I should add some CPAN modules that deal with this like

TryCatch # has more dependencies but is nicer than Try::Tiny

Error
Exception::Class