我看到的代码是这样的:
do_something($param) || warn "something went wrong\n";
我也看到过这样的代码:
eval {
do_something_else($param);
};
if($@) {
warn "something went wrong\n";
}
我应该在所有子程序中使用eval/die吗?我是否应该基于从子程序返回的内容编写所有代码?eval
执行代码(一遍又一遍)会减慢我的速度吗?
我看到的代码是这样的:
do_something($param) || warn "something went wrong\n";
我也看到过这样的代码:
eval {
do_something_else($param);
};
if($@) {
warn "something went wrong\n";
}
我应该在所有子程序中使用eval/die吗?我是否应该基于从子程序返回的内容编写所有代码?eval
执行代码(一遍又一遍)会减慢我的速度吗?
eval
块不是字符串eval
,所以它并不慢。使用它绝对是推荐的。
不过,它的工作方式有几个令人讨厌的微妙之处(主要是由于$@
是全局变量而带来的令人讨厌的副作用),因此考虑使用Try::Tiny代替记住所有需要使用eval
进行防御性编程的小技巧。
在这种情况下,如果出现问题,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
在某些情况下会返回错误值,只有在真正的异常情况下才会抛出异常,那么你也应该检查它的返回值。在第二种形式中,BLOCK 内的代码仅被解析一次--与包含
eval
的代码同时解析--并在当前 Perl 程序的上下文中执行。这种形式通常用于比第一种形式更有效地捕获异常(请参见下文),同时还提供了在编译时检查BLOCK
中的代码的好处。
warn
非常烦人。要么成功,要么失败。不要在终端上打印信息然后继续运行;我的程序不能根据你打印的信息采取行动。如果程序可以继续运行,则仅在明确告知可以的情况下才打印消息。如果程序无法继续运行,则die
。这就是它的用途。你的两个例子完全不同。第一个检查返回值是否为false,并作出相应的操作。第二个则检查被调用代码的实际死亡情况。
你需要自己决定在每种情况下采取哪种行动是合适的。我建议在大多数情况下只需返回false。只有在遇到无法继续执行的严重错误(或者没有继续执行的意义,但即使这样你仍然可以返回false)时才明确使用die
。
将一个块包装在eval {}
中并不等同于将任意代码包装在eval ""
中。在前一种情况下,代码仍然在编译时解析,您不会遇到任何额外的开销。您只需捕捉该代码的任何死亡(但您不会得到关于发生了什么以及您的代码运行到了哪里的任何指示,除了留给您的$@
中的值)。在后一种情况下,Perl解释器将该代码视为简单字符串,直到实际评估它,因此这里确实存在成本,因为解释器被调用了(并且您失去了对代码的所有编译时检查)。
顺便说一句,您调用eval
并检查$@
值的方式不是推荐的形式;有关Perl中异常陷阱和技术的广泛讨论,请参见此讨论。
warn()
将字符串输出到stderr,或者使用日志工具(例如Log::Log4perl)记录消息。 - Ether第一个版本非常“Perl式”,很容易理解。这种习惯用法的唯一缺点是仅适用于短案例的可读性。如果错误处理需要更多逻辑,请使用第二个版本。
undef
来表示失败的代码并不特别可靠,仅仅是因为人们倾向于在不检查undef返回值的情况下使用它——这意味着他们假定一个变量中有一些有意义的内容,而实际上可能没有。这会导致复杂、难以调试的问题,甚至会在先前工作正常的代码中出现意外的问题。eval{..}
(或更好地使用Try::Tiny中提到的try{..}catch{..}
)。在大多数情况下,调用代码无法进行有意义的恢复,因此在常见情况下,调用代码保持简单,您可以假设会获得有用的返回值。如果发生错误,则会从实际失败的代码部分获得错误消息,而不是静默地获得未定义的值。如果您的调用代码可以执行某些操作来恢复失败,则可以安排捕获异常并执行所需的任何操作。