Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Best practices for handling errors

by v_melnik (Scribe)
on Sep 27, 2014 at 11:31 UTC ( [id://1102209]=perlquestion: print w/replies, xml ) Need Help??

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

Dear colleagues,

I think, it's a matter of religion, but I'd like to get to know more on how other people, more experienced, prefer to handle errors/exceptions in respect to the structure of your programs.

Let me describe how I'm doing it now and, if you have some time to share your experience, I'd be very grateful to you for describing how do you prefer to do it.

My own "rules" for myself are quite simple.

  1. Don't die() while executing a subrotine or method. Only the main module can die() if something goes wrong. Nobody can predict where the class will be used, so an unexpected die() can break the caller's logic.
  2. If I've got an exception inside of a subroutine, the subroutine may return(undef). If everything's fine, it return's some value (it can be true or false - no matter), but if some error has been occuried (e.g. if we can't get data from the database), the undef shall be returned.

That's okay, but how to let the caller know what's happened with the subroutine? As I think, the caller must have some explaination to be able to write something to the log-file or to show the error message to the operator. So, there is one more rule.

  1. Any class may have the "errstr" attribute, so if its' methor returned undef, the caller may get the explaination from this attribute.

So, usually it looks like this:

package SomeClass; #... sub some_method { # ... eval { die("Oops!"); }; if($@) { $self->{'errstr'} = "Something has gone wrong: $@"; return(undef) } # ... } #... package main; #... my $result = $obj->some_method; unless(defined($result)) { die("Can't SomeClass->some_method(): $obj->{'errstr'}"); } #...

And, when something goes wrong, I can get something like that:

Can't SomeClass->some_method(): Can't AnotherClass->another_method(): Can't OtherClass->other_method(): Can't open(): No such file at script.pl line 666.

Frankly speaking, I have a persistent feeling that there are some other, much more elegant way to do it. And I hate how the final error message looks like. Just like "can't A, because can't B, because can't C, because f*** you". Ugh... :(

And there is another annoying thing: I have to use die() in the constructor of an object (I mean new()), because if the constructor returns undef, the caller doesn't have an access to the object's "errstr" attribute at all (as we don't have the object bless()ed). So I have to always call constructors from eval()-blocks and get the explaination from $@.

package SomeClass; #... sub new { # ... eval { die("Oops!"); }; if($@) { die("Something has gone wrong: $@"); } # ... } #... package main; #... my $obj = eval { $obj->new }; unless(defined($obj)) { die("Can't SomeClass->new(): $@"); } #...

I absolutely hate it, but I don't see better ways to let the caller know why the object hasn't been blessed.

Maybe I should consider using some global variable to keep the reference to the stack of errors occuried? What do you think?

Maybe I should always die() (or confess() - as a better way to get to know who has called whom) inside of any method and call every method inside of eval()- or try()-block?

I'll be very grateful to each of you for sharing your best practices on this matter. It really makes me feel unsatisfacted. :)

Have a nice time!

UPD: Now I see, why it's better to use exceptions such as die(), croak() or even my own exception classes based on Throwable::Error superclass instead of returning undef's or setting flags. Lots of thanks to all!

V.Melnik

Replies are listed 'Best First'.
Re: Best practices for handling errors
by Your Mother (Archbishop) on Sep 27, 2014 at 18:54 UTC
    Don't die() while executing a subroutine or method. Only the main module can die() if something goes wrong. Nobody can predict where the class will be used, so an unexpected die() can break the caller's logic.

    How about looking at it more like this? The sub or method is a table saw. With the logic above, only the operator can decide to turn the saw off, i.e., die. For the logic to work reasonably, the main program must have all knowledge of all internal code paths and wrinkles to know when die is appropriate. This is obviously unreasonable and untenable except in the most trivial case. It’s too likely to get through a huge execution chain with a bad result without having any idea where it went bad. Or why it’s harder to turn the saw off when your fingers are on the floor. So, for my part–

    Die early, die often (using Carp as suggested elsewhere). This is why, I’d guess, nearly every single experienced monk here would tell you to set RaiseError => 1 in your DBI handles. Again, consistency is always the most important part whichever road you take.

Re: Best practices for handling errors
by eyepopslikeamosquito (Archbishop) on Sep 27, 2014 at 22:33 UTC

    From essential practice number eight in Conway's Ten Essential Development Practices, "Throw Exceptions Instead of Returning Special Values or Setting Flags" (also covered in Perl Best Practices, Chapter 13, Error Handling):

    The bottom line: regardless of the programmer's (lack of) intention, an error indicator is being ignored. That's not good programming.

    Though you may not agree with all Conway's arguments, as indicated at Two Different Languages: Two Similar Books (PBP and CCS) (in the "Some Similar Guidelines Found in Both Books" section), throwing exceptions rather than returning flags seems to have become mainstream error handling advice nowadays.

    Update: All 255 Perl Best Practices listed here: Perl Best Practices Summary

Re: Best practices for handling errors
by Your Mother (Archbishop) on Sep 27, 2014 at 23:03 UTC

    In fact, after reading eyepopslikeamosquito’s response I'm reminded of this kind of approach. It’s not very hard, it can DWIW a little with overload, and it can be very useful/robust compared to matching for content against error strings–

    use 5.014; # Safer eval+$@ handling. use strictures; package HURR_DERR_ERR { use Moo; use Devel::StackTrace; use overload '""' => sub { +shift->as_string }; has code => is => "ro", default => sub { "[uncoded]" }; has message => is => "ro", required => 1; has stacktrace => is => "ro", default => sub { Devel::StackTrace->new->as_string }; sub as_string { my $self = shift; join ": ", $self->code, $self->message; } }; for ( 1 .. 100 ) { eval { die HURR_DERR_ERR->new( message => "Unlucky number encountered +: $_" ) if /\b4\b/; # You'll have to localize if you don't want Ch +inese values. }; if ( $@ ) { # If you want it -> say $@->stacktrace; die "Got an error -> $@"; } else { say; } }
    1 2 3 Got an error -> [uncoded]: Unlucky number encountered: 4 at example.pl + line 33.

    Update: changed “script name” in error output. Update #2: changed version per suggestion.

      You might want to make that use 5.014;, since that's when eval {...}; if ($@) {...} became safe(r) (see Exception Handling).

        Good note. I use 5.16 and 5.20 normally. The 5.12 was boilerplate and I considered using Try/Try::Tiny in it but the example was long enough already.

Re: Best practices for handling errors
by Anonymous Monk on Sep 27, 2014 at 12:31 UTC

    Two threads about similar topics with helpful replies: Croak, return et al. and Using die() in methods - the latter is yours!

    All I have to add at the moment is that your concatenation of error messages is beginning to look like a stack trace, so you really should look into Carp - I'd say Carp is a best practice for throwing errors from modules. (Also, your use of eval still isn't reliable, as mentioned in both links above.)

      Thanks for replying!

      So, it would be better to use Carp's confess() or other backtracing tools (such as Devel::StackTrace, right?) for generating a stack trace and don't add anything to the error message, passing it to the topmost caller without any additions, right?

      And, yes, this usage of eval() has caveats, so I'm going to get used to Try::Tiny or TryCatch instead of it.

      May I ask you, what do you personally think on using die(), croak() or confess() inside of a method instead of returning undef?

      Thank you!

      V.Melnik

        Use Carp's croak and carp instead of die and warn, and then you can optionally turn on the stack traces as documented in Forcing a Stack Trace. Don't build your own stack traces.

        As always TIMTOWTDI, but here's how I might do what you asked:

        • If it's common for a method to "fail" and it can be represented by undef, that's probably what I'd do instead of forcing my callers to eval all the method calls.
        • An error internal to the method that really shouldn't have gone wrong, e.g. assertions: die or confess (because I want to know exactly what happened where)
        • Something that the caller of the method did wrong, such as pass a bad argument: croak, definitely
        • If my method calls another method that might die or croak, it really depends on the situation, but often it makes sense to simply have that error be passed along.

        This last point is something I've learned from my experience with exceptions: If the exceptions are designed properly, meaning they are true errors and not a normal mechanism to return values, then very rarely should any layer of my program be catching exceptions without re-throwing them except the very outermost layer. In a GUI, I'd like errors to be displayed to a user, in a daemon, they need to go into a log or email, and in command-line programs, they need to be displayed on the console. If a program has both (a) "exceptions" that mean something went very wrong and the program needs to terminate and (b) "exceptions" that mean a particular operation failed and the program may continue running, there needs to be a clear way for the program to distinguish these two types of exceptions. One way to do that is have (a) be fatal errors (e.g. die and friends) and (b) be special return values (e.g. undef). Sometimes you might be working with external libraries that take a different approach than yours, meaning they die more often, or they never die and return undef instead, that's one case where I might "transform" these exceptions using eval { ... ; 1} or return undef or defined(my $x = foo()) or die "foo() failed".

Re: Best practices for handling errors
by Laurent_R (Canon) on Sep 28, 2014 at 09:21 UTC
    Frankly, if something really goes wrong in a sub so that the very purpose of the program can't be achieved, I do not see any reason not to die (or, better, to croak or possibly to confess) within the sub.

    I am not saying that you should always die when something goes wrong, it depends on the context, sometimes you can recover from an error (for example, in an interactive program, you would probably want to give your user another chance to give the right arguments to your program), I am only saying that when an error is severe enough, especially in a non-interactive program, there is no reason to bend over backward to avoid using die or an equivalent within a sub.

Re: Best practices for handling errors
by shmem (Chancellor) on Sep 28, 2014 at 10:11 UTC

    My take on that: since objects are blessed, they have to confess any deadly sin committed at home.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: Best practices for handling errors
by sundialsvc4 (Abbot) on Sep 29, 2014 at 02:28 UTC

    There are many different opinions on this.   In Python circles, for example, there’s a truism that “it’s easier to ask forgiveness than permission,” so a widely accepted practice is to “just do it,” relying upon an exception (of a particular class) being thrown if it turns out that you can’t.   Exceptions are thrown in all sorts of benign circumstances, including “to end an iterator-loop.”   Ordinarily, code which uses the try..except..finally constructs check for a particular exception-class or classes, intercepting only those that are expected.   (As expected, exceptions which are never caught by anyone typically kill the program.)

    And it turns out that Perl can do this, too.   The die() function can accept any object, not just a string, and there are several “exception class hierarchy” packages available to exploit this capability.

    As it happens, I find myself using “classed exceptions” frequently, throwing exceptions in basically all of the situations where I might previously have used “a non-zero return code,” but also for benign purposes.   It’s much too late to change the name of Perl’s die() function, as if we ever would want to, but it is a bit misleading.   You are raising, or “throwing,” an exception ... which may or may not be “a bad thing.”

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (3)
As of 2024-03-19 04:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found