如果我有像
这样的Perl模块 package X;
和像
这样的对象 my $x = X->new ();
在 X.pm 内,我为$x
编写了一个名为handle_error
的错误处理程序,我称之为
sub check_size
{
if ($x->{size} > 1000) {
$x->handle_error ();
return;
}
}
有没有办法让handle_error
强制从其来电程序返回?换句话说,在此示例中,我是否可以在handle_error
中return
check_size
return
而不在其中实际编写{{1}}?
答案 0 :(得分:11)
回调调用堆栈的多个级别的唯一合理方法是抛出异常/死亡。
那就是说,你真的应该重新考虑你想做什么。程序员(包括你自己,从现在起六个月)将期望,当函数调用完成后,它将执行后的语句(除非抛出异常)。违反该期望将导致handle_error
中导致的错误,但看起来与调用handle_error
的代码有关,这使得它们极难调试。这不是一件好事。
您还假设绝对没有情况,其中在处理错误后继续是适当的。硬编码这样的假设实际上是一个肯定的保证,一旦你有时间忘记它,你会遇到一个案例,你需要在调用handle_error
之后继续(然后浪费大量的时间试图找出handle_error
之后的代码无法运行的原因。
然后假设您总是想要在调用堆栈中跳过完全两个级别。这是另一个假设,一旦硬编码就会失败。不仅会出现调用代码应该继续的情况,还会出现需要在调用堆栈中向上三级的情况。
所以只需通过调用handle_error
而不是die
退出return
,并将异常捕获到应继续执行的适当级别。你不知道sub将被调用的每个地方,所以你无法预测它需要多少级别。
在手头的代码中,如果只是说return
的额外行困扰你,你可以使用return $x->handle_error;
你甚至可以摆脱封闭范围并使其成为return $x->handle_error if $x->{size} > 1000;
在那里 - 删除了三行而不是一行,加上一对括号和两对括号作为免费奖励。
最后,我还建议更改handle_error
的名称,以更好地反映其实际效果。 (report_error
,也许?)“处理错误”通常意味着要清理以解决错误,以便继续执行。如果你希望你的handle_error
阻止调用它的代码继续,那么它似乎不太可能清理它以使延续成为可能,并且再一次,它将导致令人讨厌的,难以调试的意外使用此代码的未来程序员。
答案 1 :(得分:4)
您可以使用goto &NAME,错误处理程序的返回将返回到check_size
被调用的位置。
sub check_size { 我的$ x =班次; #你没有说X.pm中的$ x来自哪里 #我认为它是调用者。
if( $x->{size} > 1000 ) {
my $sub = $x->can('handle_error');
goto $sub;
}
}
这是有效的,因为goto &NAME
将控制转移到被调用函数而不创建新的堆栈帧。
我使用can
来获取handle_error
$x
的引用,以便该方法可以正常使用覆盖handle_error
的子类。
但这个设计对我来说似乎不太好。
也许这是使用例外的好地方:
use Try::Tiny;
my $x = X->new();
try { $x->check_size }
catch { $x->handle_error };
答案 2 :(得分:2)
你的问题的答案非常困难,而且我甚至不打算将它解决,因为它对你真正的问题很难解决。什么是真正的问题?问题是你想要一个深度嵌套的子程序调用中的错误来冒泡堆栈。这就是例外情况。
这是您的代码被重写以使用croak
抛出异常。
package X;
sub new {
my $class = shift;
my %args = @_;
my $obj = bless \%args, $class;
$obj->check_size;
return $obj;
}
my $Max_Size = 1000;
sub check_size
{
my $self = shift;
if ($self->{size} > $Max_Size) {
croak "size $self->{size} is too large, a maximum of $Max_Size is allowed";
}
}
然后当用户创建无效对象时......
my $obj = X->new( size => 1234 );
check_size
死掉并将其异常抛到堆栈上。如果是用户
没有任何阻止它,他们得到一个错误消息"size 1234 is too
large, a maximum of 1000 is allowed at somefile line 234"
。 croak
确保错误消息发生在new
的位置
调用,用户发出错误的地方,而不是X.pm内部的某个地方。
或者他们可以在eval BLOCK
中编写new()以捕获错误。
my $obj = eval { X->new( size => 1234 ) } or do {
...something if the object isn't created...
};
如果您想在发生错误时再做一些事情,可以换行
方法调用中的croak
。
sub error {
my $self = shift;
my $error = shift;
# Leaving log_error unwritten.
$self->log_error($error);
croak $error;
}
my $Max_Size = 1000;
sub check_size
{
my $self = shift;
if ($self->{size} > $Max_Size) {
$self->error("size $self->{size} is too large, a maximum of $Max_Size is allowed");
}
}
来自croak
的例外情况会通过error
,check_size
和new
冒出来。
正如daotoad所指出的,Try :: Tiny是一个比直接eval BLOCK
更好的异常处理程序。
有关异常是个好主意的更多理由,请参阅Should a Perl constructor return an undef or a "invalid" object?。
答案 3 :(得分:1)
您可以使用Continuation::Escape。这基本上允许您将“返回点”传递给错误处理程序。
答案 4 :(得分:0)
可行的解决方法但不好看。
使用select语句。
如果你有一个函数my_func(),你调用它,然后根据返回值你想要做一些错误处理,然后从你可以做的调用者返回:
($result = my_func()) == 0 ? return handle_error($result) : 0;
这是一个(丑陋的)单行代替:
$result = my_func();
if ($result == 0) {
handle_error($result);
return;
}
如果您有许多功能可以调用并检查返回值,则非常方便。