13

我有一段类似于以下的 Perl 代码(非常简化): 有一些级别的嵌套子例程调用(实际上是方法),一些内部的调用自己的异常处理:

sub outer { middle() }

sub middle {
    eval { inner() };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }

现在我想更改该代码,使其执行以下操作:

  • 为每个“冒泡”到最外层 ( sub outer) 的异常打印完整的堆栈跟踪。具体来说,堆栈跟踪不应停止在“ eval { }”的第一级。

  • 不必更改任何内部级别的执行。

现在,我这样做的方法是在sub中安装一个本地化__DIE__处理程序:outer

use Devel::StackTrace;

sub outer {
    local $SIG{__DIE__} = sub {
        my $error = shift;
        my $trace = Devel::StackTrace->new;
        print "Error: $error\n",
              "Stack Trace:\n",
              $trace->as_string;
    };
    middle();
}

[编辑:我犯了一个错误,上面的代码实际上并没有按照我想要的方式工作,它实际上绕过了middlesub 的异常处理。所以我想问题应该是:我想要的行为是否可能?]

这完美地工作,唯一的问题是,如果我正确理解文档,它依赖于明确弃用的行为,即__DIE__处理程序被触发的事实,即使是 " die"s 内部的 " eval { }"s,他们真的不应该这样做。两者都perlvar声明perlsub这种行为可能会在 Perl 的未来版本中被删除。

有没有另一种方法可以在不依赖已弃用的行为的情况下实现这一目标,或者即使文档另有说明也可以节省依赖?

4

3 回答 3

11

更新:我将代码更改为全局覆盖die,以便也可以捕获来自其他包的异常。

以下内容是否符合您的要求?

#!/usr/bin/perl

use strict;
use warnings;

use Devel::StackTrace;

use ex::override GLOBAL_die => sub {
    local *__ANON__ = "custom_die";
    warn (
        'Error: ', @_, "\n",
        "Stack trace:\n",
        Devel::StackTrace->new(no_refs => 1)->as_string, "\n",
    );
    exit 1;
};

use M; # dummy module to functions dying in other modules

outer();

sub outer {
    middle( @_ );
    M::n(); # M::n dies
}

sub middle {
    eval { inner(@_) };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }
于 2009-06-09T17:36:51.680 回答
9

依赖文档中所说的已弃用的任何内容是不安全的。该行为可能(并且可能会)在未来的版本中发生变化。依赖已弃用的行为会将您锁定在当前运行的 Perl 版本中。

不幸的是,我没有看到符合您标准的解决方法。“正确”的解决方案是修改内部方法以调用Carp::confess而不是die删除自定义$SIG{__DIE__}处理程序。

use strict;
use warnings;
use Carp qw'confess';

outer();

sub outer { middle(@_) }

sub middle { eval { inner() }; die $@ if $@ }

sub inner { confess("OH NOES!") }
__END__
OH NOES! at c:\temp\foo.pl line 11
    main::inner() called at c:\temp\foo.pl line 9
    eval {...} called at c:\temp\foo.pl line 9
    main::middle() called at c:\temp\foo.pl line 7
    main::outer() called at c:\temp\foo.pl line 5

由于无论如何您都将死去,您可能不需要捕获对inner(). (您的示例中没有,您的实际代码可能会有所不同。)

在您的示例中,您试图通过$@. 你不能那样做。采用

my $x = eval { inner(@_) };

反而。(我假设这只是简化代码以将其发布到此处的错误。)

于 2009-06-09T17:27:10.523 回答
4

请注意,覆盖die只会捕获对 的实际调用die而不是像取消引用这样的 Perl 错误undef

我认为一般情况是不可能的。的全部意义eval在于消耗错误。正是由于这个原因,您可能能够依赖已弃用的行为:目前没有其他方法可以做到这一点。但是我找不到任何合理的方法来在每种情况下获取堆栈跟踪,而不会破坏已经存在的任何错误处理代码,无论堆栈有多远。

于 2009-06-09T18:17:04.067 回答