1
thedevil5032 2012-12-16 09:19:32 +08:00
聊聊自己实践的经验:
1. try ... except, 如果只是偶尔发生异常(except), 速度上比 if ... else 快. 2. '''You shouldn't throw exceptions for things that happen all the time. Then they'd be "ordinaries".''' 如果某件事结果总是这样的(假设为A), 那么你就不需要抛出异常. 因为 A 就是常态. 3. 可预见, 可 handle 的当然可以利用异常解决啊, 这应该是设计 EAFP 的初衷之一吧? 比如, 一段代码读取文件, 但是可能传过来的文件名实际上是不存在的, 那么这时用户实际上需要创建一个新文件. try : readFile() except IOError : # 有时确实需要查文档, 或者通过试错来找出可能的异常. 另外的时候 except Exception 也可以. createFile() 针对不存在的 Key 或者两个对象相加, 我觉得首先应该从程序的逻辑上考虑能不能消除这样的可能性. 从代码上解决, 而不是留给后面的代码去判断(我的理解中异常也算是一种判断). 如果不能消除, 那么就根据正常情况和异常情况出现的概率决定用 try 或者 if 吧. 因为 try 在正常情况下比 if 快, if 在异常情况下比 try 快. 这时我的一些想法, 欢迎讨论. |
2
reorx 2012-12-16 09:49:02 +08:00
"对于我们可预见的、并且可以handle的错误我们需要异常吗?"
我个人觉得是不需要的,或者说不应该使用异常的。 比如楼上的例子,文件读取,我们很容易就能考虑到文件不存在的情况,这对我们来说是可预见而且清晰的,那么就应该先做判断: if os.path.exists(filepath): with open(filepath, 'r') as f: ... 这样和 try except IOError 有什么区别呢?你虽然知道不存在的文件路径会引发 IOError,但你不能确定这是唯一引发 IOError 的情况,那么做明确的条件判断,以后某种你所未知的情况同样引发了 IOError,你就能及时了解到,并选择更恰当的方式进行处理。 当然有些异常处理和条件判断区别是完全一样的,比如 if key in dict 和 try dict[key] except KeyError,但就像楼主引用的那句英文所说,可遇见的错误,是 ordinary situation,还是应该尽量 if 之。至少可以避免 try except 的使用不当导致的某种本应出现的错误被屏蔽的情况,那样才真的是鬼都不知道哪里错了 XD |
3
bhuztez 2012-12-16 10:52:06 +08:00
所以要用Erlang啊,模式匹配才是正解
|
4
pythonee OP @reorx
其实你的这个想法就是符合 返回错误 代码的这种形式的,但是对于python来说,这是非常不合理的想法,就拿你举的这个例子来说, if key in dict # do something 想想如果在并发的时候,这个if刚刚检测完,另外一个线程把这个key remove掉了,怎么办 |
5
pythonee OP @thedevil5032
其实正是这个”正常“情况比较难区分,文件不存在是”正常“的吗,数组越界是可预见的吗,文件不可读是预见的吗?..... 这些可预见并可handle的异常我想可以类比java中的checked exception,那IOException, IndexOutOfBoundException等都是可以预见的,但是我们能做的并不多。 另外java这种静态语言还能强迫程序员对Checked exception进行处理,而python可能要去看文档,还要想该捕捉哪些异常,想想对于大型程序库,如果对于一些普通操作,你都需要考虑每行代 码是否会抛出异常、是否有必要捕捉处理,这对于开发效率和程序员的时间来说都是非常严重的拖累 |
8
thedevil5032 2012-12-16 11:15:06 +08:00
@pythonee 举个例子, 比如我有一个容器, 容器是 Counter 类, 实际上就是 dict, 而它的 value 存的是计数值. 容器里面装的都是单词和他们在某些文本里出现的次数, 通过这个次数来决定单词出现的概率. 那么很有可能我输入的单词, 即使拼写完全正确, 并没有被容器所收录. 但显然不能因为这个单词没有被收录就认为这个单词不存在(概率 0), 所以此时我不应该使用 Error, 而是自定义 Counter.__missing__ 使它返回 1. 这可以算是一个"正常"的异常.
参考: http://blog.youxu.info/spell-correct.html 其中关于 "平滑化" 的那段. 可能并不是一个很好的例子, 但是可以说明某些情况下的 Error 是不用异常来或者判断处理的. |
9
reorx 2012-12-16 11:15:43 +08:00
@pythonee 你说得有道理,在并发的情况下,要考虑得更多。可否这样归纳,一般情况下,多用 explicit if,并发多线程等环境下,多用 try except?
|
10
bhuztez 2012-12-16 12:00:51 +08:00
函数总是返回错误代码,由调用的一方去做模式匹配,不匹配说明是碰到了意外情况则抛错,抛错直接导致process crash掉。若无必要,绝不catch。
$ cat mymodule.erl -module(mymodule). -export([do_something/0]). do_something() -> {error, sorry}. $ erl Erlang R15B02 (erts-5.9.2) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false] Eshell V5.9.2 (abort with ^G) 1> c(mymodule). {ok,mymodule} 2> {ok, Result} = mymodule:do_something(). ** exception error: no match of right hand side value {error,sorry} 3> case mymodule:do_something() of 3> {ok, Result} -> 3> success; 3> {error, Reason} -> 3> failure 3> end. failure 4> 第一,抛错是一种代价很高的操作,当且仅当发生意外的时候抛错。而如果没有抛错机制,意外情况处理起来很麻烦。 第二,从模式匹配的角度看,if/else, try/except 都是poor man's pattern match,其实你已经在用模式匹配了,只不过每次都自己人肉把模式匹配的逻辑翻译成 if/else, try/except,而且很多时候还很难处理好 很多人对Erlang有偏见,非要认为Erlang是函数式语言,程序员会不习惯。事实上,Erlang是模式匹配语言,模式匹配就是实现正确的错误处理机制的关键。Erlang的函数式特征仅仅是巧合,而不是故意要设计成这样的。Erlang的消息机制就已经是副作用了,已经不能算是纯函数式了。 在没有发生意外的时候就抛错和发生意外了还不抛错,都是不对的。非要说一种错误比另一种错误更不坏是很荒唐的。明明知道模式匹配才是正确的做法,而故意不提那也太赖皮了。恩,我就是怀疑Go的粉丝们在耍赖。 |
12
haohaolee 2012-12-16 15:23:50 +08:00
“在正式发布的产品里,速错应该不是理想的解决办法” 这得看产品和方法论,要是用户不在乎是不是crash呢,人家就想快速定位错误呢?严不严肃还是看coder的水平和态度,没理由用异常的就比使用错误码的更容易崩溃,况且用异常也可以在边界try catch all
异常的问题(我的认识范围,主要是C++)在于它对程序员的要求高,特别是在判断什么是异常什么不是方面必须有足够的经验。所以在水平参差不齐的团队中使用异常更难控制代码的质量和复杂度 |
13
pythonee OP @haohaolee 非常同意你的观点,区分什么是异常什么不是,真的是个big problem,所以也是本帖想要讨论的
但是如果从这个角度对比的话,貌似java的检查异常和非检查异常就很好呢,它提供了区分的标准 |
14
pythonee OP @bhuztez 哦,纯不纯函数不要紧,lisp要不纯,haskell也是装纯呢
erlang的函数调用貌似都是模式匹配,对于你举的例子来说,类比成go就是go版的多返回值,然后可以检查err是否为nil了,你这个不就是包装后的"返回错误码"了吗,go的error类型也只是一个接口而已啊,也可以自己定义更加具体的错误消息的,相比c语言一个冰冷的数字,好像进化了一点 |
15
pythonee OP lisp也不纯
|