我应该如何处理Perl方法中的错误,以及从方法中返回什么?

时间:2009-01-09 21:26:07

标签: perl language-agnostic error-handling

我用一个小模块包装了Perl的Net::SSH::Expect,以减少编写用于我们的HP iLO卡的新配置脚本所需的样板代码。虽然我一方面希望这个包装器尽可能精简,但非程序员同事可以使用它,我也希望它尽可能好。

它的使用方式如下:

my $ilo = iLO->new(host => $host, password => $password);
$ilo->login;

$ilo->command("cd /system1");
$ilo->command("set oemhp_server_name=$system_name", 'status=0');

这是iLO::command()

sub command {
    my ($self, $cmd, $response) = @_;

    $response = 'hpiLO-> ' unless defined($response);

    # $self->{ssh} is a Net::SSH::Expect object
    croak "Not logged in!\n" unless ($self->{ssh});

    $self->{ssh}->send($cmd);
    if ($self->{ssh}->waitfor($response, $self->{CMD_TIMEOUT}, '-re')) {
        return {
            before => $self->{ssh}->before(),
            match => $self->{ssh}->match(),
            after => $self->{ssh}->after(),
        };
    } else {
        carp "ERROR: '$cmd' response did not match /$response/:\n\n",
            $self->{ssh}->before()),
            "\n";
        return undef;
    }
}

我有两个相关的查询。首先,我应该如何处理与预期响应不匹配的响应?我想我现在正在做的事情是令人满意的 - 通过返回undef我发出信号,我的croak()会输出一个错误(虽然不是很优雅)。但感觉就像代码味道。如果Perl有异常,我会提出一个并让调用代码决定是否忽略它/退出/打印一个警告,但它没有(好吧,在5.8中)。也许我应该返回一些带有错误消息的其他对象(iLO::response或其他东西)和$ilo->before()的内容(这只是Net :: SSH :: Expect的before())?但是,如果我这样做 - 并且必须将每个$ilo->command包装在一个测试中以便捕获它 - 我的脚本将再次充满样板。

其次,我应该回归什么才能获得成功?同样,我的哈希包含来自Net :: SSH :: Expect的响应或多或少可以完成工作,但它不会以某种方式感觉“正确”。虽然这个例子在Perl我的代码在其他语言中发出了同样熟悉的气味:我从来不知道如何或从一个方法返回什么。你能告诉我什么?

4 个答案:

答案 0 :(得分:5)

在Perl中引发异常的常用方法是使用die。捕获它们的常用方法是使用eval将块作为参数,并在eval结束后测试$ @。

答案 1 :(得分:5)

如果您熟悉Java等语言的例外情况,请将die视为throw,将eval视为trycatch。您可以执行以下操作,而不是返回undef

if ($self->{ssh}->waitfor($response, $self->{CMD_TIMEOUT}, '-re')) {
    return {
        before => $self->{ssh}->before(),
        match => $self->{ssh}->match(),
        after => $self->{ssh}->after(),
    };
}

die "ERROR: '$cmd' response did not match /$response/:\n\n" 
. $self->{ssh}->before();

然后,在您的主叫代码中:

eval { 
    $ilo->command("set oemhp_server_name=$system_name", 'status=0');
};

if ( my $error = $@ ) { 
    # handle $error here
}

就像其他语言中的异常一样,这允许您在任何时候摆脱子方法,而不必担心在调用堆栈中传播返回值。它们将被发现它们的第一个eval块捕获。此外,您可以再次die重新抛出一个无法处理备份的异常。

更好的是,您可以使用die来抛出一个对象,您的异常处理程序可以查询该对象以获取有用的信息和错误消息。我喜欢将Exception::Class用于此目的。 Error模块也为类似Java的try / catch块提供了一些语法糖。

答案 2 :(得分:4)

你会在googlespace中找到很多关于这类事情的讨论。无论你决定什么,最佳做法是不重载任何值,因此返回值意味着不同的东西。它应该始终是一个错误代码,或者它永远不应该是错误代码。人们不应该查看实际值来判断它是否是错误代码。

查看CPAN(或您已经使用过的)上流行的Perl模块,了解它们的功能。我甚至在Mastering Perl中谈了这个,但我没有给出一个黑白回答。与所有真实代码一样,真正的答案是“它取决于”。

有很多不同的方法可以做到这一点。不幸的是,这意味着人们在各方面都这样做。既然如此,我调用一致性作为最重要的规则。大多数代码已经做了什么(不计算错误的方式)?如果我必须适应现有的代码库,我会尝试使用与大多数代码已经使用的相同类型的接口。

如果没有明确的赢家,请使用几种不同的样式编写用例。哪个适合问题更好或更自然地表达大多数用户将采取的步骤?这不仅仅是dieeval的阅读。使用未实现的接口编写示例脚本。你想要使用哪种风格?我发现在实现界面之前实际编写脚本比我想的要多得多。如果我正在为其他人写东西,我会向他们展示不同风格的剧本,并问他们哪个更好。

并且,如果所有这些都失败了,那就达到2d6。 :)

答案 3 :(得分:0)

除了使用“die”作为例外之外,您还可以添加另一种方法:

if (!$ilo->commandSucceeded("set oemhp_server_name=$system_name", 'status=0')) {
     #recover here
}

当然,command()的内部实现变为

die ... if !commandSucceeded;