本文是回答StackOverflow上的问题,但因为写太长了,所以就发到这里了。


http://stackoverflow.com/q/299068/315943


实际上,真正要讨论的问题并不是,“相对‘那些不会发生错误的代码’来说,‘那些以异常形式上报的错误’会有多慢?”,因为你可能也认同“已接受的回答”。相反,真正的问题是,“相对‘那些以其他形式上报的错误’来说,‘那些以异常形式上报的错误’会有多慢?”


通常认为,“不要抛出你想要捕获的异常”。所以,抛出一个其他人——如平台或框架API——要捕获的异常是合适的。或者在编写一些工具API时,抛出异常也可以的,如日志记录或消息发送,这些操作需要处理外部虚拟机的错误,例如文件IO或网络IO错误。


这是适合抛出异常的例子,应该没有人会在这些例子上有争议。现在,看一下简单方法中出现错误时会发生什么。假设方法签名如下:


/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the transformed instance,

 *         or null if the transformation was unsuccessful

 */

public SomeOtherClass transform(SomeClass input)


调用该方法的代码如下所示:


SomeClass input = ... // get the input from somewhere

SomeOtherClass result = transform(input);

if(result == null) {

   // handle the failure

} else {

   // use the result

}


但现在,当方法返回null时,我们想知道哪里出现错误了。简单来说可以这样:


/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the transformed instance, never null

 * @throws TransformationException on failure

 */

public SomeOtherClass transform(SomeClass input) throws TransformationException

 

为了实现这个功能,你需要将“return null”改为“return new TransformationException(“reason”);”。调用方法需要做改动么?


SomeClass input = ... // get the input from somewhere

try {

   SomeOtherClass result = transform(input);

   // use the result

} catch(TransformationException e) {

   // handle the failure

}


没有人会去读上面的代码块,没什么意义。所以也没什么可惊讶的。你可能每天都在写类似的代码,但也说不上是“代码异味”。可是,将来你会明白在“已预料到”的错误上使用异常是多么大的错误假设有一天你开始读到在“已预料到”的错误上使用异常是非常不好的。现在,捕获“未预料到”异常是非常可笑的,因为编写catch代码块,并显式的处理异常本身就是预料到会有异常。但没关系,我们还可以修改代码改变这种窘境。于是,我们退回到了C语言时代,返回一个异常值来表示错误。


/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the SomeOtherClass instance or, 

 *    if the transform fails, a TransformFailure instance 

 */

public Object transform(SomeClass input)


于是,调用部分的代码也不得不奇葩一些:


SomeClass input = ... // get the input from somewhere

Object result = transform(input);

if(result instanceof SomeOtherClass) {

   SomeOtherClass success = (SomeOtherClass)result;

   // use the result

} else {

   TransformFailure failure = (TransformFailure)result;

   // handle the failure

}


所以,如果你觉着使用异常有代码异味,但可以接受打破类型安全,那么你最终要面对的是难以维护,没法使用,仅仅比基于异常的解决方案快两倍的代码。但是你又不能接受类型安全被破坏,因为这2倍的性能提升还未被证明,现在就用实在太鲁莽。所以,你决定使用结果对象,而不是返回异常值。


/**

  * Transforms SomeClass into SomeOtherClass.

  * @param input some class instance

  * @return the TransformResult instance

  */

 public TransformResult transform(SomeClass input)


现在,相应地,调用部分的代码变成了这样:


SomeClass input = ... // get the input from somewhere

TransformResult result = transform(input);

if(result.isSuccess()) {

   SomeOtherClass success = result.getSuccess();

   // use the result

} else {

   TransformFailure failure = result.getFailure();

   // handle the failure

}


现在,我们有了一个隐晦,但可管理的解决方案。可是,它比使用异常的第一个方案慢2倍,即使你将TransformResult和TransformFailure合并也是一样的。再说明一遍,使用结果对象比使用异常慢,即使在调用过程中发生了错误。每次你都需要创建一个新的结果对象,这没什么实际意义,而异常对象只在发生错误的时候才会创建。


对于异常,还有一个要讨论的地方。假设有人在使用方法transform时,没有认真看javadoc。在使用异常的例子中,会有下面的代码:


SomeClass input = ... // get the input from somewhere

SomeOtherClass result = transform(input);

// use the result


在使用异常的例子中,他们知道返回值的类型,以及是否一个“已检查异常”,他们可能会得到一个编译时错误,或者他们会在throws语句中声明相应的异常。即使是“未检查异常”,错误会传递到上层调用。现在,考虑使用异常返回值的例子:


SomeClass input = ... // get the input from somewhere

SomeOtherClass result = (SomeOtherClass)transform(input);

// use the result


这个粗心的用户写的代码看起来挺漂亮,但当运行过程中发生错误时,就满不是那么回事了。那时,你费尽力气提供的错误信息会因为发生了ClassCastException异常为全部丢失。使用结果对象也不会好到哪去。


SomeClass input = ... // get the input from somewhere

SomeOtherClass result = transform(input).getSuccess();

// use the result


再说一遍,上面的代码看来相当正常。如果他们盲目使用本文中给出的第一个方法,那么在程序运行过程中,肯定会出现NullPointerException异常。


这里主要想说的是,处理逻辑错误时,使用异常的例子可以按预想的方式正常工作,报告错误信息。但是其他的解决方案却会产生一些没用的异常,即使你已经正确将软件重新部署了一遍,它仍然会出错,只有这时,你才能得到错误信息。


所以,唯一符合逻辑性的结论是,如果你想上报错误信息,那么就应该使用异常。它比结果对象性能高,比异常返回值安全,而且运行稳定。


更多相关文章

  1. 用最低的成本,提高你的代码稳壮性。
  2. 10 行 Java 代码实现最近被使用(LRU)缓存
  3. 为什么这段代码输出的是 ”Hello World”
  4. 你不知道的,Java代码性能优化的 40+ 细节,赶快收藏!
  5. 单例模式的十种写法,你会几个?(修补了几个错误点)
  6. 桥接模式在开源代码中的应用
  7. 如何写高质量的代码(完结)
  8. 接了烂代码的项目,怎么玩好?

随机推荐

  1. 内核版本与Android版本对应关系
  2. Android网络请求框架AsyncHttpClient详解
  3. android项目在不同平台切换的问题
  4. Android(安卓)安全机制
  5. Android(安卓)adb 命令大全
  6. Android(安卓)Content Provider[转]
  7. Android事件分发机制详解(二)
  8. Android(安卓)客户端与服务器端进行数据
  9. android(18)_数据存储与访问_SQLite数据
  10. 解决Android(安卓)4.2.2 脱机(offline)问