代码之家  ›  专栏  ›  技术社区  ›  user181548

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

  •  6
  • user181548  · 技术社区  · 16 年前

    如果我有Perl模块,比如

     package X;
    

    还有一个像

     my $x = X->new ();
    

    在…内 下午十点 ,我为 $x 打电话 handle_error ,我称之为

     sub check_size
     {
         if ($x->{size} > 1000) {
             $x->handle_error ();
             return;
         }
     }
    

    有没有什么办法 处理错误 强制从调用程序返回?换句话说,在这个例子中,我可以 处理错误 return 在里面 check_size 没有真正的写作 回来 那里

    5 回复  |  直到 16 年前
        1
  •  11
  •   Dave Sherohman    16 年前

    回溯调用堆栈多个级别的唯一合理方法是抛出异常/终止。

    也就是说,你 真正地 应该重新考虑你想做什么。程序员(包括你自己,六个月后)会期望,当函数调用完成时,它后面的语句会执行(除非抛出异常)。违反该期望将导致在 handle_error ,但似乎与调用 处理错误 ,使它们极难调试。这不是一件好事。

    你还假设有 绝对没有情况 在这种情况下,在处理错误后继续操作是合适的。硬编码这样的假设实际上是一个确定的保证,一旦你有时间忘记它,你就会遇到一个情况,在打电话后需要继续 处理错误 (然后浪费大量时间试图弄清楚为什么代码会在 处理错误 不跑)。

    还有一个假设是 总是 想跳回去吗 确切地 调用堆栈中的两个级别。这是另一个一旦硬编码就会失败的假设。不仅在某些情况下,调用代码应该继续,而且在某些情况下,您还需要在调用堆栈上执行三个级别的操作。

    那就吃吧 处理错误 呼叫退出 die 而不是 return 并在应该继续执行的适当级别捕获异常。你不知道潜艇的每个位置,所以你无法预测它需要返回多少层。

    在手边的代码中,如果额外的一行只是说 回来 正在困扰你,你可以用 return $x->handle_error; 您甚至可以去掉封闭范围并使其 return $x->handle_error if $x->{size} > 1000; 这里——删除了三行而不是一行,加上一对大括号和两对括号作为免费奖励。

    最后,我还建议更改 处理错误 更好地反映它的实际功能。( report_error (也许吧?)“处理错误”通常意味着清理问题以解决错误,以便继续执行。如果你想 处理错误 为了防止调用它的代码继续运行,那么它似乎不太可能进行清理以使继续成为可能,而且,这将再次给使用该代码的未来程序员带来令人讨厌的、难以调试的惊喜。

        2
  •  4
  •   daotoad    16 年前

    你可以用 goto &NAME ,您从错误处理程序返回的值将返回到 check_size 有人打电话来。

    子检查尺寸{ 我的$x=shift;#你不会在下午x点说x美元从哪里来。
    #我猜是发票。

    if( $x->{size} > 1000 ) {
        my $sub = $x->can('handle_error');
        goto $sub;
    }
    

    }

    这是因为 goto &NAME 将控制转移到被调用的函数,而不创建新的堆栈帧。

    我用 can 以获取对 handle_error 对于 $x ,以便该方法可以与重写 处理错误 .

    不过,这个设计对我来说似乎是个坏主意。

    也许这是一个使用异常的好地方:

    use Try::Tiny;
    my $x = X->new();
    
    try   {  $x->check_size }
    catch {  $x->handle_error };
    
        3
  •  2
  •   Community Mohan Dere    9 年前

    你的问题的答案非常难,我甚至不打算一一回答,因为这对你真正的问题来说是一个糟糕的解决方案。真正的问题是什么?问题是,您希望在深度嵌套的子例程调用中出现错误,从而使堆栈冒泡。这就是例外。

    下面是您的代码重写,使用 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" . 呱呱叫 确保错误消息发生在 new 是 调用,用户在其中出错,而不是在X.pm的深处。

    或者他们可以用 eval BLOCK 来捕捉错误。

    my $obj = eval { X->new( size => 1234 ) } or do {
        ...something if the object isn't created...
    };
    

    如果你想在错误发生时做更多的事情,你可以包装 呱呱叫 在方法调用中。

    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");
       }
    }
    

    例外 呱呱叫 会从烟囱里冒出来 error , 检查尺寸 .

    正如daotoad所指出的,Try::Tiny是一个比straight更好的异常处理程序 评估块 .

    看见 Should a Perl constructor return an undef or a "invalid" object? 更多的理由说明为什么例外是个好主意。

        4
  •  1
  •   phaylon    16 年前

    你可以用 Continuation::Escape 。这基本上允许您将“返回点”传递给错误处理程序。

        5
  •  0
  •   Giulio    12 年前

    可行但不好看的变通方法。

    使用select语句。

    如果有一个调用的函数my_func(),然后根据返回值进行一些错误处理,然后从调用方返回,则可以执行以下操作:

    ($result = my_func()) == 0 ? return handle_error($result) : 0;
    

    这是一个(丑陋的)单衬板,替代了:

    $result = my_func();
    if ($result == 0) {
        handle_error($result);
    
        return;
    }
    

    如果你有很多函数可以调用和检查返回值,那就很方便了。