很长一段时间,我一直就纠结,什么时候应该使用if处理错误,什么时候应该使用try catch处理错误,甚至是很少有人用到的Assert,我知道大多数程序员仅仅是乱用而已。
区分
首先我们要理解,if,assert,try分别擅长处理那些错误。类似写文章一样,不要一“好”到底,一“很”到底,要学会用语言去表达一种思想。
不会终止程序逻辑运行的归类为错误,会终止程序逻辑运行的归类为异常。
因为错误不会终止逻辑运行,所以错误是逻辑的一部分。而异常就是那些不应该出现在业务逻辑中的东西。
首先要理解if本身是用来处理逻辑跳转的,其本身并不能做什么有关错误处理的事情。就一个函数而言,也就是在逻辑判断的时候返回一个值,或者直接return。而这只能算逻辑处理的一部分,最多你可以将其区分为正确的逻辑和错误的逻辑。
所以我觉得如果你写出这样的代码,绝对不是一个好做法:
1 | if(a == null){ |
而assert翻译为断言,算是一种约定,就是说如果程序运行到这里,一定是这样的结果才对,对于程序员而言,早期写断言来排查bug很重要,而且需要注意的是,assert在发行版(非debug版)之后会被编译器“注释”掉,所以在assert中写语句简直是作死。assert本身的写法也说明它并不能处理异常,只能用来报告错误。
1 | assert a != 0, "a 的值不能为0" |
try,catch,throw,就是处理异常比较好的方式了,对于一个操作,比如读取文件,或者访问网络,访问数据库,你不会知道究竟会发生什么,所以就需要有一个异常处理,保证出现异常时,你能够及时处理补救,使程序不会蹦。或者将异常扔个你的上一层,由它处理,甚至包括无论成功与否,都要释放的某些资源。如果正确的使用这些特性,我们会在后面进行介绍。
异常本身是去处理所谓的异常情况,类似于文件读写,数据库访问,网络连接,因为这其中会包含很多不确定的因素。不属于正常业务逻辑的一部分,可能有一部分人会用if来进行穷举。
但是首先的问题是异常太多了,还有就是这会使你的代码被大量的if所包裹,看上去像是业务逻辑的一部分,但实际上仅仅是防止异常情况。
顺便说下,我喜欢异常那种“跳跃”带来的方便,这种方便不类似于goto
语句。而是可以选择处理异常或者向上一层抛。这将帮你减轻很多的判断和代码量,否者你需要在每一个调用者中都想办法,用if判断返回值,保持统一。
我最近有一段代码,对传入的字典对象验证其合法性。你要判断是否缺字段,是否多字段,每个字段的类型是否正确,每个字段的值是否符合范围。想象一下,你要用if进行判断,然后还要将错误原因返回,这要写多少。但是如果你将不同的判断方式都写成一个函数,仅仅判断失败的时候向上抛就行了。总会有地方接住它们,读出Exception信息,然后返回给客户端。
当年的c语言
写过一段时间linux下得c程序,c中使用如下的方式处理错误:
1 | fd = open("my.file",CREATE_FLAGS, CREATE_MODE); |
我们会发现这种处理方式有几个不实用的地方:
这样程序代码很大一部分就可能花费在错误处理上。
我们必须对每个函数进行正确性验证,就是在调用函数的时候检查他的返回值。
它不强制你处理错误,而且在不进行处理的情况下,程序仍然能够运行,但结果是不可预知的。
还包括使用全局errno:就是在出现错误的时候,将错误代码记录到一个全局变量errno中。
1 | int fd; |
全局变量的缺点就不用多说了。