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

发布新日志

  • 如何处理异常: 以函数为中心

    2010-09-16 15:39:02

    继续总结对异常处理的一些想法.

    以函数为中心

    处理异常的核心在函数. 函数是捕获异常情况, 抛出exception的地方.每一个函数都涉及健壮, 那么整个应用程序健壮性可期.

    函数要处理好异常, 核心有三点
        1) 明确自己处理流程, 包括主流程和分支流程和异常流程. 这个相当于对调用者的一个"契约"
        2) 全面的异常情况的捕获/检查点
    1. 输入参数约束
    2. 系统本身的状态
    3. 外部系统的状态
    4. 调用下层library的返回值检查
    5. 调用下层library的exception捕获
        3) 合理的异常处理方法, 根据异常的"程度"高低, 适用的方法有
    1. Assert
    2. Runtime exception
    3. Named runtime exception with declaration
    4. General runtime exception without declaration
    5. checked exception
    6. return code

    分类的异常情况的捕获和处理方法


    1) 非法输入, 且检查责任在调用者 
    如果检查非法输入完全是调用者的责任, 本函数只是为了自己的健壮性做出检查.
    那么抛出NullPointerException, 或者IllegalArgumentExcedption. 这表明了调用者存在bug, 在调用此函数时没有作相应的检查.
    比如 name 是空指针.
     

    2) 非法输入,但检查责任在函数本身
    这种情况应该纳入函数的"异常流"处理, 抛出有意义的exception, 可以用checked的exception, 也可以用named runtimeexception并且在函数签名里声明.
    由于多用户操作引起的对象被删除, 一般来说这是"比较少见但不能杜绝的异常", 那么抛出一个general的service exception是合理的. 用户可以重新尝试.

    如果检查非法输入不是调用者的责任, 本函数有责任检查输入是否合法 ,那么调用者应该知道调用为什么失败.
    抛出有意义的Exception, 且这个exception从library的exception 层次里继承.
    比如 throw new InvalidName("duplicated name")


    3)  系统处于非法状态, 函数本身无法解决
    这种情况一般表示本程序出现了bug, 应该马上退出程序. 保存现场, 而不应该继续提供任何服务.
    用断言机制是比较简单的方法.

    4)  系统处于非法状态, 函数可以恢复
    函数尝试恢复. 在log里记录这个情况.

    5) 底层系统的返回值
    如果底层系统根据返回值来标示调用成功与否, 那么返回值一定是要检查的.

    5)  底层library抛出exception
    如果底层library抛出checked 异常, 那么捕获, 并抛出named runtime exception.
    如果底层liabrary抛出none-checked异常, 一般不用捕获, 在最外层处理即可.

    对于底层liabrary出现的异常要区分是由于外部系统(文件系统,数据库系统, ...)引起的, 还是应用程序本身的问题.
    比如hibernate出现问题, 对于是connection错误, 还是查询的对象不存在是完全不同的异常.

    5)  外部系统出现问题
    这种情况一般系统本身不能处理, 能做的仅仅是体面的通知用户.
    所以java io package里大量的checked IO exception是不好的设计.

    6) 后置条件不满足
    用到的很少.

    异常处理不当导致的bug

    • 本来应该捕获的异常没有捕获, 导致系统在不正确的状态运行, 造成应用的不正确(比如数据丢失等)
    • 本来应该在A点捕获的异常没有捕获, 结果exception在B点或者下层library里抛出
    • 本来应该处理的异常没有处理/处理不正确, 导致用户的操作不能成功
    • 本来应该处理的异常没有处理/处理不正确, 用户在页面看到exception的原始信息
    • 本来应该处理的异常没有处理/处理不正确, 用户丢失了前期的工作成果(特别是提交form)
    • 本来应该处理的异常没有处理/处理不正确, 在log里看到error with stacktrace

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

    2010-09-08 17:40:07

    首先澄清一下, 这里的"异常", 并不是指程序设计语言里的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才能不断提高程序的质量.

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

    2007-06-02 09:26:38

    一个java web application,开始只是支持英文,后来需要提供中文支持。前前后后搞了几天,终于全部解决。

    这个程序基于 servlet/jsp+spring+hibernate, 数据库是MySql。我把问题分解为下面的问题
        0) MySql支持中文
        1) 字符串资源支持多国语言
        2) 页面显示中文
        3) 提交中文form
        4) Ajax中包括中文

    为了问题简单,server端都使用unicode,用utf8编码,包括MySql。

    1 MySql支持中文

    在mysql.ini中
    default-character-set=utf8

    在spring的context定义中,作如下设置
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="url">
            <value><![CDATA[jdbc:mysql://localhost/echo?useUnicode=true&characterEncoding=UTF-8]]></value>
        </property>
    </bean>

    2 字符串资源支持多国语言


    字符串资源支持多国语言
    也就是根据浏览器的locale,页面元素显示为用户能懂的文字。这里需要用到资源文件。

    3 页面显示中文

    对于servlet,写一个filter,在里面设置response.setContentType("text/html; charset=UTF-8");
    对于jsp,在文件头加一行,<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

    4 提交中文form

    还是在上面的filter中,在从request里取得parameters之前,指定编码方式
    request.setCharacterEncoding("utf8");这样request.getParameter的适合,就会用utf8的解码方式。

    5 Ajax中包括中文

    我用的是buffalo,它已经解决好这个问题了。所有中文都会用utf8编码后提交。
Open Toolbar