一些Java程序设计的原则和建议
2013年09月04日


在写方法(函数)的过程中,总结了如下原则:

 

【问题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那样记录堆栈信息了。