对于错误处理我有一段时间处在迷糊的状态,一些参数或者程序本身总会引来一些错误,处理或者不处理,还真是个问题。错误处理的目的是为了防止在非法输入的情况下对程序产生破坏,但到底处理还是不处理,如何处理为什么是个问题呢?
##Garbage in, Garbage out?
在某些教科书里面可能会出现这句话(号称经典的潭老师可能根本不会去考虑这些问题),什么是“垃圾”呢,可能是空指针,可能是超长的字符串,可能是打不开的文件,更糟糕的情况是让你缓冲区越界的,注入的SQL等不怀好意的输入。这就需要调用者自己保证输入的合法性,比如一些库函数的设计中,如果出错了,可能就是暴力的 core dump。
##外部的数据vs内部的数据
对于外部传入的数据,尤其是网络传输的数据,谨慎的程序员都会抱着如临大敌的感觉,对每一个输入量进行可靠性检查,防止程序被破坏或者被获取 root 权限。对于内部传入的数据,往往是程序员自己或者其他人调用时的疏忽导致的,这时处理方式往往集中在调试阶段,在正式代码中可以忽略这些错误(断言)。以上分析是丰满的理想情况,骨感的现实是往往很难区分内部或者外部数据,函数的提供者难以知道调用者会将代码用在什么场合。
##可能的错误vs不可能的错误
可能的错误指的是那些程序实际运行时可能遇到的各种各样的情况,比如内存分配失效,打开端口失败等,我们需要做的可能是优雅的退出或者反复的重试。不可能的错误指的是程序在合法的相同输入条件下,得到的结果是唯一的,或者如果函数调用者没有犯二,输入的参数都是符合预期的。对于不可能的错误,常常在开发的时候就可以暴露出来,似似乎断言就可以处理这个问题。但是同样,这两种错误本身有时候比较难以区分,比如某个环境的温度是0度以上,程序如果读取到负的温度输入就是“不可能的错误”,但如果把这个程序应用到寒冷的环境,这个输入就是“可能的情况”。
##断言?
从上面的几种情况来看,断言似乎可以处理很大一部分问题,断言本身的确也是方便使用,同时区分了调试版本和发行版本。很多人也推崇这种用法,比较极端的就是“契约式编程”,通过前后条件限制了函数的责任和义务,不失为一种有效的开发方法。但好用容易导致滥用,有时候调试版本运行的好好的,一上线就悲剧了。同时,有些错误就是在开发时和运行时处理方式不一样,于是产生了先进行断言后进行错误处理的开发方式。滥用断言也有可能导致程序员懒得去思考错误处理的合理方式。
##异常?
反复的异常处理往往会比较麻烦,可能导致程序结构很不清晰,c语言里面有时候会用goto 来解决这个问题,但毕竟不是一个“喜闻乐见”的处理方式。于是现代语言里面往往引入了 try .. catch .. (finally) .. 的处理结构,try 里面的代码可能 throw 出一些异常,上层函数通过catch 这些异常进行比较优雅的处理。同样的,异常容易引来滥用,把一些本该自己处理的问题 throw 出去,或者 throw 了内部信息,暴露了自己的处理方式。所以异常往往用在处理真正的例外——不可忽略的错误,如果大家都在throw 异常,到处推卸责任,就会出现很多空的 catch 语句,因为调用者根本不知道如何处理某些异常。
综上所述,错误处理的基本方式就是看着办。