【问题标题】:How should I handle errors in Perl methods, and what should I return from the methods?我应该如何处理 Perl 方法中的错误,我应该从方法中返回什么?
【发布时间】:2016-02-20 01:10:05
【问题描述】:

我已经用一个小模块包装了 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->before() 内容的其他对象(iLO::response 或其他对象)(这只是 Net::SSH::Expect 的before())?但如果我这样做——并且必须将每个 $ilo->command 包装在一个测试中以捕获它——我的脚本将再次充满样板。

其次,我应该为成功返回什么?同样,我的散列或多或少包含来自 Net::SSH::Expect 的响应,但它在某种程度上感觉不“正确”。虽然这个例子是用 Perl 编写的,但我用其他语言编写的代码发出了同样熟悉的气味:我从不知道如何或从方法返回什么。你能告诉我什么?

【问题讨论】:

  • 你永远不应该return undef(),因为这会在列表上下文中创建一个单元素列表。只要return; 在任何情况下都会做正确的事情。 :)

标签: perl language-agnostic error-handling


【解决方案1】:

如果您熟悉 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::ClassError 模块也为执行类似 Java 的 try/catch 块提供了一些语法糖。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      查看 CPAN 上流行的 Perl 模块(或您已经使用的模块)以了解它们的作用。我什至在Mastering Perl 中谈到了这一点,但我没有给出非黑即白的答案。与所有真实代码一样,真正的答案是“视情况而定”。

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

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

      而且,如果所有这些都失败了,那就去争取 2d6。 :)

      【讨论】:

        【解决方案4】:

        除了使用“die”作为异常,还可以添加其他方法:

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

        当然,command()的内部实现就变成了

        die ... if !commandSucceeded;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-11-04
          • 1970-01-01
          • 1970-01-01
          • 2020-08-27
          • 1970-01-01
          • 2012-06-07
          • 2011-06-13
          • 2021-12-24
          相关资源
          最近更新 更多