免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3353 | 回复: 2

[学习]EAFP 和 LBYL (关于「权限」和「原谅」以及「异常」) [复制链接]

论坛徽章:
0
发表于 2007-09-20 14:16 |显示全部楼层
偶然看 Python 的邮件列表,发现了很有意思的关于「异常」的来世今生的讨论,整理了一下,加上了一些分析,仅供各位参考。

本文统一用词

Permission 权限
Forgiveness 原谅
Exceptino 异常

关于「异常」

任何一种新的编程理念出现的时候,都不应该以机器为主导,而应该以「人类容易理解」为主导。而「异常」的出现也不例外。但是,「异常」这个概念因为有些含混,所以,并没有受到应有的重视,很多时候人们甚至抵制使用「异常」,认为使用「异常」没有好处却让程序更加复杂。本文将着重分析异常的定位和正确使用方法,以及和传统方式的对比等等。

前世 - LBYL

LBYL,就是 Look Before You Leap。简单的说,就是「用之前先检查一下」。比如以下的 pseudocode:

  1. file_handle = fopen('foo', 'rb'); // open a file

  2. if (file_ok(file_handle)) { // check if file is properly opened - LBYL
  3.     // read file...
  4. }
复制代码


上面的代码:首先,打开了一个文件;然后,检查文件是否正常打开;然后,读取文件。这就是 LBYL。再看一个例子:

  1. if (is_ready(my_thing)) { // check if my_thing is ready for process
  2.     // process my_thing
  3. }
复制代码


两个代码意思是一样的,就是用一个东西之前,先检查一下,然后使用。

今生 - EAFP

很多教材和书籍介绍「异常」的产生,往往说,是因为:有很多函数,都会返回错误代码,例如 false,但是这些函数都是嵌套型的,一个调用另一个,另一个再调用另一个,会产生以下问题:

  1. A 调用 B
  2.     B 调用 C
  3.         C 调用 D
  4.             D 出错!返回 false
  5.         C 无法处理,继续返回 false
  6.     B 无法处理,继续返回 false
  7. A 处理错误
复制代码


这就是通常认为的,「异常」的作用,就是为了解决上面这种嵌套型的问题,让程序更加清晰。但是,实际应用中,特别是「模块化」和「面向对象」等等理念的普及,使得上面这种情况越来越少,似乎「异常」也就没有了优势。

其实不然。「异常」的作用远远不止于此。

先来看看前面的方法 LBYL 的问题:

  1. file_handle = fopen('foo', 'rb'); // open a file

  2. if (file_ok(file_handle)) { // check if file is properly opened - LBYL
  3.     // read file...
  4. }
复制代码


当 file_ok() 检查通过的时候,是不是就真的说明文件是可以读取的呢?在比较复杂的情况下,例如考虑到并发访问以及硬件错误的情况下,如果程序在执行 file_ok() 那一瞬间通过了,但是文件却在下一瞬间突然不能用了呢?怎么办?

如果文件是通过网络读取,读取过程网络偶然失效怎么办?

用 LBYL 是没办法完全解决这些问题的。因为文件有可能读了一半出问题,也有可能读到第 2 个 byte 的时候出问题,也有可能读到最后一个 byte 出问题,但是却没有在你检查的时候出问题。而实践中是不可能每读一个 byte 就检查一下的。

怎么办?

用新办法,EAFP。EAFP 的名字稍微长一些,It is Easier to Ask for Forgiveness than Permission,即「要求原谅比要求权限更容易」。什么意思呢?就是说,我不检查了,我就直接用,出错了,你通知我,我「原谅」你,并且解决问题。例如:

  1. try {
  2.     fopen('foo', 'rb');
  3.     // read file
  4. } catch (io_err) {
  5.     // deal with io_err
  6. } catch (sys_err) {
  7.     // deal with sys_err
  8. }
复制代码


这种方法看似和前面一样,其实大有玄机。在这种情况下,任何 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

论坛徽章:
8
羊年新春福章
日期:2015-03-19 02:03:312015亚冠之北京国安
日期:2015-06-16 22:04:45程序设计版块每日发帖之星
日期:2015-06-23 22:20:00每日论坛发贴之星
日期:2015-06-23 22:20:002015亚冠之首尔
日期:2015-06-24 19:18:072015亚冠之广州恒大
日期:2015-08-06 10:29:442015亚冠之柏太阳神
日期:2015-11-02 11:21:0515-16赛季CBA联赛之辽宁
日期:2015-12-09 15:05:02
发表于 2012-12-30 10:38 |显示全部楼层
Nice post!
lz所举的book属性一列中,检测属性成功的一瞬间被更改了。那不也会出现问题吗?
没理解透。

论坛徽章:
0
发表于 2013-01-01 12:37 |显示全部楼层
大哥,php 异常很少人用
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP