【问题标题】:Error handling - is this a decent pattern?错误处理——这是一个不错的模式吗?
【发布时间】:2012-02-19 16:22:50
【问题描述】:

我是一个顽皮的程序员,直到现在我还没有正确处理错误(例如,只是捕获 java.lang.Exception,打印调试消息,然后继续)。当我“处理”它们时,只是关闭了编译器。

我最近发现了我的方法的错误(哈哈),并想开始做正确的事。所以我在这里和其他地方(通过谷歌搜索)研究它。

假设我有一段代码执行以下操作:

  ...  
x.method1(); //  throws ExceptionTypeA
  ...  
y.method2(); //  throws ExceptionTypeB
  ...  
z.method3(); //  throws ExceptionTypeC
  ...  
x.method4(); //  throws ExceptionTypeA (again)
  ...  

根据我收集到的信息,处理此问题的正确方法是:

try {
      ...  
    x.method1(); //  throws ExceptionTypeA
      ...  
    y.method2(); //  throws ExceptionTypeB
      ...  
    z.method3(); //  throws ExceptionTypeC
      ...    
    x.method4(); //  throws ExceptionTypeA (again)
      ...
} catch (ExceptionTypeA e) {
    //  do something about condition A
} catch (ExceptionTypeB e) {
    //  do something about condition B
} catch (ExceptionTypeC e) {
    //  do something about condition C
}

这对我来说似乎很简单,但是当我有很长的代码块时,它似乎会变得一团糟,这会引发各种错误。在我的整个方法中,我似乎只需要一次巨大的尝试/捕捉!替代方案似乎是:

try {
      ...  
    x.method1(); //  throws ExceptionTypeA
      ...
} catch (ExceptionTypeA e) {
    //  do something about condition A
}

try {
      ...  
    y.method2(); //  throws ExceptionTypeB
      ...
} catch (ExceptionTypeB e) {
    //  do something about condition A
}

try {
      ...  
    z.method3(); //  throws ExceptionTypeC
      ...
} catch (ExceptionTypeC e) {
    //  do something about condition C
}
try {
      ...  
    x.method4(); //  throws ExceptionTypeA
      ...
} catch (ExceptionTypeA e) {
    //  do something about condition A
}

这看起来真的很讨厌。在这种情况下,我考虑过执行以下操作:

private void doSomething() throws exceptionTypeA, exceptionTypeB, exceptionTypeC {
      ...  
    x.method1(); //  throws ExceptionTypeA
      ...  
    y.method2(); //  throws ExceptionTypeB
      ...  
    z.method3(); //  throws ExceptionTypeC
      ...  
    x.method4(); //  throws ExceptionTypeA (again)
      ...  

}

public void doSomething_andHandleErrors() {
    try {
        this.toSomething();
    } catch (ExceptionTypeA e) {
        //  do something about condition A
    } catch (ExceptionTypeB e) {
        //  do something about condition B
    } catch (ExceptionTypeC e) {
        //  do something about condition C
    }
}

... 然后调用 doSomething_andHandleErrors();从外面。这是一个“好”的做法吗?我是否陷入了某种反模式?

谢谢!

【问题讨论】:

标签: java error-handling


【解决方案1】:

如果方法调用之间的行数不超过几行,我会更喜欢您的第一个示例。否则你的第二个例子会更合适。我从未在上一个示例中看到过这种模式,反正在我们的代码库中也从未见过。

【讨论】:

    【解决方案2】:

    第一个示例和第二个示例之间的主要区别在于您如何处理错误本身。是事务性的吗?在您的第一个示例中,如果 x.method1() 引发异常,则 y.method2() 将不会运行。在您的第二个示例中,可能取决于错误处理的作用。

    这两个都是不错的模式,这是此处所需的业务案例的问题。您是否希望将异常传递给调用者以便他们处理它?你想因为错误而做其他事情吗?

    另外,不要忘记 finally 块。如果您正在处理资源管理(例如 IO 流、数据库连接),则需要确保使用一个,以便在必要时进行清理。

    【讨论】:

    • 同意,第一或第二,取决于所需的处理方式。第三,不太好。
    • 或 try-with-resources 而不是 finally 块。
    • 是的,如果您使用的是 Java 7,您可以使用 try-with-resources 代替 biziclop 所说的。您没有指定您使用的 java 版本。
    • 使用 try-with-resources 的唯一问题是 java 7 的早期版本中存在一个错误(不确定它是否实际上已修复),如果您无法关闭它需要的所有内容将资源嵌套在 try 部分(想想 BufferedReader 包装 InputReader 或您创建两个资源的东西,实际上只有外部资源被关闭)。
    【解决方案3】:

    通常在一个 catch 块中没有太多不同的事情要做,所以你应该瞄准一个块来处理一种行为。

    因此,如果您所做的只是记录并重新抛出异常,那么您应该选择第一个选项。您必须分别处理来自多个方法调用的每个可能的异常抛出是非常罕见的,并且在不需要时选择选项二会大大降低可读性,特别是如果您也将值分配给局部变量,您必须在外部声明和初始化try 块。

    boolean success = false;
    try {
      success = doStuff();
    } catch( ... ) {
       ...
    }
    

    这是一个非常可怕的代码模式,如果可能,我会尽量避免它。这样做的方法是意识到堆叠你的catch 块(选项二)只有在那些catch 块正常终止时才有意义(即它们没有抛出异常)。但在这种情况下,您可以将整个块移动到被调用的方法中。

     private boolean doStuff() {
       try {
           ...do stuff...
           return true;
       } catch( SomeException ex ) {
           ...fidget around...
           return false;
       }
     }
    

    而你只是这样称呼它:

     boolean success = doStuff();
    

    顺便说一句,Java 7 对异常处理有很大帮助,你可以catch multiple exceptions in one catch block, or catch and rethrow neatly。它还可以帮助您do away with catch blocks altogether 处理诸如关闭连接之类的事情。如果没有其他因素阻碍您,我会考虑改用它。

    【讨论】:

      【解决方案4】:

      您正在尝试创建干净的代码,这很好。

      恕我直言,你所做的有点过分。假设您必须处理异常,您所做的就是创建另一个方法调用。你仍然需要一个 try/catch 块。我只会按照您所说的“正确方法”来处理它。

      如果您不需要处理异常并且它们代表您无法恢复的故障,您可以创建运行时异常,这将停止您的程序(假设您没有捕获它们)。

      【讨论】:

        【解决方案5】:

        除了一件事之外,您拥有的最后一段代码看起来不错。最好是从RuntimeException 扩展异常。这样您就不必报告doSomething() 抛出的异常。

        将错误处理与其他代码分开通常是一种很好的做法。它使其他代码保持干净。

        【讨论】:

        • 您能否澄清一下“如果异常从 RuntimeException 扩展”是什么意思?
        • 检查的异常需要在它们可能被抛出的每个方法上公布,这往往会使代码混乱很多。运行时异常不需要广告,因此如果您在方法中抛出一些自定义异常,我会确保它们是运行时异常。
        • 扩展 RuntimeException 不一定“更好”——检查异常和未检查异常可以两种方式争论。已检查的异常(在我看来)可以更轻松地处理可以尝试从中恢复的异常情况,因为您知道它们是在什么情况下被抛出的。
        • 可能会出现错误处理在更高级别完成的情况,并且某些通告多个不同异常的方法可能会深埋在某些层次结构中。介于两者之间的每个方法也必须宣传它们,这会破坏封装,因为它们都知道低级异常。如果每次更改都抛出异常,则所有这些方法的签名都需要更改。根据该层次结构的大小,成本可能是巨大的,虽然它没有太多好处,但可以简单地在更高级别记录例外情况。
        【解决方案6】:

        在我看来,您的第一个和第三个示例都是处理此类情况的最佳方式,也是我通常处理代码可能抛出的大量异常的方式。就个人而言,我更喜欢第三种选择,它更有条理和简洁,并且不属于我所知道的任何反模式。第二个选项很丑陋,应该避免,因为你不需要这么多的 try 子句就可以完成工作,这只是浪费空间。

        【讨论】:

          【解决方案7】:

          处理异常的重点是,即使遇到异常,您也应该能够继续进行。第一种方式和第三种方式基本相同。如果method1() 发生异常,您将直接退出整个父方法,甚至不会尝试执行method2() 和其他方法。第二种方法一开始可能看起来很混乱,但实际上应该这样做。

          比这更好的是处理您希望它在方法本身中抛出的异常并返回一种默认值,这将允许进一​​步执行而不会破坏业务逻辑或导致不一致。

          编辑:

          使用2方法时的优势示例:

          假设您正在制作一个文本解析器,并期望date 格式为DD-MM-YYYY。但是在解析时你会发现你得到date 格式为DD-MON-YYYY。这些类型的解析异常可以被处理并且仍然允许进一步执行。

          【讨论】:

            【解决方案8】:

            如果有一些异常只有一个代码块只抛出一次,并且如果异常处理的位置不影响业务逻辑,我更愿意处理它们当场

            但是如果可以从多个地方抛出相同的异常,我喜欢在最后处理它,就像你的正确的方式

            添加一个额外的方法调用对我来说有点笨拙,因为你并没有避免这个问题,你所做的只是将它放在其他地方。

            这里缺少一个重要的东西是 finally 块,我认为不管异常处理风格如何,它都是必要的。

            当然,这是个人选择,我猜没有对错之分。

            【讨论】:

              【解决方案9】:

              这实际上取决于您想在哪里(在哪个级别)catch 该异常。

              一个确实抛出异常的方法仅仅意味着“我不想处理这个异常/问题,让其他人抓住它”。干净的代码应该遵循这种思维方式;我应该在这里抓还是不抓...

              在您的最后一种情况下,如果您再次抛出这些异常,这意味着您在错误处理期间将不需要对象 x,y,z,因为它们很可能超出范围。

              【讨论】:

                【解决方案10】:

                抛出异常的目的是通知调用者您无法完成请求的操作,捕获异常的目的是针对该失败采取适当的操作。如果您的操作不会因异常类型而有所不同,那么捕获特定类型是没有用的。

                作为捕获的异常的调用者,您的工作要么是从故障中恢复(尝试替代方法、使用默认值、whaever),要么管理故障(清理、日志记录)。

                如果异常的类型对做任何一个都有帮助,那么获取类型,如果你没有做任何一个,让它冒泡给你的调用者之一。

                【讨论】:

                  猜你喜欢
                  • 2018-03-06
                  • 2011-06-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-09-18
                  • 2012-03-25
                  相关资源
                  最近更新 更多