Perl子程序是否有可能强制其调用者返回?

时间:2010-05-20 06:41:15

标签: perl

如果我有像

这样的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_errorreturn check_size return而不在其中实际编写{{1}}?

5 个答案:

答案 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的例外情况会通过errorcheck_sizenew冒出来。

正如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;
}

如果您有许多功能可以调用并检查返回值,则非常方便。