我需要能够在动作可能会死的词块中添加动作。而且我需要正常抛出异常并且能够被正常捕获。
不幸的是,在DESTROY期间Perl特殊情况下的例外情况都是通过在消息中添加“(清理)”并使它们无法覆盖。例如:
{
package Guard;
use strict;
use warnings;
sub new {
my $class = shift;
my $code = shift;
return bless $code, $class;
}
sub DESTROY {
my $self = shift;
$self->();
}
}
use Test::More tests => 2;
my $guard_triggered = 0;
ok !eval {
my $guard = Guard->new(
#line 24
sub {
$guard_triggered++;
die "En guarde!"
}
);
1;
}, "the guard died";
is $@, "En guarde! at $@ line 24\n", "with the right error message";
is $guard_triggered, 1, "the guard worked";
我想要通过。目前,eval完全吞下了这个例外。
这适用于Test :: Builder2,所以除了纯Perl之外我不能使用任何东西。
根本问题是我有这样的代码:
{
$self->setup;
$user_code->();
$self->cleanup;
}
即使$ user_code死掉也必须进行清理,否则$ self会进入奇怪的状态。所以我这样做了:
{
$self->setup;
my $guard = Guard->new(sub { $self->cleanup });
$user_code->();
}
复杂性的出现是因为清理运行任意用户代码,并且它是一个用例,代码将会死亡。我希望这个例外可以被警卫所取代,并且不会改变。
由于改变堆栈的方式,我正避免将所有内容包装在eval块中。
答案 0 :(得分:2)
这在语义上是否合理?根据我的理解,你有这个(伪代码):
try {
user_code(); # might throw
}
finally {
clean_up(); # might throw
}
有两种可能性:
user_code()
和clean_up()
永远不会投入相同的运行,在这种情况下,您可以将其编写为顺序代码而不会有任何有趣的后卫业务,并且它将起作用。user_code()
和clean_up()
可能会在某个时刻同时进行。如果两个函数都可能抛出,那么您有两个活动异常。我不知道任何语言可以处理多个当前抛出的活动异常,我确信这是有充分理由的。 Perl添加(in cleanup)
并使异常无法应用; C++ calls terminate()
,Java drops the original exception silently等等。
如果您刚刚eval
user_code()
和cleanup()
引发了异常,那么您希望在$@
中找到什么?
通常这表示您需要在本地处理清理异常,可能忽略清理异常:
try {
user_code();
}
finally {
try {
clean_up();
}
catch {
# handle exception locally, cannot propagate further
}
}
或者你需要选择一个异常以便在两次抛出时忽略(这是DVK的解决方案所做的;它忽略了user_code()异常):
try {
user_code();
}
catch {
$user_except = $@;
}
try {
cleanup();
}
catch {
$cleanup_except = $@;
}
die $cleanup_except if $cleanup_except; # if both threw, this takes precedence
die $user_except if $user_except;
或以某种方式将两个异常组合成一个异常对象:
try {
user_code();
}
catch {
try {
clean_up();
}
catch {
throw CompositeException; # combines user_code() and clean_up() exceptions
}
throw; # rethrow user_code() exception
}
clean_up();
我觉得应该有办法避免在上面的例子中重复clean_up()
行,但我想不出来。
简而言之,在不知道你认为两个部分都会发生什么的情况下,你的问题就无法解决了。
答案 1 :(得分:0)
更新:以下方法似乎没有像Eric所说的那样有效。
我将离开这个答案,万一有人可以把它变成工作状态。
问题是:
我预计,一旦本地超出范围将弹出旧的全局值返回全局绑定变量将涉及对FETCH / STORE的调用,但不知何故它只是静默地发生而没有达到绑定机制(问题与异常处理)。
Schwern - 我不是100%确定你可以使用领带技术(从Perlmonks post by Abigail偷来的)用于你的用例 - 这是我试图做我认为你想做的事情
use Test::More tests => 6;
my $guard_triggered = 0;
sub user_cleanup { $guard_triggered++; die "En guarde!" }; # Line 4;
sub TIESCALAR {bless \(my $dummy) => shift}
sub FETCH { user_cleanup(); }
sub STORE {1;}
our $guard;
tie $guard => __PACKAGE__; # I don't think the actual value matters
sub x {
my $x = 1; # Setup
local $guard = "in x";
my $y = 2; #user_code;
}
sub x2 {
my $x = 1; # Setup
local $guard = "in x2";
die "you bastard"; #user_code;
}
ok !eval {
x();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n", "with the right error message";
is $guard_triggered, 1, "the guard worked";
ok !eval {
x2();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n", "with the right error message";
is $guard_triggered, 2, "the guard worked";
输出:
1..6
ok 1 - the guard died
ok 2 - with the right error message
ok 3 - the guard worked
ok 4 - the guard died
ok 5 - with the right error message
ok 6 - the guard worked