我该如何在Perl方法链中处理错误?

10
什么是在Perl方法链中抛出异常的最佳处理方式?如果方法链中的任何一个方法抛出异常,我想将值分配为0或undef。
代码示例:
```perl my $value = Object->new()->method1()->method2()->method3(); ```
my $x = $obj->get_obj->get_other_obj->get_another_obj->do_something;

有什么最好的方法吗?我每次都需要用try/catch/finally语句包装吗?我的情景是这样的:我在使用Catalyst和DBIC进行Web开发,并且我经常使用链式结果集。如果其中一些结果集抛出异常,我只想将值赋为0或undef,然后在模板中处理此错误(我正在使用Template Toolkit)。如果有其他方法可以在不用每次都包装try/catch的情况下做到这一点,请告诉我。如果您知道在相同的上下文(Catalyst/DBIC/TT)中更好地处理此类型的错误的方法,请建议。一个实际的例子是当用户搜索某些不存在的内容时。

3个回答

9
我通过在失败点返回一个空对象来处理这个问题。该对象通过简单地返回自身来响应每个方法,因此它会一直这样做,直到使用剩余的方法。最后,您查看 $x,看看它是您期望的结果还是这个空对象。
以下是这种情况的示例:
use v5.12;

package Null {
    my $null = bless {}, __PACKAGE__;
    sub DESTROY { 1 }
    sub AUTOLOAD { $null }
    }

对于每个调用的方法,AUTOLOAD截获它并返回一个空对象。 当你遇到错误时,返回其中一个Null对象。在方法链的中间,你仍然会得到一个对象,所以当你调用下一个方法时,Perl不会崩溃。
sub get_other_obj {
    ...;
    return Null->new if $error;
    ...;
    }

在链的末尾,您可以检查返回结果是否为 Null 对象。如果是,说明出了问题。

这是基本想法。您可以改进 Null 类,使其记住消息和创建位置,或添加一些多态方法(例如sub is_success { 0 })以使其与预期获得的对象接口良好匹配。

我记得我以前写过关于这个的长篇文章,但现在找不到了。

更新:找到了一些相关文章:


但这里有一个问题:Setter方法将使用null条目来表示返回当前值。例如,$foo->Name("David")将把名称设置为David,而$foo->Name将返回当前名称。因此,一个方法的空返回可能是另一个方法的有效输入。 - David W.
这并不是真正的问题。它是一个无操作。后续方法不会执行任何操作。你没有将空对象作为参数传递;它是指示物。如果前面的方法没有返回对象,那么你无法链接。 - brian d foy
抱歉,我不理解。我该如何在DBIC链接结果集中应用它? - nsbm
我知道这已经过时了,但那是什么样子?这个线程(http://www.perlmonks.org/?node_id=899958)似乎表明Moose/Mouse的人们对返回undef有些不喜欢(perlcritic默认也会抱怨),特别是注意stvn的评论。那么,在Perl中,“null对象”是什么?您是否正在祝福包含null的hashref,还是简单地“返回undef”? - swelljoe
1
他们正在讨论一个明确的 return undef,在列表上下文中存在问题,因为它是一个只有一个元素的列表。我在谈论返回单个对象,并且其他所有情况都期望得到单个对象。 - brian d foy
这是一个非常棒的想法。为了实现链式调用或返回undef,有一个很棒的模块Want,可以像这样实现Brian所建议的:package Null; BEGIN { use strict; use Want; }; sub new { return( bless( {}, __PACKAGE__ ) ); } AUTOLOAD { rreturn( $_[0] ) if( want( 'OBJECT' ) ); return; }; DESTROY {};然后可以像这样使用:my $rc = $my_object->get->something->die->finish; die设置并返回一个Null对象,finish按预期返回undef。 - Jacques

3

您可以编写一个标量方法,将方法链包装在错误处理中:

my $try = sub {
    @_ > 1 or return bless {ok => $_[0]} => 'Try';

    my ($self, $method) = splice @_, 0, 2;
    my $ret;
    eval {
        $ret = $self->$method(@_);
    1} or return bless {error => $@} => 'Try';
    bless {ok => $ret} => 'Try'
};

{package Try;
    use overload fallback => 1, '""' => sub {$_[0]{ok}};
    sub AUTOLOAD {
        my ($method) = our $AUTOLOAD =~ /([^:]+)$/;
        $_[0]{ok} ? $_[0]{ok}->$try($method, @_[1..$#_]) : $_[0]
    }
    sub DESTROY {}
    sub error {$_[0]{error}}
}

如何使用它:

{package Obj;
    sub new {bless [0]}
    sub set {$_[0][0] = $_[1]; $_[0]}
    sub add {$_[0][0] += ($_[1] || 1); $_[0]}
    sub show {print "Obj: $_[0][0]\n"}
    sub dies  {die "an error occured"}
}

my $obj = Obj->new;

say "ok 1" if $obj->$try(set => 5)->add->add->show; # prints "Obj 7"
                                                    # and "ok 1"

say "ok 2" if $obj->$try('dies')->add->add->show;   # prints nothing 

say $obj->$try('dies')->add->add->show->error;  # prints "an error occured..."
$try 方法的第一行还允许以下语法:
say "ok 3" if $obj->$try->set(5)->add->add->show;

-1
一个想法是创建一个类,使用overload来在字符串/数字/布尔上下文中评估实例对象时返回false值,但仍允许调用方法。一个AUTOLOAD方法可以始终返回$self,从而使方法链传播相同的错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接