【问题标题】:Try-catch: is this acceptable practice?Try-catch:这是可以接受的做法吗?
【发布时间】:2011-11-19 17:40:33
【问题描述】:

我们从软件供应商处收到了 Java 代码。它包含很多 try-catch 块,catch 部分没有任何内容。他们到处都是。示例:

        try {
            spaceBlock.enable(LindsayModel);
        } catch (Exception e) {
        }

我的问题是:上述做法是否可以接受?如果有,什么时候?还是我应该继续删除所有这些“虚假”trycatch 声明?

在我看来,这似乎是一种糟糕的做法,但我在 Java 方面的经验还不够,无法确定。如果你不打算对它们做任何事情,为什么还要捕获错误呢?在我看来,只有当您确信异常绝对没有任何后果并且您不在乎是否发生异常时,您才会这样做。但是,在我们的特定应用程序中并非如此。

编辑 提供一些背景信息:我们从供应商处购买了可编写 Java 脚本的产品。除了产品之外,他们还提供了一个根据我们的需求量身定制的大型概念验证脚本。这个脚本是“免费”提供的(尽管如果它没有附带脚本,我们就不会购买该产品)并且它“有效”。但是构建脚本真的很痛苦,因为很多事情连我作为一个 Java 新手都认为是糟糕的做法,一个例子就是这种虚假的 try-catch 业务。

【问题讨论】:

  • 嘿,看起来像On Error Resume Next :)
  • 附带说明,不幸的是,由于 Java 的异常规则,删除它们可能不是那么容易。也就是说,您将不得不更改您的方法签名以声明它们抛出该异常...
  • ExceptionException 的某个子类?
  • 吞下异常是 Java 中最可怕的事情。消除所有的多头*将需要相当多的时间,并且可能会发现许多问题。更换供应商。
  • “我们从软件供应商处收到了 Java 代码”:我建议不要付钱。

标签: java error-handling try-catch


【解决方案1】:

这确实是一种糟糕的做法。尤其是捕获Exception 而不是特定的东西会散发出可怕的气味——即使是NullPointerException 也会被吞下。即使确定某个特定抛出的异常没有真正的后果,至少应该始终记录它:

try {
    // code
}
catch (MyInconsequentialException mie) {
   // tune level for this logger in logging config file if this is too spammy
   MY_LOGGER.warning("Caught an inconsequential exception.", mie);
}

但是,在这种情况下,异常不太可能完全没有意义。我建议准确研究应用程序的代码打算在这里吞下哪些异常,以及它们对执行的真正意义。

一个重要的区别是 try/catch 是否用于吞下检查的异常。如果是这种情况,它可能表明程序员的极端冷漠 - 有人只是希望他/她的代码能够编译。至少,代码应该被修改:

try {
   // code
}
catch (SpecificCheckedException sce) {
   // make sure there is exception logging done farther up
   throw new RuntimeException(sce);
}

这将重新抛出包含在未经检查的RuntimeException 中的异常,从而有效地允许代码编译。然而,即使这也可以被视为创可贴——检查异常的最佳实践是单独处理它们,无论是在当前方法中,还是通过在方法签名中添加throws SpecificCheckedException 来进一步处理。

正如@Tom Hawtin 提到的,new Error(sce) 可以用来代替new RuntimeException(sce),以规避任何额外的Exception 捕获更远,这对于预期不会被抛出的东西是有意义的。

如果 try/catch 没有被用来吞下检查的异常,它同样危险,应该简单地删除。

【讨论】:

  • 通常情况下,您不希望在发生此类异常的情况下执行 catch 块之后的代码。因此,您最好继续使用throw new Error(exc); 之类的内容。某种日志记录适用于您将继续执行的异常(例如,事件线程通常会从事件处理程序捕获所有异常(ThreadDeath 除外)并传递给异常处理程序。
  • @Tom - 同意。该示例仅适用于实际上无关紧要且不需要重新抛出的假设异常。实际上,我怀疑情况是否如此。 OP 应该研究什么被吞下(检查/未检查或只是未检查?)。我只是想表明至少应该始终进行日志记录以提供可见性。
  • @Tom - 顺便说一句,您的 throw new Error(exc);throw new RuntimeException(exc); 之间有区别吗?
  • 没有太大区别。 catch (Exception exc) 只会捕获后者。一般来说,它可能会被扔得更远一些。如果您不希望抛出异常,则某些描述的Error 通常是正确的抛出(AssertionError 可能更好,但它没有那个构造函数,所以代码会有点过长)。
  • “我建议研究究竟是什么......”如果一个现已离职的同事创建了此代码,我会同意。对于仍然在场的同事,我会把它还给他们并告诉 他们 修复它。但这是“来自软件供应商”,所以我会拒绝它,告诉他们修复它,推迟付款直到他们收到,然后永远再使用它们。
【解决方案2】:

确实很糟糕。吞下这样的异常可能很危险。你怎么知道是否发生了不好的事情?

如果供应商写 cmets 来记录并承认它(“我们知道我们在做什么”),我会感觉更好。如果代码中有明显的策略来处理后果,我会感觉更好。将其包装在 RuntimeException 中并重新抛出;将返回值设置为适当的值。任何事物!

“到处都是”?是否有多个 try/catch 块乱扔代码?就个人而言,我不喜欢这个成语。我更喜欢每种方法一个。

也许您应该寻找新的供应商或自己编写。

【讨论】:

  • 每个方法有多个 try/catch 块,并排甚至嵌套。
  • 我认为这很糟糕。我会以此为线索寻找另一个供应商或编写自己的供应商。听起来他们对如何正确分解和构造代码一无所知。这对他们的能力来说并不是一个好兆头。
  • +1 for "wrote cmets to document":在某些情况下可以吞下一些异常。
【解决方案3】:
    try {
        meshContinuum.enable(MeshingModel);
    } catch (Exception e) {
    }

这看起来像是未完成的代码。如果 enable 方法抛出异常,那么它将被代码捕获并吞下。如果没有,那么尝试捕获未发生的异常是没有意义的。

检查方法及其签名后没有throws exceptionName 的位置,然后从调用它们的位置删除空的try-catch 语句。

理论上,您可以将 try-catch 放在任何语句周围。编译器不会抱怨它。但这没有任何意义,因为这样可能会隐藏真正的异常。

您可以将此视为代码质量不佳的标志。您可能也应该准备好遇到不同类型的问题。

【讨论】:

  • 是的,你敢打赌我也遇到了不同类型的问题!
【解决方案4】:

这不是最好的:

  • 它隐藏了异常的证据,因此调试更加困难

  • 可能会导致功能静默失败

  • 这表明作者实际上可能想要处理异常,但从未解决过

因此,可能存在这样的情况,例如确实没有任何后果的异常(想到的情况是 Python 的 mkdirs,如果目录已经存在则抛出异常),但通常,这不是很好。

【讨论】:

    【解决方案5】:

    不幸的是,您不能只删除它,因为 try 块会引发检查异常,然后必须在方法的 throws 子句中声明它。然后方法的调用者必须catch 它(但如果调用者也有这个catch (Exception e) {} abomination)。

    作为替代方案,请考虑将其替换为

    try {
        meshContinuum.enable(MeshingModel);
    } catch (Exception e) {
        throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
    }
    

    由于 RuntimeException(以及扩展它的类)是未检查异常,它们不需要在 throws 子句中声明。

    【讨论】:

    • 我建议用它的专用子类替换RuntimeException(例如, WrappedException) and lengthy line by a method call like throw wrap(e)` 其中wrap 是声明返回WrappedException 的方法但实际上总是扔掉它。
    • IIRC throw e 丢失堆栈跟踪。最好是if (e instanceof RuntimeException) { throw; } else { throw new RuntimeException(e); }
    • @AndrewPiliser throw 无效,也许你在想C# :)
    • @maaartinus 刚刚看到您的评论。我喜欢WrappedException 的想法。在我最初回答两年后,我什至认为即使是被捕获的 RuntimeException 也应该被包装,以便知道具有空 catch 块的违规代码的位置
    【解决方案6】:

    您与供应商的合同具体规定了什么?如果他们写的东西、不好的做法和所有的东西都符合规范,那么他们会向你收取重写费用。

    最好指定一组测试,这些测试将进入许多或所有这些 try-catch 块并因此失败。如果测试失败,你有更好的理由让他们修复糟糕的代码。

    【讨论】:

    • 我们从供应商处购买了一个可编写 Java 脚本的产品。除了产品之外,他们还提供了一个根据我们的需求量身定制的大型概念验证脚本。这个脚本是“免费”提供的(尽管如果它没有附带脚本,我们就不会购买该产品)并且它“有效”,因此没有任何违约行为。但是该脚本构建起来确实很痛苦,因为很多事情连我作为一个 Java 新手都认为是糟糕的做法,一个例子就是这个虚假的 try-catch 业务。
    • 代码看起来物有所值。设置一些基本的日志记录,并将其粘贴到每个空的 catch 中,这样至少你可以看到其中一个被击中。
    • “然后他们会向你收费”:他们是否可以取决于管辖权和合同。您可能(但我不是律师)能够声称该产品不“适合用途”或“不适合其销售的一般用途”(en.wikipedia.org/wiki/Merchantable)。但似乎供应商没有获得此代码的任何费用,所以在这种情况下可能不是。
    【解决方案7】:

    表面上看是个可怕的想法,完全取决于您实际调用的内容。通常是出于懒惰或习惯性的不良做法。

    【讨论】:

      【解决方案8】:

      其实……没那么快。

      存在忽略异常的合理情况。

      • 假设已经发生异常,并且我们已经在 catch() 中。在 catch() 中,我们希望尽最大努力进行清理(这也可能失败)。返回哪个异常?当然是原版的。代码如下所示:
          try {
              something-that-throws();
          } catch(Exception e) {
              try {
                  something-else-that-throws();
              } catch(Exception e1) {}
              throw e;
          }
      
      • 当我们真的不关心操作是否成功,但(不幸的是)操作的作者在失败时抛出异常。
          if (reinitialize) {
              try {
                  FileUtils.forceDelete(sandboxFile); // delete directory if it's there
              } catch(Exception e) {}
          }
      

      以上方法在删除之前保存了一个相当折磨人的尝试,即查看 sandboxFile 是否确实存在。

      【讨论】:

        猜你喜欢
        • 2023-02-22
        • 2012-05-27
        • 1970-01-01
        • 2011-08-12
        • 2022-07-11
        • 1970-01-01
        • 2013-02-08
        • 1970-01-01
        • 2011-06-17
        相关资源
        最近更新 更多