【问题标题】:What is the point of finally in a try catch/except finally statement在 try catch/except finally 语句中 finally 的意义是什么
【发布时间】:2012-03-13 16:16:34
【问题描述】:

多年来,我一直在多种语言中使用 try-catch/except-finally 变体,今天有人问我 finally 的意义何在,我无法回答。

基本上,为什么您要在 finally 中放置一个语句,而不是在整个 try-catch 块之后放置它?或者换句话说,以下代码块之间是否存在差异:

try{ //a}
catch {//b}
finally {//c}


try{//a}
catch{//b}
//c

编辑:
人,我知道 finally 会做什么,我已经使用了很长时间了,但我的问题是在上面的示例中,将 //c 放入 finally 似乎是多余的,不是吗?

【问题讨论】:

    标签: exception-handling finally


    【解决方案1】:

    finally 块的目的是确保代码在三种单独使用“catch”块不能很干净地处理的情况下运行:

    1. 如果try 块中的代码通过return 退出
    2. 如果catch 块中的代码要么重新抛出捕获的异常,要么——意外或有意——最终抛出一个新异常。
    3. 如果try 块中的代码遇到try 没有catch 的异常。

    可以在每个 return 或 throw 之前复制 finally 代码,并将 catch 块包装在它们自己的 try/catch 中,以允许发生意外异常的可能性,但放弃所有这些要容易得多并简单地使用 finally 块。

    顺便说一句,我希望语言设计人员包括的一件事是finally 块的exception 参数,以处理需要在异常发生后进行清理但仍希望它渗透到调用中的情况堆栈(例如,可以将构造函数的代码包装在这样的构造中,如果构造函数要退出异常,则 Dispose 正在构造的对象)。

    【讨论】:

    • 我们试过了,你抓住了,我们终于有了这个问题的答案!
    • 我认为这里的关键是即使try/catch 语句中有return 语句,它也会执行。如果没有这种理解,finally 子句似乎很简单,因为知道catch all 本质上将允许try/catch 块之后的代码执行,而不管是否有异常。非常感谢,@supercat!
    • @Swivel:使用finally 处理捕获情况下的返回和(通常是重新)抛出的异常都比没有处理更容易。
    • 对不起,第三个错了。您可以在控制台应用程序 Visual Studio 2019 中尝试简单的 try 和 finally 块。在 try 块中抛出异常,不要捕获它,finally 块将永远不会被调用...
    • 变化在哪里?我担心 #3 仍然是错误的。请尝试一下。打开 Visual Studio 2019、Console 应用程序、.Net Framework 4.7 并在 main 中编写,A try 和 finally 块没有捕获。在 try 块内抛出异常,并判断 finally 块是否正在执行。
    【解决方案2】:

    即使在 try 块中抛出异常,Finally 块也会被执行。因此,例如,如果您之前打开了一个流,您可能希望关闭该流,无论是否引发异常。 finally 块对于此类问题很有用。

    【讨论】:

    • 但是你也可以在 try-catch 之后关闭流,而不需要 finally 块。
    • 这不能回答 OP 的问题。
    【解决方案3】:

    finally 是一个语法糖,允许 try-catch 模式中的 DRY 原则。如果库代码没有足够的信息来处理某些状态并希望客户端代码解决它,通常会抛出异常。如果你没有库-客户端代码分离,你可以通过if而不是try来处理所有事情。

    让我们看看没有finally的标准情况:

    void myFunction() {
      var r = allocateResources();
      r.doSomething();
      if(somethingBadHappens) {
        freeResources(r);
        throw new Exception(CODE42);
      }
      r.doSomethingMore();
      freeResources(r);
    }
    

    在上面的 sn-p 中,您重复 freeResources():这可以是您需要重复的多个语句。这闻起来很臭,finally 块是干净代码的解决方案:

    void myFunction() {
      var r = allocateResources();
      try {
        r.doSomething();
        if(somethingBadHappens) throw new Exception(CODE42);
        r.doSomethingMore();
      }
      finally {
        freeResources(r);
      }
      happyFunction();
    }
    

    让我们实现三个抽象层次:

    • A1是提供allocateResources()函数的库代码
    • A2是我们提供myFunction的代码,消耗A1
    • A3 是一些在 try-catch 块中消耗 myFunction 的客户端代码:
    function A3code() {
      try {
        myFunction();
        doSomething();
      }
      catch(Exception e) {
        // no hanging resources here
        Console.WriteLine(e);
      }
    }
    

    现在让我们看看会发生什么:

    • 如果allocateResources()在A1中抛出,我们不知道如何在A2中处理它(A2代码可以在无控制台的环境中运行),所以我们将情况委托给A3而不添加任何进一步的代码。 如果这里抛出异常,finally块不会被执行,因为finally绑定到try,而try没有被输入。
    • 如果somethingBadHappens在try块中,堆栈展开到处理情况的A3但在finally块执行之前,所以如果没有异常我们不需要重复发生。
    • finally 之前,我们可以添加catch 块并尝试解决A1 中可能出现在调用r.doSomething 方法中的一些潜在异常。通常我们希望尽快处理异常,以使客户端代码 (A3) 更适合客户端编码人员。
    • happyFunction() 仅在 myFunction()try 块内部或外部)中没有抛出任何内容时执行。

    正如@supercat 指出的那样,如果try 块通过返回退出,finally 块也会被执行。我建议你避免这个坏习惯,并且在每个函数中只有一个返回(可能在函数的一开始就存在一些早期)。单返回函数的原因是:

    1. 代码更具可读性:你看看最后,看看函数返回什么。在多次返回中,您必须找到所有返回出现,检查所有 if,考虑 if 何时满足,然后才知道函数返回什么。
    2. 代码可以被编译器优化,见copy elision

    多次返回的原因是避免了许多嵌套的 if,但还有其他技术可以解决它。 Exceptions 是这条规则的例外。

    【讨论】:

      【解决方案4】:

      通过例子学习

      let v = 0;
      function f() {
          try {
              v = 1;
              return 2;
          } finally {
              v = 3;
              return 4;
          }
          v = 5;
          return 6;
      }
      
      const r = f();
      console.log(r, v);
      

      以下打印“3, 4”

      【讨论】:

        【解决方案5】:

        Finally 确保即使您遇到异常也执行您的代码。

        finally 块对于清理在 try 块中分配的任何资源以及运行任何必须执行的代码(即使出现异常)很有用

        http://msdn.microsoft.com/en-us/library/zwc8s4fz(v=vs.80).aspx

        【讨论】:

        • 但是我提到的两个代码块有什么区别?在这两种情况下,//c 都会被执行,对吧?
        • 请参阅已编辑的问题。
        • @Ali - finally 块允许您清理分配的所有资源,如果您没有在本地处理异常,则允许 excpetion 传播调用堆栈。
        • 在第二个版本中 //c 不会被执行,如果 //a 中的代码调用'return' 来退出函数。 finally 确保调用 //c,即使函数提前退出。 ...schwehr.org/blog/archives/2012-03.html#e2012-03-01T13_11_15.txt
        • 不回答实际问题。
        猜你喜欢
        • 2013-05-18
        • 2018-02-24
        • 1970-01-01
        • 2018-03-19
        • 2013-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多