如果Perl代码出现问题,最佳实践是什么?

4

可能重复:
如何在Perl中干净地处理错误检查?
关于Perl异常有什么问题?

我看到的代码是这样的:

do_something($param) || warn "something went wrong\n";

我也看到过这样的代码:

eval {
  do_something_else($param);
};
if($@) {
  warn "something went wrong\n";
}

我应该在所有子程序中使用eval/die吗?我是否应该基于从子程序返回的内容编写所有代码?eval执行代码(一遍又一遍)会减慢我的速度吗?


2
假装别人写的? ;) - Mitch Wheat
是的 :). 当它说作者:Geo. :) 时有点困难。 - Geo
有关Perl中异常的广泛讨论,请参见https://dev59.com/3HI95IYBdhLWcg3wxA09 - Ether
使用坚固的钢框笼来放置服务器机架,这样CPU爆炸时的冲击波就不会摧毁整个城市街区。 - jdizzle
6个回答

13

eval块不是字符串eval,所以它并不慢。使用它绝对是推荐的。

不过,它的工作方式有几个令人讨厌的微妙之处(主要是由于$@是全局变量而带来的令人讨厌的副作用),因此考虑使用Try::Tiny代替记住所有需要使用eval进行防御性编程的小技巧。


6
do_something($param) || warn "something went wrong\n";
在这种情况下,如果出现问题,do_something 应该返回一个错误代码。无论它是否失败,都是非常不寻常的情况。
eval {
  do_something_else($param);
};
if($@) {
  warn "something went wrong\n";
}
这里的假设是 do_something_else 通过抛出异常来传达某些意外情况。如果 do_something_else 在某些情况下会返回错误值,只有在真正的异常情况下才会抛出异常,那么你也应该检查它的返回值。
使用 eval 的块形式不会在运行时导致额外的编译,因此没有严重的性能缺陷:

在第二种形式中,BLOCK 内的代码仅被解析一次--与包含 eval 的代码同时解析--并在当前 Perl 程序的上下文中执行。这种形式通常用于比第一种形式更有效地捕获异常(请参见下文),同时还提供了在编译时检查 BLOCK 中的代码的好处。


6
模块中的warn非常烦人。要么成功,要么失败。不要在终端上打印信息然后继续运行;我的程序不能根据你打印的信息采取行动。如果程序可以继续运行,则仅在明确告知可以的情况下才打印消息。如果程序无法继续运行,则die。这就是它的用途。
当出现问题时始终抛出异常。如果可以修复问题,请修复它。如果无法修复问题,请不要尝试;只需抛出异常并让调用者处理它。(如果您无法处理来自所调用内容的异常,请勿这样做。)
基本上,许多程序出现错误的原因是他们尝试修复自己无法解决的错误。当程序第一次出现问题时干净地停止很容易进行调试和修复。当程序在混乱状态下继续运行时只会破坏数据并使所有人感到困扰。因此不要那样做。尽早停止。

5

你的两个例子完全不同。第一个检查返回值是否为false,并作出相应的操作。第二个则检查被调用代码的实际死亡情况。

你需要自己决定在每种情况下采取哪种行动是合适的。我建议在大多数情况下只需返回false。只有在遇到无法继续执行的严重错误(或者没有继续执行的意义,但即使这样你仍然可以返回false)时才明确使用die

将一个块包装在eval {}中并不等同于将任意代码包装在eval ""中。在前一种情况下,代码仍然在编译时解析,您不会遇到任何额外的开销。您只需捕捉该代码的任何死亡(但您不会得到关于发生了什么以及您的代码运行到了哪里的任何指示,除了留给您的$@中的值)。在后一种情况下,Perl解释器将该代码视为简单字符串,直到实际评估它,因此这里确实存在成本,因为解释器被调用了(并且您失去了对代码的所有编译时检查)。

顺便说一句,您调用eval并检查$@值的方式不是推荐的形式;有关Perl中异常陷阱和技术的广泛讨论,请参见此讨论


当我返回 false 时,我该如何同时提供一个错误消息? - Geo
@Geo:你可以使用warn()将字符串输出到stderr,或者使用日志工具(例如Log::Log4perl)记录消息。 - Ether

2

第一个版本非常“Perl式”,很容易理解。这种习惯用法的唯一缺点是仅适用于短案例的可读性。如果错误处理需要更多逻辑,请使用第二个版本。


2
没有人真正解决这个问题的“最佳实践”部分,所以我来发表一下意见。
是的,当代码出现问题时,您应该绝对抛出异常,并且应尽早抛出(这样您就可以限制需要调试的代码,以找出导致问题的原因)。
像返回undef来表示失败的代码并不特别可靠,仅仅是因为人们倾向于在不检查undef返回值的情况下使用它——这意味着他们假定一个变量中有一些有意义的内容,而实际上可能没有。这会导致复杂、难以调试的问题,甚至会在先前工作正常的代码中出现意外的问题。
一种更可靠的方法是编写代码,使其在出现问题时停止运行,然后仅在需要从该失败中恢复时,将任何对它的调用包装在eval{..}(或更好地使用Try::Tiny中提到的try{..}catch{..})。在大多数情况下,调用代码无法进行有意义的恢复,因此在常见情况下,调用代码保持简单,您可以假设会获得有用的返回值。如果发生错误,则会从实际失败的代码部分获得错误消息,而不是静默地获得未定义的值。如果您的调用代码可以执行某些操作来恢复失败,则可以安排捕获异常并执行所需的任何操作。
值得一读的是异常类,它们是一种结构化的方式,可以向调用代码发送额外的信息,并允许其选择要捕获哪些异常以及不能处理哪些异常。您可能不想在代码的每个地方都使用它们,但当您遇到一些复杂的东西可能以同样复杂的方式失败时,它们是一种有用的技术,并且您希望安排故障可恢复。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接