开发者

What are the best practices for error handling in Perl?

开发者 https://www.devze.com 2022-12-31 03:15 出处:网络
I\'m learning Perl, and in a lot of the examples I see errors are handled like this open FILE, \"file.txt\" or die $!;

I'm learning Perl, and in a lot of the examples I see errors are handled like this

open FILE, "file.txt" or die $!;

Is die in the middle of a开发者_运维百科 script really the best way to deal with an error?


Whether die is appropriate in the middle of the script really depends on what you're doing. If it's only tens of lines, then it's fine. A small tool with a couple hundred lines, then consider confess (see below). If it's a large object-oriented system with lots of classes and interconnected code, then maybe an exception object would be better.

confess in the Carp package:
Often the bug that led to the die isn't on the line that die reports. Replacing die with confess (see Carp package) will give the stack trace (how we got to this line) which greatly aids in debugging.

For handling exceptions from Perl builtins, I like to use autodie. It catches failures from open and other system calls and will throw exceptions for you, without having to do the or die bit. These exceptions can be caught with a eval { }, or better yet, by using Try::Tiny.


Since I use Log::Log4perl almost everywhere, I use $logger->logdie instead of die. And if you want to have more control over your exceptions, consider Exception::Class.

It is better to catch your exceptions with Try::Tiny (see its documentation why).


Unless you've got a more specific idea, then yes you want to die when unexpected things happen.

  • Dying at the failure to open a file and giving the file name is better than the system telling you it can't read from or write to an anonymous undefined.

  • If you're talking about a "script", in general you're talking about a pretty simple piece of code. Not layers that need coordination (not usually). In a Perl Module, there is an attendant idea is that you don't own the execution environment, so either the main software cares and it catches things in an eval, OR it doesn't really care and dying would be fine. However, one you should try at a little more robustness as a module and just pass back undefs or something.

  • You can catch whatever dies (or croaks) in an eval block. And you can do your more specific handling there.

  • But if you want to inspect $! then write that code, and you'll have a more specific resolution.

  • Take a look at the near-universal standard of using strict. That's code that dies on questionable syntax, rather than letting you continue along.

So I think the general idea is: yes, DIE unless you have a better idea of how things should be handled. If you put enough foresight into it, you can be forgiven for the one or two times you don't die, because you know you don't need to.


The more modern approach is to use the Carp standard library.

use Carp;
my $fh;
open $fh, '<', "file.txt" or confess($!);

The main advantage is it gives a stack trace on death.


I use die but I wrap it in eval blocks for control over the error handling:

my $status = eval
{
    # Some code...
};

If the 'eval' fails:

  1. $status will be undefined.
  2. $@ will be set to whatever error message was produced (or the contents of a die)

If the 'eval' succeeds:

  1. $status will be the last returned value of the block.
  2. $@ will be set to ''.


As @friedo wrote, if this is a standalone script of a few lines, die is fine, but in my opinion using die in modules or require'd piece of code is not a good idea because it interrupts the flow of the program. I think the control of the flow should be a prerogative of the main part of the program.

Thus, I think, in module it is better to return undef such as return which would return undef in scalar context and an empty list in list context and set an Exception object to be retrieved for more details. This concept is implemented in the module Module::Generic like this:

# in some module
sub myfunc
{
  my $self = shift( @_ );
  # some code...
  do{ # something } or return( $self->error( "Oops, something went wrong." ) );
}

Then, in the caller, you would write:

$obj->myfunc || die( "An error occurred at line ", $obj->error->line, " with stack trace: ", $obj->error->trace );

Here error will set an exception object and return undef.

However, because many module die or croak, you also need to catch those interruption using eval or try-catch blocks such as with Nice::Try

Full disclosure: I am the developer of both Module::Generic and Nice::Try.

0

精彩评论

暂无评论...
验证码 换一张
取 消