我想在Perl中支持多种返回类型

Dil*_*nte 3 perl function

我正在编写一个包含许多模块的perl框架.现在任何这样的模块都有一个SUCESS和许多级别的FAILURE.

失败可能是因为它不满足某个条件,或者可能是由于某些库故障或远程计算机无法正常工作.

在我目前的实现中,我传递不同的返回码(0,1,2等)表示不同的场景.

我想知道是否有更优雅的方式,因为当前的逻辑看起来好像是硬编码的.

Sch*_*ern 9

正如在评论中多次提到的,不是重载返回值,而是你真正想做的就是抛出异常.在返回值中混合成功的值和错误代码会使用该函数变得复杂,同时获取值并检测错误,从而生成错误.CPAN上有几个模块可以更轻松地抛出和捕获异常. Try :: Tiny,TryCatch,ThrowableException :: Class就是例子.

使用错误代码有一种诱人的虚假经济.假设这两件事是等价的.

# Basic error handling with a return value and flag
my $value = function or die $Some::Module::error;
...continue...

# "Basic" error handling with exceptions
try {
    my $value = function;
    ...continue...
}
catch {
    die $@;
}
Run Code Online (Sandbox Code Playgroud)

例外看起来像更多的代码!除了它的假等价.如果您要做的就是因错误而死亡,而且大多数情况下,这些都是实际的等效返回值和异常样本.

# Basic error handling with a return value and flag
my $value = function or die $Some::Module::error;
...continue...

# Basic error handling with exceptions, for reals
my $value = function;
Run Code Online (Sandbox Code Playgroud)

如果您返回错误标志,则用户始终必须检查错误,即使他们要做的就是立即死亡.如果您使用例外,用户只需要检查错误,如果他们想要做一些特别的事情.你可能会说,这是常态的例外.

这就是例外的巨大经济和安全.它对用户的工作较少,并且不会被意外遗漏.

现在已经说过,而且你真的想要使用例外,这里有几种方法可以做你想要的.这既是为了完整性,也是为了说明每个落下的地方.这是计算机API中一个古老的,旧的问题,长期存在异常,所以有很多"聪明"的方法来解决它.与异常相比,所有这些都存在许多严重问题.我相信我会错过一些,但这更多的是说明你可以走多远的兔子洞.

1)返回false并将全局变量设置为错误代码.

在Perl社区接受异常之前,这在很多库中很常见,例如DBI(也是,btw,建议你使用异常).

my $dbh = DBI->connect($data_source, $username, $password)
      or die $DBI::errstr;
Run Code Online (Sandbox Code Playgroud)

它也是Perl如何做到的.

open my $fh, $file or die "Cannot open $file for reading: $!";
Run Code Online (Sandbox Code Playgroud)

这有返回错误代码的普遍问题:它失败了.如果用户忘记检查错误,那么程序运行的时间不会太大,因为在其他地方可能会出现故障,可能是神秘的,可能需要花费很长时间进行调试.例外失败.忘记检查异常,至少你仍然知道问题的根源.

最大的错误之一(新手和经验丰富)是忘记检查错误,这就是为什么像autodie这样的模块非常棒的原因.

它的第二个问题是它只有在你立即检查错误标志时才有效.该标志是一个全局变量,如果发生另一个错误,它将击败你的.你可能会认为"这不是问题,而是永远function() or die $error_flag".除非...

sub function {
    ...blah blah blah...
    if( $error ) {
        $Error = "Something went wrong, oh god eject!";
        another_function();
        return 0;
    }
    else {
        return $value;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果another_function还有错误怎么办?现在库的内部必须小心,始终设置错误标志并立即返回.任何涉及"小心"的系统都注定要失败.

这个问题困扰$!着Perl.很多东西都可以设置它,有时Perl会调用多个内部函数,这些函数可以在一个Perl函数调用中设置和重置它.

2)返回简单的成功代码和复杂的错误代码.

shell就是这样做的.0表示成功,其他一切都是错误代码.

my $exit = system @args;
if( $exit != 0 ) {
    ...error handling...
    ...and what did error code 136 mean again?...
}
Run Code Online (Sandbox Code Playgroud)

我希望问题很明显:你不能回报一个有趣的价值.它还颠倒了自然的真==成功,错误==失败约定,这是令人困惑和容易出错的.另见system.

很容易混淆一个非常糟糕的错误,导致undef返回成功.

3)正值是成功,负值是失败.

这是2,但现在你可以返回有趣的值......只要它们是数字.此外,您的所有错误值都必须是数字,然后您必须在某个表中查找某些数字.喜悦.

my $return = function @args;
if( $return <= 0 ) {
    ...error handling...
    ...and what did error code -136 mean again?...
}
Run Code Online (Sandbox Code Playgroud)

至少是真的与"成功"一致,0和undef都是失败的,这就是事情.但负数也是如此.用户很可能会犯错,只需检查一个真值,并错过错误代码.

4)返回一个错误的重载对象.

这是你无可比拟的最佳选择.

所有真实值都表明成功.所有错误值都表示错误.通过返回一个具有重载的布尔值的对象来指示错误,该对象始终为false并且其字符串值被重载以返回其错误代码.

use overload
    '""'   => sub { $_[0]->error_string },
    'bool' => sub { 0 },
    '0+'   => sub { 0 },
    fallback => 1
;
Run Code Online (Sandbox Code Playgroud)

真是成功,假是错误.错误不能互相覆盖,您可以获得丰富而有趣的错误信息.它不完美,其中一个问题就是你不能拥有一个虚假的成功价值,限制你可以返回的东西,但这是非常好的.

但是,由于您需要返回值,因此无法使用该or die成语.

my $value = function;
die $value if !$value;
Run Code Online (Sandbox Code Playgroud)

它仍然存在返回错误代码的普遍问题,用户必须检查它.这是任何错误处理系统都会遇到的最大问题,并且只有一个例外可以解决.

5)传递参考值以返回值.

我要包括这个,以显示你可以走多远.它不会让您的函数返回值,而是返回错误代码.你如何归还价值?您传入函数设置的引用值!听起来很疯狂?!

my $success_flag = open my $fh, $file;
Run Code Online (Sandbox Code Playgroud)

哦.

这是许多语言中推荐的样式,不支持异常.

您可以在不干扰返回值的情况下判断是否存在错误,但代价是尴尬且无法链接,但您无法返回有关错误的任何信息.你要么必须反转true/false成功/失败才能返回错误代码或字符串(所以false就是成功),或者你做open open并且有一个side全局变量.

它是有道理的,但只有相对而言,才能返回,而是传入引用以捕获错误.

my $fh = open \$error, $file or die "Can't open $file: $error";
Run Code Online (Sandbox Code Playgroud)

这是我从未在野外见过的,但效果非常好.如果返回错误代码的最大问题是用户将忽略它们,强制它们传入错误引用作为第一个参数将是一个很好的方式将它推到他们的脸上.