[学习]EAFP 和 LBYL (关于「权限」和「原谅」以及「异常」)
偶然看 Python 的邮件列表,发现了很有意思的关于「异常」的来世今生的讨论,整理了一下,加上了一些分析,仅供各位参考。本文统一用词
Permission 权限
Forgiveness 原谅
Exceptino 异常
关于「异常」
任何一种新的编程理念出现的时候,都不应该以机器为主导,而应该以「人类容易理解」为主导。而「异常」的出现也不例外。但是,「异常」这个概念因为有些含混,所以,并没有受到应有的重视,很多时候人们甚至抵制使用「异常」,认为使用「异常」没有好处却让程序更加复杂。本文将着重分析异常的定位和正确使用方法,以及和传统方式的对比等等。
前世 - LBYL
LBYL,就是 Look Before You Leap。简单的说,就是「用之前先检查一下」。比如以下的 pseudocode:
file_handle = fopen('foo', 'rb'); // open a file
if (file_ok(file_handle)) { // check if file is properly opened - LBYL
// read file...
}
上面的代码:首先,打开了一个文件;然后,检查文件是否正常打开;然后,读取文件。这就是 LBYL。再看一个例子:
if (is_ready(my_thing)) { // check if my_thing is ready for process
// process my_thing
}
两个代码意思是一样的,就是用一个东西之前,先检查一下,然后使用。
今生 - EAFP
很多教材和书籍介绍「异常」的产生,往往说,是因为:有很多函数,都会返回错误代码,例如 false,但是这些函数都是嵌套型的,一个调用另一个,另一个再调用另一个,会产生以下问题:
A 调用 B
B 调用 C
C 调用 D
D 出错!返回 false
C 无法处理,继续返回 false
B 无法处理,继续返回 false
A 处理错误
这就是通常认为的,「异常」的作用,就是为了解决上面这种嵌套型的问题,让程序更加清晰。但是,实际应用中,特别是「模块化」和「面向对象」等等理念的普及,使得上面这种情况越来越少,似乎「异常」也就没有了优势。
其实不然。「异常」的作用远远不止于此。
先来看看前面的方法 LBYL 的问题:
file_handle = fopen('foo', 'rb'); // open a file
if (file_ok(file_handle)) { // check if file is properly opened - LBYL
// read file...
}
当 file_ok() 检查通过的时候,是不是就真的说明文件是可以读取的呢?在比较复杂的情况下,例如考虑到并发访问以及硬件错误的情况下,如果程序在执行 file_ok() 那一瞬间通过了,但是文件却在下一瞬间突然不能用了呢?怎么办?
如果文件是通过网络读取,读取过程网络偶然失效怎么办?
用 LBYL 是没办法完全解决这些问题的。因为文件有可能读了一半出问题,也有可能读到第 2 个 byte 的时候出问题,也有可能读到最后一个 byte 出问题,但是却没有在你检查的时候出问题。而实践中是不可能每读一个 byte 就检查一下的。
怎么办?
用新办法,EAFP。EAFP 的名字稍微长一些,It is Easier to Ask for Forgiveness than Permission,即「要求原谅比要求权限更容易」。什么意思呢?就是说,我不检查了,我就直接用,出错了,你通知我,我「原谅」你,并且解决问题。例如:
try {
fopen('foo', 'rb');
// read file
} catch (io_err) {
// deal with io_err
} catch (sys_err) {
// deal with sys_err
}
这种方法看似和前面一样,其实大有玄机。在这种情况下,任何 IO 方面的错误,不管是读 1 byte 或者 2 byte 的时候错了,还是文件突然不能用了,还是网络实效,还是系统出错,都能被解决。这些错误,在任何时候任何地方都可能发生,但你不可能在所有这些地方都加上检查,即使你的程序需要面对的情况很少很简单,效率也不高,因为每个动作都得小心翼翼地「要求权限」。什么「权限」?比如说:可以打开文件了吗?可以开始读取了吗?可以读下一 byte 了吗?可以关闭文件了吗?等等等等。
「异常」不止解决了上面这个问题,还让程序更加结构化。因为每一种错误,都由不同的 catch/rescue 代码块来解决,非常清晰。在某些允许 retry 的语言中,问题如果是可以解决的,还可以继续程序的运行,使程序更加人性化。
「异常」不是万能药
「异常」并不是解决一切问题的办法。它也有它不足的地方,例如,需要更加详实的规划(例如「异常」的结构,错误信息处理,程序的结构等等)。所以,杀牛用牛刀,杀鸡用菜刀,EAFP 和 LBYL 要配合使用才能使程序即有可读性又稳定。
个人推荐的用法是,分开程序系统中「原子」(atomic)的操作和「复合」(compound)的操作。所谓「原子」的操作就是例如:「访问 book 对象的 title 属性」在这种情况下,就可以用 LBYL,因为只有一个需要检查的地方,就是检查 book 是否有 title 属性;所谓「复合」的操作,就是「文件读写」、「查询数据库」,这些情况常常需要多个环节的配合,而每个环节都有可能出错,而这些环节有可能被封装成了寥寥数个函数,你无法对每个环节都进行详细的检查,就应该用 EAFP,用「异常」来统一处理这些问题,也使得程序更加地清晰和稳定。
在更加复杂的情况下(例如并发等等),不管是为了运行稳定还是除错简单,EAFP 一定是首选。
延伸阅读以及参考资料
Wikipedia - Race Conditions
Wikipedia - Time-of-check-to-time-of-use
Python Mailing List - EAFP vs. LBYL Nice post!
lz所举的book属性一列中,检测属性成功的一瞬间被更改了。那不也会出现问题吗?
没理解透。 大哥,php 异常很少人用
页:
[1]