我正在编写一个包含许多模块的 perl 框架。现在对于任何这样的模块,都有一个 SUCESS 和许多级别的 FAILURE。
失败可能是因为它不满足某个条件,也可能是由于某些库故障或远程机器不工作。
在我目前的实现中,我传递了不同的返回码(0、1、2 等)来表示不同的场景。
我想知道是否有更优雅的方式,因为当前逻辑看起来好像是硬编码的。
正如评论中多次提到的,您真正想要做的不是重载返回值,而是抛出异常。在返回值中混合成功值和错误代码会使函数的使用变得复杂,既获取值又检测错误,从而产生错误。CPAN 上有几个模块可以更轻松地抛出和捕获异常。 Try::Tiny、TryCatch、Throwable和Exception::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 $@;
}
异常看起来像更多的代码!除了它是一个错误的等价物。如果您要做的只是死于错误,而且大多数情况下都是这样,那么这些是实际的等效返回值和异常示例。
# 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;
如果你返回一个错误标志,用户总是必须检查错误,即使他们要做的只是立即死亡。如果你使用异常,用户只需要在他们想要做一些特别的事情时检查错误。您可能会说,这是规范的例外。
这就是例外的巨大经济性和安全性。它为用户减少了工作量,并且不会意外错过。
既然已经说过了,并且真的想使用异常,这里有几种方法可以做你想做的事。这既是为了完整性,也是为了说明每个下降的位置。这是计算机 API 中的一个老问题,老问题,早于异常,所以有很多“聪明”的方法来解决它。与例外相比,它们都有许多严重的问题。我敢肯定我会错过一些,但这更多是为了说明你可以在兔子洞里走多远。
1) 返回 false 并将全局变量设置为错误代码。
在 Perl 社区接受异常之前,这在很多库中很常见,例如 DBI(顺便说一句,它也建议您使用异常)。
my $dbh = DBI->connect($data_source, $username, $password)
or die $DBI::errstr;
这也是 Perl 是如何做到的。
open my $fh, $file or die "Cannot open $file for reading: $!";
这具有返回错误代码的普遍问题:它安静地失败。如果用户忘记检查错误,程序运行时不会在其他地方出现任何错误,这可能很神秘,可能会花费非常长的时间来调试。异常大声失败。忘记检查异常,至少您仍然知道问题的根源。
最大的错误之一(新手和老手)是忘记检查错误,这就是为什么像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;
}
}
如果another_function
也有错误怎么办?现在库的内部必须小心始终设置错误标志并立即返回。任何涉及“小心”的系统都注定要失败。
这个问题困扰$!
着 Perl。许多东西可以设置它,有时 Perl 调用多个内部函数,这些函数可以在单个 Perl 函数调用中设置和重置它。
2) 返回一个简单的成功代码和一个复杂的错误代码。
壳牌就是这样做的。0 是成功,其他都是错误代码。
my $exit = system @args;
if( $exit != 0 ) {
...error handling...
...and what did error code 136 mean again?...
}
我希望问题很明显:你不能返回一个有趣的值。它还颠倒了自然的真 == 成功,假 == 失败的约定,这种约定令人困惑且容易出错。另请参阅system
。
也很容易混淆导致undef
成功返回的非常糟糕的错误。
3) 正值为成功,负值为失败。
这是 2,但现在您可以返回有趣的值……只要它们是数字。此外,您所有的错误值都必须是数字,然后您必须在某处的某个表中查找这些数字。喜悦。
my $return = function @args;
if( $return <= 0 ) {
...error handling...
...and what did error code -136 mean again?...
}
至少 true 与“成功”对齐,并且 0 和 undef 都是失败的,就是这样。但负数也是正确的。用户很可能会出错,只检查一个真值,而错过错误代码。
4) 返回一个错误的重载对象。
这是您将获得的最好的,无一例外。
所有真实值都表示成功。所有错误值都表示错误。通过返回一个对象来指示错误,该对象的布尔值重载始终为假,其字符串值重载以返回其错误代码。
use overload
'""' => sub { $_[0]->error_string },
'bool' => sub { 0 },
'0+' => sub { 0 },
fallback => 1
;
真为成功,假为错误。错误不能相互覆盖,您可以获取丰富有趣的错误信息。它并不完美,问题之一是你不能有一个错误的成功值,限制你可以返回的东西,但它非常好。
但是,由于需要返回值,所以不能使用or die
成语。
my $value = function;
die $value if !$value;
它仍然存在返回错误代码的普遍问题,用户必须检查它。这是任何错误处理系统都会遇到的最大问题,也是唯一能解决的问题。
5) 传入返回值的引用。
我包括这个是为了展示你可以走多远。它不是让你的函数返回一个值,而是返回一个错误代码。你如何返回值?您传入一个引用,该函数使用该值设置!听起来很疯狂?!
my $success_flag = open my $fh, $file;
哦。
这是许多不支持异常的语言的推荐样式。
您可以在不干扰返回值的情况下判断是否存在错误,但代价是笨拙且不可链接的接口,但是您无法返回有关错误的任何信息。您要么必须反转 true/false 成功/失败以返回错误代码或字符串(所以 false 是成功),或者您执行 open 所做的操作并拥有一个 side 全局变量。
返回值并传递引用以捕获错误是有道理的,但仅限于相对而言。
my $fh = open \$error, $file or die "Can't open $file: $error";
这是我在野外从未见过的,但效果很好。如果返回错误代码的最大问题是用户会忽略它们,那么强制他们将错误引用作为第一个参数传递将是一个很好的解决方法。