Hi, 如果有任何想法与我沟通, 请用: lifr_nj 在 msn.com

异常的深入分析和解决方案

上一篇 / 下一篇  2010-09-08 17:40:07 / 个人分类:java

首先澄清一下, 这里的"异常", 并不是指程序设计语言里的Exception, 而应该是指"Negative Situation".

异常情况处理中的问题

异常处理在程序设计里是"必须的", 但异常处理并不是简单的try catch, throw. 下面的一些疑问, 曾经困惑过我
1. 到底用返回值还是异常?
2. 到底用checked exception还是none-checked exception
3. 什么时候打印stacktrace, 什么时候不打印
4. 应该有自己的异常继承体系吗?
5. 要不要捕获library里抛出的runtime exception, 比如hibernet?

要回答这些问题, 就需要对异常处理(negative situation handling)有深入的理解.

异常的分析

对于异常情况的处理的分析, 首先对问题进行分析.
总的来说, 分为
  1.     对异常情况的感知
  2.     对异常情况的处理

异常情况的感知

  • 感知异常的方式         
            * 条件检查, 比如 if (fileNotExist)
            * Exception捕获, 比如 try { } catch (FileNotExistException) {}

  • 感知异常情况的广度
            这里是指对发生异常的最原始的位置的分类
            * 外部系统 -> 底层系统/周边系统, 比如数据库, 文件系统
            * App系统本身 -> 比如访问的对象已经在数据库里不存在了(这种情况对数据库系统来说不是异常情况,但对App系统来说是)
            * 用户输入

  • 感知异常情况的深度
            这里是指异常的情况到底有多异常. 这个深度是由App系统本身的性质决定的.
            比如你自己写的Shell程序就不用检查文件创建失败, 但对于Office Word来说,这种检查是必须的.
            以一个创建用户的function为例
                必须要感知的, 比如用户重名, 这也是业务逻辑的一部分. 否则用户就会看见database的striction错误, 这种信息对用户来说是无意义的
                可以感知的, 比如数据库不能建立连接. 告诉用户发生的错误, 并建议用户检查数据库的状态.
                一般不用感知的, 比如在function里会为用户默认分配一个组"Users", 但是这个组实际上载数据库中不存在
                ... ...
                非常不用感知的, 比如硬件资源不足.
           

异常情况的处理

  • 从系统的角度
            * recover in place:   系统在感知异常的地点能恢复, 并继续回到正常流程
            * user recover:   系统不能继续处理下去, 但系统立即提供用户recover的操作步骤
            * user solution:  系统也不能提供用户recover的操作步骤, 但系统给用户处理建议
            * user informed:  系统仅仅显示给用户错误消息, 但不能提供给用户任何建议(一般需要系统管理员处理或者软件生产商的处理)
            * out of control: 系统根本没有感知到异常, 程序直接退出, 或者用户看到stacktrace信息,一般由App系统的外围系统展示(e.g. tomcat)
           
  • 从技术的角度
            * recover in place
            * 返回值
            * 直接抛出(特殊情况, 一般情况下这样做没有意义)
            * 创建新的异常并抛出
                # 抛出Checked Excpetion
                # 抛出Unchecked Exception, 但在函数signature里声明
                # 抛出Unchecked Exception, 但不在函数signature里声明

处理异常

核心问题"到底有多异常"

异常处理问题的核心问题是"到底有多异常?", 首先要回答这个问题才能谈后面的处理方式.
但回答这个问题并不容易, 因为这并没有一个标准答案, 一种情况是否是异常,或者说有多么异常, 取决于很多因素, 主要是包括
  •     从宏观角度: 系统的业务要求
  •     从微观角度: 函数的前置条件的定义


到底采用哪种异常处理方式

对一个特定的异常处理点, 到底采用哪种异常处理方式是依据实际情况而定, 需要综合考虑多个因素. 包括
  •     异常的"异常程度",
  •     系统异常业务流程
  •     系统健壮性要求
  •     用户界面要求
  •     程序开发的性价比.
从用户角度最佳是recover in place, 最差是out of control, 但这不是绝对的. 更佳的方式要求更多的代码, 更多的工作.

从系统角度, 对异常的处理, 以Web App为例
    * user recover. 用户能看到错误信息(甚至消息已经本地化处理), 并且App系统提供了明确可行的结局方法
    例子: 用户输入错误, 返回页面重新进入编辑界面
    例子: 用户上传的文件有重名, 返回页面提供重命名的输入框
    Exception类型: ConcretRuntimeException, 实现具体的类型

    * user solution. 用户能看到错误信息(甚至消息已经本地化处理), 并且App系统提供解决方法的信息
    例子: 文件不可写, 让用户检测文件访问权限或者磁盘未满
    例子: 访问的对象已经被删除
    Exception类型: ResolvableException

    * user informed. 用户能看到错误信息, 但App系统未提供任何解决方法的信息
    这种异常一般是用户没法处理的异常. 一般需要系统管理员或者软件生产商的支持.
    这种情况一般来说是
    例子: 检测到数据完整性的条件没有满足(可能因为数据库管理员无意中删除了记录)
    例子: 
    Exception类型: AppRuntimeException

    * out of control. 用户看到的错误由App系统的外部系统捕获/处理/展现
    比如由Struts framework或者Tomcat展现的异常
    这种异常是非App系统抛出来的, 一般是由于下面的情况引起
        1) 程序应用逻辑的bug, 比如文件改名后, 还用文件的原名来访问文件.
        2) 程序没有捕获/探测应该处理的异常情况. 比如访问的Object不存在
        3) 极端的环境或者硬件错误比如Memory不足
    总的来说, 出现这样的情况都表示App系统本身的bug.

Java 异常解决方案

1) exception 层级 in java


           AppException    -> SpecificException(e.g. DBConnectionException)
     /  
Exception                                          SpecificRuntimeException(e.g. InputException)
         \                                       /
      RuntimeException -> AppRuntimeException
                                     \
                                                   ResolvableException
说明:
    # 首先来说每一个App系统应该有自己的exception层级, 方便在最外层进行集中处理
    # 对于checked exception, 因为明确需要调用者处理, 所以应该为异常定义一个特定的Exception, 否则调用者根本不知道如何处理.
    # 对于user recover的exception, 因为需要制定特定的解决步骤,所以往往Exception需要包含额外的信息, 所以也应该为之定义特定的Exception
    # 对于user solution的exception, 需要定义一个总的ResolvableException, 增加solution属性
    # 对于user informed的exception, 仅仅用AppRuntimeException即可


2) 关于checked exception和unchecked exception

绝大多数情况不要用checked exception.
对于checked exception, 一般表示明确定义的系统业务逻辑异常处理. 比如创建用户的程序, 对名字重复的处理是业务逻辑中明确定义的一部分. 所以可以创建check exception "DuplicatedNameException".
对于checked exception, 一般要求function的调用者马上处理此异常情况.
对于checked exception, 考虑用返回值是否更合适. 比如对于findById(Long id) 这样的函数, 用返回null来代替ObjectNotFoundException可能是更好的方法.

3) 关于exception的log

遵循下面的原则, 就不会出现到处出现的stacktrace, 或者漏掉重要的信息.
    1. 错误情况在最接近错误源的地方记录, 等级是error
    2. 对于AppException, AppRuntimeException不要在任何地方记录, 因为原则1
    3. 对于捕获的外部系统的exception, 一般不用记录stacktrace, 如果实在有必要, 那么等级用log而不要用error

4) App系统是否需要在最上层捕获RuntimeException

这样处理的出发点是避免out of control的情况, 向用户显示丑陋的stacktrace. 一般来说 我觉得是不需要的. 如果按照上面的设计, 出现out of control的情况一般都表示程序的bug. 发现bug才能不断提高程序的质量.

综上所述, 异常处理的两个核心问题是
捕获异常的深度与广度
处理异常的方式
理解了这两个问题, 就能在程序中更好的处理异常.

TAG:

 

评分:0

我来说两句

Open Toolbar