改进异常处理的6条建议

合理地使用异常处理可以帮你节省数小时(甚至数天)调试时间。一个乘法异常会毁掉你的晚餐乃至周末计划。如果处置不及时,甚至对你的名誉都会造成影响。一个清晰的异常处理策略可以助你节省诊断、重现和问题纠正时间。下面是6条异常处理建议。

1. 使用一个系统全局异常类

不必为每种异常类型建立单独的类,一个就够了。确保这个异常类继承RuntimeException,这样可以减少类个数并且移除不必要的异常声明。

我知道你正在想什么:如果类型只有一个,那么怎么能知道异常具体是什么?我将如何追踪具体的属性?请继续阅读。

2. 使用枚举错误码

我们大多被教授的方法是将异常转为错误信息。这次查看日志文件时很好,(呃)但是这样也有缺点:

  1. 错误信息不会被翻译(除非你是Google
  2. 错误信息不会转换为用户友好的文字
  3. 错误信息不能用编程的方式检测

将异常消息留给开发者定义也会出现同样的错误有多种不同的描述。

一个更好的办法是使用枚举表示异常类型。为每个错误分类创建一个枚举(付款、认证等),让枚举实现ErrorCode接口并作为异常的一个属性。

当抛出异常时,只要传入合适的枚举就可以了。

throw new SystemException(PaymentCode.CREDIT_CARD_EXPIRED);

现在如果需要测试异常只要比较异常代码和枚举就可以了。

} catch (SystemException e) {
  if (e.getErrorCode() == PaymentCode.CREDIT_CARD_EXPIRED) {
  ...
  }
}

通过将错误码作为查找资源的key就可以方便地提供友好的国际化文本。

public class SystemExceptionExample3 {

    public static void main(String[] args) {
        System.out.println(getUserText(ValidationCode.VALUE_TOO_SHORT));
    }

    public static String getUserText(ErrorCode errorCode) {
        if (errorCode == null) {
            return null;
        }
        String key = errorCode.getClass().getSimpleName() + "__" + errorCode;
        ResourceBundle bundle = ResourceBundle.getBundle("com.northconcepts.exception.example.exceptions");
        return bundle.getString(key);
    }

}

3. 为枚举添加错误值

在很多时候可以为异常添加错误值,比如HTTP返回值。这种情况下,可以在ErrorCode接口添加一个getNumber方法并在每个枚举中实现这个方法。

public enum PaymentCode implements ErrorCode {
  SERVICE_TIMEOUT(101),
  CREDIT_CARD_EXPIRED(102),
  AMOUNT_TOO_HIGH(103),
  INSUFFICIENT_FUNDS(104);

  private final int number;

  private PaymentCode(int number) {
    this.number = number;
  }

  @Override
  public int getNumber() {
    return number;
  }

}

添加错误码可以是全局数值也可以每个枚举自己负责。你可以直接使用枚举里的ordinal()方法或者从文件或数据库加载。

4. 为异常添加动态属性

好的异常处理还应该记录相关数据而不仅仅是堆栈信息,这样可以在诊断错误和重现错误时节省大量时间。用户不会在你的应用停止工作时告诉你他们到底做了什么。

最简单的办法是给异常添加一个java.util.Map字段。新字段的职责就是通过名字保存相关数据。通过添加setter方法可以遵循流式接口

可以像下面示例这样添加相关数据并抛出异常:

throw new SystemException(ValidationCode.VALUE_TOO_SHORT)
  .set("field", field)
  .set("value", value)
  .set("min-length", MIN_LENGTH);

5. 避免不必要的嵌套

冗长的堆栈信息不会有任何帮助,更糟糕的是会浪费你的时间和资源。重新抛出异常时调用静态函数而不是异常构造函数。封装的静态函数决定什么时候嵌套异常什么时候只要返回原来的实例。

public static SystemException wrap(Throwable exception, ErrorCode errorCode) {
  if (exception instanceof SystemException) {
    SystemException se = (SystemException)exception;
    if (errorCode != null && errorCode != se.getErrorCode()) {
      return new SystemException(exception.getMessage(), exception, errorCode);
    }
    return se;
  } else {
    return new SystemException(exception.getMessage(), exception, errorCode);
  }
}

public static SystemException wrap(Throwable exception) {
  return wrap(exception, null);
}

Your new code for rethrowing exceptions will look like the following.

} catch (IOException e) {
  throw SystemException.wrap(e).set("fileName", fileName);
}

6. 使用带Web支持的集中式logger

再额外附赠一个建议。可能你情况很难向产品记录日志,这个麻烦可能来自多个中间商(很多开发者不能直接访问产品环境)。

在多服务器环境下情况可能会更糟。找到正确的服务器或者确定问题影响到了哪个服务器是一件非常令人头痛的事情。

我的建议是:

  1. 将你的日志记录到一个地方,推荐记录到数据库中。
  2. 通过Web浏览器访问数据库。

有很多方法和备选产品可以达成这一目标,log collector、远程logger、JMX agent、系统监视软件等。甚至可以自己写一个。重要的是要快速行动,一旦你达成了目标,你就可以:

  • 几秒钟之内定位错误
  • 为每个异常增加一个URL,可以记录或者发送email
  • 让你的伙伴可以在没有你的情况下定位错误原因
  • 避免测试人员为同一个bug添加多个记录。他们可以在bug记录里增加一条异常URL
  • 省钱
  • 让你的周末和名誉不受影响

你有什么好的建议吗?

希望这些建议对你有所帮助。给异常添加正确的信息和将异常放在易于访问的地方可以避免很多灾难事故和时间浪费。如果你有一些自己的异常处理秘诀,欢迎分享。

下载

这里包含了本文的所有代码(包括Eclipse项目)。代码的发布遵循Apache 2.0协议

祝编程快乐!

原文链接: northconcepts 翻译: ImportNew.com - 唐尤华
译文链接: http://www.importnew.com/5616.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 唐尤华

我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其实我是一个程序员。(新浪微博:@唐尤华

查看唐尤华的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

2 条评论

  1. jiaozg 说道:

    最后能把完整的代码贴出来么?

    Thumb up 0 Thumb down 0

  2. Techni 说道:

    Fluent interface, 翻译成流式比较好。
    连贯接口看了半天没反应过来。。。

    Thumb up 0 Thumb down 0

跳到底部
返回顶部