对于每个抛出异常的语句,我们都使用try/catch来处理,这被认为是反模式



我目前正在审查同事的Java代码,我看到很多情况下,每个可能抛出异常的语句都被封装在自己的try/catch中。其中catch块都执行相同的操作(哪个操作与我的问题无关)。

对我来说,这似乎是一种代码气味,我确实记得读到过它是一种常见的反模式。但是我找不到任何参考资料。

那么,对于抛出和异常的每一条语句,是否都认为是反模式,支持这一点的参数是什么?


构造的例子:(与原来的问题无关,所以请不要介意这个例子中的其他问题,因为它只是为了说明我的意思而构造的。)

public int foo()
{
    int x, y = 7;
    try
    {
        x = bar(y);
    }
    catch(SomeException e)
    {
        return 0;
    }
    try
    {
        y = baz(x,y);
    }
    catch(SomeOtherException e)
    {
        return 0;
    }
    /* etc. */
    return y;
}

(假设在这里捕获两个异常是合适的,即我们知道如何处理它们,并且在这两种情况下都返回0是合适的)

我无法为您提供权威的来源,但这些是异常处理的基本原则:

  • 异常应该在可以正确处理的地方被捕获,而
  • 你不能吞下异常——应该总是(至少)有一个异常抛出的跟踪,所以至少要记录它,这样至少开发人员有机会注意到发生了一些不好的事情。这就引出了第三点:
  • 异常应该只用于发出异常坏事件的信号,而不是用来控制程序流程。

之前有很多关于SO处理这个问题的帖子,例如JAVA或c#中异常管理的最佳实践。

我最初的反应是这是一件坏事。(另外,我最初也有关于捕获异常的争论,但删除了这些,以专注于问题中给出的例子。)对于异常返回0很难判断,因为不知道bar和baz做什么,也不知道异常代表什么。如果调用者真的不需要知道出了什么问题,调用者也不需要区分作为错误条件的返回值和作为数据的返回值,那么这是可以的。另一方面,如果抛出的异常是资源不可用的问题,那么最好是快速失败,而不是继续努力。

我怀疑这是一件好事,因为如果这段代码在异常时返回0是合理的,那么bar和baz至少在一开始就返回0是合理的。

一般来说,为每个语句捕获异常(而不是像示例那样立即返回默认值),这是一件不好的事情,因为一旦抛出异常,就会出现问题,并且您的方法或对象可能处于不良状态。异常处理的目的是提供一种方法,可以从出现错误的上下文中逃脱,并返回到具有已知状态的地方。如果捕获每个异常并继续下去是可以的,那么我们所有的语言都将支持"on Error Resume Next"。

细节决定成败。这取决于上下文。你预计什么时候会出现这些例外?例如,如果SomeException指示了非常常见的业务规则违反,并且在该上下文中返回0是有效的,则I可以看到上面的代码是非常有效的。让try..catch块靠近导致异常的方法并不是一件坏事,因为如果您在一个巨大的try..catch中包装了10个方法,那么找出哪个方法可能导致异常可能是一件相当繁琐的事情。

然而,如果这些异常表明某些事情已经非常错误,SomeException不应该发生,那么返回"0"作为一个神奇的错误指示器隐藏可能隐藏某些事情已经出错,这可能是一个维护噩梦,找出问题发生在哪里。

如果异常不是你可以恢复的,那么你不妨把它放在抛出子句中,或者包装它并重新抛出它(如果异常是特定于实现的,我不会只是把它放在抛出子句中,因为你不想用特定于实现的异常污染干净的接口)。另请参阅这篇关于检查异常和未检查异常的文章。

不记录异常也不一定是件坏事。如果异常发生得非常频繁,并且不是某个事情发生严重错误的指示,那么你不会想要每次都写日志,否则你会"毒害日志"(即,因为如果99%的日志条目与SomeException相关,那么你错过日志中的异常和/或你耗尽磁盘空间的可能性是什么,因为你每天得到日志消息50,000次)。

对于给定的示例,论证是它是冗余的。你只需要一个try/catch块和一个return null

我想你的同事读过旧的J2EE最佳实践"尽可能接近它的源捕获异常",然后他有点过火了。

实际上我并不认为这是一个反模式。但它绝对不是干净的代码,这几乎一样糟糕。

我不相信它是一个反模式的原因是它看起来不像是一个好主意,这是任何东西成为反模式的必要条件。此外,这是编码问题的解决方案,而反模式是架构问题的一般(坏)解决方案。

最新更新