可能重复:
Is it possible for a Perl subroutine to force its caller to return?
我想编写一个子程序,使调用者在某些条件下返回。这意味着用作验证函数输入的快捷方式。到目前为止我所拥有的是:
sub needs($$) {
my ($condition, $message) = @_;
if (not $condition) {
print "$message\n";
# would like to return from the *parent* here
}
return $condition;
}
sub run_find {
my $arg = shift @_;
needs $arg, "arg required" or return;
needs exists $lang{$arg}, "No such language: $arg" or return;
# etc.
}
在needs
中从调用者返回的优点是避免必须在or return
和类似函数内写入重复的run_find
。
答案 0 :(得分:4)
听起来你正在重新发明异常处理。
needs
函数不应该神奇地推断其父级并中断父级的控制流 - 这是不礼貌的行为。如果您向调用链添加其他功能,并且需要返回两个甚至三个功能,该怎么办?你怎么能以编程方式确定这个?呼叫者是否期望他或她的功能提前返回?如果你想避免错误,你应该遵循最小惊喜的原则 - 这意味着使用异常来表明存在问题,并让调用者决定如何处理它:
use Carp;
use Try::Tiny;
sub run_find {
my $arg = shift;
defined $arg or croak "arg required";
exists $lang{$arg} or croak "no such language: $arg";
...
}
sub parent {
try { run_find('foo') }
catch { print $@; }
}
try
块内的任何代码都是特殊的:如果某些东西死掉,异常将被捕获并存储在$@
中。在这种情况下,执行catch
块,将错误打印到STDOUT,控制流继续正常。
免责声明:Perl中的异常处理很痛苦。我推荐Try::Tiny,它可以防止许多常见的陷阱(并提供熟悉的try / catch语义)和Exception::Class来快速创建异常对象,以便您可以区分Perl的错误和您自己的错误。
为了验证参数,您可能会发现使用CPAN模块更加容易,例如Params::Validate。
答案 1 :(得分:4)
我认为你在这里专注于错误的事情。我使用Data :: Constraint,Brick等来做这类事情,并在 Mastering Perl 中讨论这个问题。通过对程序结构和Perl具有的动态特性的一点巧妙和思考,您不需要这样一种规范的,程序化的方法。
然而,你需要弄清楚的第一件事是你真正想知道的那个调用子程序。如果你只是想知道是或否,这很容易。
needs
的问题在于,您正考虑为每种情况调用一次,这会强制您使用needs
来控制程序流。这是错误的方式。 needs
只是给你一个答案。它的工作不是改变程序状态。如果你滥用它会变得没那么有用,因为即使needs
返回false,其他一些调用子程序也可能想要继续。调用一次,让它返回一次。调用子例程使用返回值来决定它应该做什么。
基本结构涉及一个传递给needs
的表。这是您的验证资料。
sub run_find { my $arg = shift @_; return unless needs [ [ sub { $arg }, "arg required" ], [ sub { exists $lang{$arg} }, "No such language: $arg" ], ]; } ... }
您可以根据自己的要求构建表格。在needs
中,您只需处理该表:
sub needs($$) {
my ($table) = @_;
foreach $test ( @$table ) {
my( $sub, $message ) = @$test;
unless( $sub->(...) ) {
print $message;
return
}
}
return 1;
}
现在,这种方法真的很酷的是你不必提前知道这个表。您可以从配置或其他方法中获取它。这也意味着您可以动态更改表。现在你的代码缩小了很多:
sub run_find { my $arg = shift @_; return unless needs( $validators{run_find} ); ... }
你继续坚持下去。在 Mastering Perl 中,我展示了几个完全从代码中删除它并将其移动到配置文件中的解决方案。也就是说,您可以在不更改代码的情况下更改业务规则。
请记住,几乎在您输入相同字符序列的任何时候,您可能做错了。 :)
答案 2 :(得分:1)
你可能想看看kinopiko最近的类似问题: Is it possible for a Perl subroutine to force its caller to return?
执行摘要是:最佳解决方案是使用异常(die / eval,Try :: Tiny等等)。你也可以使用GOTO和Continuation :: Escape
答案 3 :(得分:0)
以这种方式做事是没有意义的;具有讽刺意味的是,你不需要needs
。
这就是原因。
如果您想在参数失败时终止执行(将其重命名为run_find
),我将如何编写您的well_defined
sub:
sub well_defined {
my $arg = shift;
$arg or die "arg required";
exists $lang{$arg} or die "no such language: $arg";
return 1;
}
同时应该有 return 0
和warn
的方法,但我需要再多玩一遍。
run_find
也可以写入return 0
以及相应的warn
消息(如果条件不符合),return 1
(如果有){重命名为well_defined
)。
sub well_defined {
my $arg = shift;
$arg or warn "arg required" and return 0;
exists $lang{$arg} or warn "no such language: $arg" and return 0;
return 1;
}
这会启用布尔式行为,如下所示:
perform_calculation $arg if well_defined $arg; # executes only if well-defined