Pulpcode

捕获,搅碎,拼接,吞咽

0%

如何处理错误

很长一段时间,我一直就纠结,什么时候应该使用if处理错误,什么时候应该使用try catch处理错误,甚至是很少有人用到的Assert,我知道大多数程序员仅仅是乱用而已。

区分

首先我们要理解,if,assert,try分别擅长处理那些错误。类似写文章一样,不要一“好”到底,一“很”到底,要学会用语言去表达一种思想。

不会终止程序逻辑运行的归类为错误,会终止程序逻辑运行的归类为异常。

因为错误不会终止逻辑运行,所以错误是逻辑的一部分。而异常就是那些不应该出现在业务逻辑中的东西。

首先要理解if本身是用来处理逻辑跳转的,其本身并不能做什么有关错误处理的事情。就一个函数而言,也就是在逻辑判断的时候返回一个值,或者直接return。而这只能算逻辑处理的一部分,最多你可以将其区分为正确的逻辑和错误的逻辑。

所以我觉得如果你写出这样的代码,绝对不是一个好做法:

1
2
3
4
if(a == null){
prinf("程序出现错误!")
return
}

而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
2
3
4
5
fd = open("my.file",CREATE_FLAGS, CREATE_MODE);
if(fd == -1){
perror("Failed to open my.file");
return 1;
}

我们会发现这种处理方式有几个不实用的地方:

这样程序代码很大一部分就可能花费在错误处理上。
我们必须对每个函数进行正确性验证,就是在调用函数的时候检查他的返回值。
它不强制你处理错误,而且在不进行处理的情况下,程序仍然能够运行,但结果是不可预知的。

还包括使用全局errno:就是在出现错误的时候,将错误代码记录到一个全局变量errno中。

1
2
3
4
5
6
7
8
int   fd;
extern int errno;
if((fd = open("/dev/dsp",O_WRONLY)) < 0)
{
printf("errno=%d\n",errno);
char * mesg = strerror(errno);
printf("Mesg:%s\n",mesg);
}

全局变量的缺点就不用多说了。