在写方法(函数)的过程中,总结了如下原则:
【问题1】、一个方法执行时走到了错误的流程,应该返回一个标识呢,还是throw一个错误?
A、返回一个标识,例如,执行失败,则返回null、-1等。
优点:执行失败时,返回null或者-1,调用者根据返回的标识即可判断执行是否有错。不需要try-catch。
缺点:通常,调用者不会去判断返回的结果是什么,它也不知道返回值代表的标识是什么,所以当返回null、-1时,可能导致后期无法预知的错误。
B、throw一个错误,终止程序。
优点:无需定义错误标识,而且throw错误时,可以给出错误信息。
缺点:调用者,需要用try-catch去捕获错误,比较麻烦。
总结:对于那种公用的方法,还是采用方案B较好,可以约定,一律采用方案B。
【问题2】、什么样的错误往外抛,什么样的错误内部处理?还有,错误日志的记录 应该在哪一层做处理(里层还是外层)?
引入自定义 Runtime Exception(ZtyRuntimeException)和自定义 Checked Exception(MedialException)。
前者,自动中断程序运行,默认情况下将堆栈信息打印到控制台,通过重载Thread.setDefaultUncaughtExceptionHandler方法,可以改变其策略,将运行时异常处理后再写入日志中。
后者必须在外层程序中捕捉进行处理。最佳的流程是:如果不需要后续处理,则直接抛出Runtime Exception,否则通过MedialException传递Exception到最外层,由最外层将错误代码和错误信息展示或者传给第三方系统。
日志的处理一定要定位准确,所以通常情况下,哪里出错,就在哪里记录日志,不要在外层写日志,外层只负责处理错误后的程序逻辑,不负责写日志的工作。
一个例子:
D程序调用C程序,C程序调用B程序,B程序调用A程序,现在A程序出错了。
情景一:
D程序需要得到执行是否正确的回调信息,然后做相应处理。
那么,错误处理应该为:A抛出MedialException(errorCode, errorMsg),B、C和A一样,D捕获MedialException异常,将errorCode和errorMsg转发到视图层或者调用方。
情景二:
D、C、B程序不需要回调信息,执行不成功只需要终止就行了。无论在哪里终止都可以,框架会捕获一个全局异常信息,然后显示全局错误页面。
情景三:
D、C、B、A流程环环相扣,联系紧密,但是都没有需要显示捕获的异常,正常情况下,是不会出错的。只有当出现未捕获的RuntimeException时,才会导致程序异常终止,
这种终止是未曾预料到的,可能导致严重后果。
情景二分析:直接抛出自定义的运行时异常ZtyRuntimeException,全局捕获到时,打印错误信息就可以了。
情景三分析:
首先我们应该分析,哪些步骤是关键性且不允许出现异常的(例如一个操作具有“要么全部成功、要么全部不成功”的特点时),需要尽量预料哪些地方容易出错,进而捕获错误。
其次,无论我们怎么努力,都有一些无法预料的错误出现,那么这个时候,我们只能考虑是否要将这些错误信息记录下来?如果要记录下来,一种方式,是通过全局的错误处理,另一种是针对性的处理(例如:全局处理是打印堆栈信息到控制台,但是如果某个功能出错时你想要记录日志)。针对性处理,可以在入口处捕获整个方法,然后做专门处理。
结合情景一、二、三,一个功能最齐全的处理流程是这样的:
A抛出MedialException错误信息给B,同时A调用了两个工具类,可能出现自定义的运行时异常ZtyRuntimeException和未捕获的运行时异常RuntimeException(及其子类)
B抛出MedialException错误信息给C,
C抛出MedialException错误信息给D,D收到MedialException异常后,将errorCode和errorMsg发送到页面上。
由于D过程十分重要,不允许出现未知异常,但是未知异常是无法避免的,为了捕获D在执行过程中出现未知异常,我们在D的整个方法上,加上了如下代码:
try{
// ..... 整个D的方法
}catch (TreRuntimeException e) {
LOG.error( e.getMessage() );
}catch (RuntimeException e) {
LOG.error( e );
}
这样,即使D出现了未知错误,也可以记录日志,便于我们分析。其中对TreRuntimeException错误进行了区别对待,因为TreRuntimeException的错误信息已经经过了加工,它的errorMsg里面自带了一手堆栈信息,故不需要像未知RuntimeException那样记录堆栈信息了。