我正在编写一个包含许多模块的perl框架。现在任何这样的模块都有一个SUCESS和多个级别的FAILURE 。
失败可能是因为它不满足条件,或者可能是由于某些库故障或远程计算机无法正常工作。
在我目前的实现中,我将不同的返回码(0,1,2等)传递给表示不同的场景。
我想知道是否有更优雅的方式,因为当前的逻辑看起来好像是硬编码的。
答案 0 :(得分:9)
正如在评论中多次提到的,不是重载返回值,而是你真正想要做的就是抛出异常。在返回值中混合成功的值和错误代码会使用该函数变得复杂,同时获取值并检测错误,从而生成错误。 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)返回一个简单的成功代码和一个复杂的错误代码。
这就是shell的做法。 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?...
}
至少是真的与&#34;成功&#34;一致,0和undef都是失败的,这是一些东西。但负数也是如此。用户很可能会犯错,只需检查一个真值,并错过错误代码。
4)返回一个错误的重载对象。
这是你无可比拟的最佳选择。
所有真实值都表明成功。所有错误值都表示错误。返回一个对象,该对象的布尔值重载为永远为false且其字符串值重载以返回其错误代码。
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 do do并使用side全局变量。
这是有意义的,但只有相对而言,才能返回值,而是传入引用以捕获错误。
my $fh = open \$error, $file or die "Can't open $file: $error";
这是我从未在野外见过的,但效果非常好。如果返回错误代码的最大问题是用户将忽略它们,强制它们传入错误引用作为第一个参数将是一个很好的方式将它推到他们的脸上。