【问题标题】:Are try/catch for every single statement that throws an exception considered an anti-pattern?每个抛出异常的语句的 try/catch 是否被视为反模式?
【发布时间】:2011-08-17 12:56:14
【问题描述】:

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

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

那么,对于每个抛出异常的语句,try/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。)

【问题讨论】:

  • 编写示例似乎很明显这是一种反模式。但是,我仍然想要一个很好的来源或论据。

标签: java exception exception-handling anti-patterns


【解决方案1】:

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

  • 应在可以正确处理异常的地方捕获异常,并且
  • 您不能吞下异常 - 应该始终(至少)有抛出异常的痕迹,所以至少要记录它,这样至少开发人员有机会注意到发生了不好的事情。这就引出了第三点:
  • 异常应该只用于发出异常坏事件的信号,而不是控制程序流。

之前有很多关于 SO 的帖子都在处理这个问题,例如 Best practices for exception management in JAVA or C#

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 返回 0 是个好主意,还是捕获异常或传播它们是否合适,这不是我在这里关心的问题。我同意这些确实是非常重要的问题,但我的问题是针对每条语句使用大量的 try/catch。这个例子就像我写的那样只是一个构造的。
    • @bjarkef:好的,你的示例代码让我感到困惑,因为它比一般的吃异常情况和继续发生的情况更不致命(这是我最近在工作的地方看到的:-( ),我担心我没有解决您的具体问题。
    • 很抱歉。是的,只是吃例外的一般情况在这里也很常见,绝对是一种反模式(可能会产生巨大的后果),但我知道如何让我的同事相信这是一种错误的做法。跨度>
    • @bjarkef:我有一个可能适用的更一般情况的答案,stackoverflow.com/questions/7001077/…
    【解决方案3】:

    魔鬼在细节中。这取决于上下文。您预计何时会出现这些异常?例如,如果 SomeException 表示一个非常常见的业务规则违规,并且在该上下文中返回 0 是有效的,那么我可以看到上面的代码非常有效。让 try..catch 块靠近导致它的方法并不是一件坏事,因为如果将 10 个方法包装在一个巨大的 try..catch 中,确定哪个方法会导致异常可能是一件很麻烦的事。

    但是,如果这些异常表明出现了严重错误并且 SomeException 不应该发生,那么返回“0”作为魔术错误指示器隐藏可能会隐藏出现问题,它可能是维护噩梦,找出问题所在。

    如果异常不是您可以从中恢复的东西,那么您不妨将它放在 throws 子句中或将其包装并重新抛出它(如果异常是特定于实现的,我不会只将它放在 throws 中子句,因为您不想使用特定于实现的异常来污染干净的接口)。另请参阅此SO article 上适用的已检查与未检查异常。

    不记录异常也不一定是坏事。如果异常非常频繁地发生并且不是出现严重错误的指标,那么您不希望每次都写入日志,否则您将“毒害日志”(即因为如果 99% 的日志条目与 SomeException 相关,那么您错过日志中的异常和/或由于每天收到 50,000 次日志消息而耗尽磁盘空间的可能性有多大)。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 我敢肯定,如果您尝试从返回原始类型 int 的方法返回 null,编译器会报错。
      • @Anthony:是的,它已修复。正如我所写,这只是一个构建的示例。
      • 在所示示例中,捕获或未捕获哪些异常取决于抛出它们的方法。使用一个巨大的“catch”将要求所有包含的语句都具有相同的异常捕获策略。
      【解决方案5】:

      我认为您的同事已经阅读了old J2EE best practise“在尽可能接近其源的位置捕获异常”。然后他就有点过火了。

      【讨论】:

        【解决方案6】:

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

        我不相信它是反模式的原因是,它似乎不是一个好主意,这是任何东西都是反模式的要求。此外,这是解决编码问题的方法,而反模式是解决架构问题的一般(坏)解决方案。

        【讨论】:

        • 有趣的一点,但作为一个标准,它是相当主观的。显然,对于 OP 的同事来说,它确实 似乎是一个好主意......也许我们可以这样改进它:反模式是在上下文中解决问题的方法,许多人认为这很好,但从根本上来说是错误的。
        • @peter 完全是主观的。对我来说,这似乎是一种糟糕的编码风格,大多数经验丰富的开发人员不会这样做——他们会使用一个 try/catch 来处理多种异常类型并处理每种类型,或者他们会将棘手的东西分成单独的方法。
        • 确实,这对我来说也是个坏主意。我的意思是作为反模式的标准是主观的,而不是这个特定的解决方案是否是一个好主意。 IMO 可以通过相当客观的论据来为它的坏处辩护。
        • @Peter 你是什么意思?无论如何,我认为这种特殊情况与拥有 16 级嵌套 if 和循环相同。它只是不干净。恕我直言,糟糕的代码!= 反模式,但它只是语义。
        • 我似乎无法清楚地解释我的意思,抱歉......关于代码的(不)清洁度,我完全同意你的看法。我只是想评论您答案的最后一句话,您在其中给出了反模式的标准。
        猜你喜欢
        • 2019-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-18
        • 1970-01-01
        相关资源
        最近更新 更多