【问题标题】:Is this an abuse of try/finally?这是对try/finally的滥用吗?
【发布时间】:2011-01-14 21:42:25
【问题描述】:

鉴于多个返回语句是可以接受的(我有点不同意,但let us digress),我正在寻找一种更可接受的方式来实现以下行为:

选项A:多次返回,重复代码块

public bool myMethod() {
    /* ... code ... */

    if(thisCondition) {
        /* ... code that must run at end of method ... */
        return false;
    }

    /* ... more code ... */

    if(thatCondition) {
        /* ... the SAME code that must run at end of method ... */
        return false;
    }

    /* ... even more code ... */

    /* ... the SAME CODE AGAIN that must run at end of method ... */
    return lastCondition;
}

每次方法返回时看到相同的(小)代码块重复 3 次让我感觉很脏。此外,我想澄清一下,上面的两个return false 语句当然可以描述为返回中间方法......它们绝对不是“守卫语句”。

选项 B 稍微更容易接受吗?我觉得我可能会滥用 try/finally,我希望我应该做一些完全不同的事情。

选项 B:多次返回,try/finally 块(没有 catch 块/异常)

public bool myMethod() {
    try {
        /* ... code ... */

        if(thisCondition) {
            return false;
        }

        /* ... more code ... */

        if(thatCondition) {
            return false;
        }

        /* ... even more code ... */

        return lastCondition;
    } finally {
        /* ... code that must run at end of method ... */
    }
}

最后,选项 C 是我的书中最好的解决方案,但我的团队出于某种原因不喜欢这种方法,因此我正在寻找折衷方案。

选项 C:单次返回,条件块

public bool myMethod() {
    /* ... code ... */

    if(!thisCondition) {
        /* ... more code ... */
    }

    if(!thisCondition && !thatCondition) {
        /* ... even more code ... */
    }

    /* ... code that must run at end of method ... */
    return summaryCondition;
}

如果您想讨论多个退货声明,请在this question 中进行。

【问题讨论】:

  • 如果您还没有提供,我会选择 C ​​选项!你的队友对选项 C 有什么反对意见?如果“必须在方法结束时运行的代码”需要更改会发生什么?
  • 我同意多纳尔的观点。我喜欢选项 C。他们的具体反对意见是什么?
  • 我真的不想为他们说话,但据我了解,他们不喜欢选项 C 仅仅是因为它没有多个返回语句。我不确定这是一个站得住脚的立场,但这是另一个话题。
  • 如果它类似于if(!save(foo)) return false;,我建议从save(..) 抛出一个异常,以迫使人们处理这种情况而不是忽略它(或忘记它!)。如果它更接近if (count() == 0) return false;,那么一个例外显然没有意义,C 将是我的选择(使用@Loadmaster 的简化)。
  • 值得指出的是,选项 B 中使用的 try-finally 等效于在其他语言中经常受到称赞的范围绑定资源管理(SBRM,有时称为 RAII)。

标签: java language-agnostic coding-style


【解决方案1】:

异常应该是异常的,所以如果周围没有其他异常,我不喜欢选项 B(请注意,反对票的人 - 我并不是说拥有 finally 是不正确的,只是我宁愿在没有异常的情况下不拥有它- 如果你有理由请评论)

如果总是需要代码,如何重构为 2 个函数

public bool myMethod() {
    bool summaryCondition = myMethodWork();
    // do common code
    return summaryCondition;
}

private bool myMethodWork() {
   /* ... code ... */

    if(thisCondition) {
        return false;
    }

    /* ... more code ... */

    if(thatCondition) {
        return false;
    }

    /* ... even more code ... */

    return lastCondition;
}

【讨论】:

  • +1 用于正确回答问题的后半部分。但是在没有例外的情况下最终不正确的评论是完全错误的。 try/finally 的存在是为了执行某个代码块,然后无论该块如何退出,总是做其他事情 - finally 通常与异常处理相关但不限于此; finally 也用于退出处理是完全合法的。
  • 接受此答案,因为此代码比 Joachim Sauer 早五分钟发布。这是我所希望的 duh 解决方案。
  • 但我没有说不正确。我说我不喜欢它 - 这就像原来的问题一样是风格问题
  • 我同意@SoftwareMonkey; finally 块独立于任何 catch 块。事实上,Java 代码中有太多的try-catch 实例实际上应该是try-finally。换句话说,当需要清理资源并让具有更多上下文的调用者处理异常时,通常会捕获和错误处理异常。
  • @Mark:很抱歉读到了一个你可能无意的推论(但是,嘿,我还是给你 +1 了!)
【解决方案2】:

如果代码需要在有Exception 的情况下运行,那么finally 不仅是一个好的选择,而且是必须的。如果不是这种情况,则不需要finally。看起来你想找到“看起来”最好的格式。但这里没有更多的利害关系。

【讨论】:

    【解决方案3】:

    您的选项 C 解决方案距离最佳解决方案不远,因为它充分编码了您尝试完成的正确执行顺序。

    类似地,使用嵌套的 if 语句可以做同样的事情。它可能在视觉上不太吸引人,但更容易理解,并且执行流程非常明显:

    public bool myMethod() { 
        boolean  rc = lastCondition; 
    
        /* ... code-1 ... */ 
    
        if (thisCondition) { 
            rc = false;
        } 
        else {  
            /* ... code-2 ... */ 
    
            if (thatCondition) { 
                rc = false;
            } 
            else {  
                /* ... code-3 ... */ 
                rc = ???;
            }  
        }
    
        /* ... the code that must run at end of method ... */ 
        return rc;  
    }
    

    简化代码产生:

    public bool myMethod() { 
        boolean  rc = false; 
    
        /* ... code-1 ... */ 
    
        if (!thisCondition) { 
            /* ... code-2 ... */ 
    
            if (!thatCondition) { 
                /* ... code-3 ... */ 
                rc = lastCondition;
            }  
        }
    
        /* ... the code that must run at end of method ... */ 
        return rc;  
    }
    

    简化的代码还揭示了您实际想要实现的目标:您正在使用测试条件来避免执行代码,因此您可能应该在条件为 时执行该代码false 而不是在它们为 true 时做某事。

    回答您关于 try-finally 块的问题:是的,您可以滥用它们。您的示例不够复杂,不足以保证使用 try-finally。不过,如果它更复杂,它可能会。

    查看我的看法:Go To Statement Considered Harmful: A Retrospective, "Exception Handling"

    【讨论】:

      【解决方案4】:

      除非您需要跳出内部循环,否则不要滥用 try/finally。滥用do/while。

      bool result = false;
      do {
        // Code
        if (condition1) break;
        // Code
        if (condition2) break;
        // . . .
        result = lastCondition
      } while (false);
      

      【讨论】:

      • 你不能是认真的!任何人都会立即知道finally 的用途。 do { ... } while (false); 的意图远不那么明显。
      • 但是任何人都不会立即知道try 的用途——你认为“前面有危险的异常抛出代码”,而不是“一些常规的控制流结构”,你可能想要真正的异常在不执行 finally 的情况下失败(即它们是真正的未捕获异常)。由于这两种结构都很奇怪,我真正的建议是重构为两种方法,但有时需要维护很多内部状态,如果您的编码团队拒绝 if-chains,拥有这样的选项可能会有所帮助。
      • 如果你不能立即理解 finally 块在做什么,那么代码无论如何都是有缺陷的:要么 a) try 块太长(必须从 try 块的开头滚动到它的end 是一个禁忌 - 提取一些方法!)或 b)catch 块太长(它不应该做的不仅仅是一些应该快速掌握的基本清理)。因为我以前从未见过这种滥用 do/while 的行为,所以我花了两秒钟才明白它的意图——这很好地表明它不是一个很好的选择。
      • 这是一个糟糕的选择,除非在整个代码库中广泛使用;那么至少那些程序员会知道发生了什么。鉴于示例中的所有代码块,在我看来,try 块很可能 太长了。
      【解决方案5】:

      如何将它分解得更多(请原谅我在相当长一段时间中没有使用 Java 的逻辑运算符),如下所示:

      public bool findFirstCondition()
      {
         // do some stuff giving the return value of the original "thisCondition".
      }
      
      public bool findSecondCondition()
      {
         // do some stuff giving the return value of the original "thatCondition".
      }
      
      public bool findLastCondition()
      {
         // do some stuff giving the return value of the original "lastCondition".
      }
      
      private void cleanUp() 
      {
         // perform common cleanup tasks.
      }
      
      
      public bool myMethod() 
      { 
      
      
         bool returnval = true;
         returnval = returnval && findFirstCondition();
         returnval = returnval && findSecondCondition();
      
         returnval = returnval && findLastCondition();
         cleanUp();
         return returnval; 
      }
      

      【讨论】:

      • @Dolph Mathews 它看起来会更干净,但是每个方法都会被执行,不管返回值如何!
      • @Dolph Mathews - 这就是我想先写它的方式,但后来我不记得 Java 是否真的有一个 &= 运算符,并且正如 sfussenegger 提到的那样,它将失去短路评估的好处。 ..
      • 感谢您对技术术语的帮助;)我仍然觉得这有点 Perl'ish 艰难:$val = foo() unless !$val; - 至少据我所知 - 感谢上帝,我已经忘记了大部分它:)
      • 没有腐烂之类的东西。 Perl 包含了几乎所有可能的做事方式。这就是为什么阅读其他人编写的 Perl 如此令人恼火的原因 :)
      • 这是一件小事(在这个问题的上下文中),但我可能不会将提取的方法公开。它们仍然是实施细节,而不是合同的一部分。但绝对是迄今为止最干净的解决方案 +1。
      【解决方案6】:

      除非必须在方法末尾运行的代码使用方法局部变量,否则您可以将其提取到如下方法中:

      public boolean myMethod() {
          /* ... code ... */
      
          if(thisCondition) {
              return myMethodCleanup(false);
          }
      
          /* ... more code ... */
      
          if(thatCondition) {
              return myMethodCleanup(false);
          }
      
          /* ... even more code ... */
      
          return myMethodCleanup(lastCondition);
      }
      
      private boolean myMethodCleanup(boolean result) {
      
          /* ... the CODE that must run at end of method ... */
          return result;
      }
      

      这看起来仍然不是很好,但它比使用类似 goto 的构造更好。为了让您的队友相信 1-return 解决方案可能没有那么糟糕,您还可以使用 2 do { ... } while (false);break 的 (*evil grin*) 展示一个版本。

      【讨论】:

        【解决方案7】:

        这是GOTO 的理想场所

        *鸭子*

        【讨论】:

        • 嘿,值得一笑:P
        • 我假设并且希望大卫是认真的。
        • 在 C 或 C++ 中,使用 goto 是可以接受的,因为您正试图提前退出该方法。
        • @Loadmaster:在 C++ 中,通常会使用 ScopeGuard 之类的东西来执行最后的代码块。
        【解决方案8】:

        IMO 的想法是将try 块放在可能引发已知异常的一小部分代码(例如方法调用)上(例如从文件中读取,将int 读取到String) .因此,将try 块放在方法的整个代码上确实不是可行的方法,除非您实际上期望每个if 条件代码都可能引发相同的异常集。我看不出仅仅为了使用finally 而使用try 块有什么意义。

        如果我没记错的话,将大块代码放在 try 中也会使其速度变慢,但不确定在最新版本的 Java 中是否如此。

        就个人而言,我会选择选项 C。但我也没有反对选项 A。

        【讨论】:

        • 我没有考虑性能影响,我想知道是否有。
        • 异常 generation 相对 昂贵,我不认为异常 handling 或 try/finally 本身特别如此.
        • @Dolph Matthews:我真的不确定,Software Monkey 可能是对的。我想最好的办法就是用大的 try 块来分析你的代码并找出你自己。
        【解决方案9】:

        有没有理由不能简单的存储返回值,掉出if?

           bool retVal = true;
           if (retVal && thisCondition) {
           }
        
           /* more code */
        
           if ( retVal ) {
             /* ... code that must run at end of method, maybe inside an if or maybe not... */
        
           }
           return retVal;
        

        【讨论】:

        • 因为这是一个可怕的代码,存在的条件越多,它就越可怕。
        • 有时你必须打破规则。如果它解决了你的问题,那就去做吧。架构的纯粹性和抽象的概念有时会妨碍现实。
        • 当然。但是使用 try/finally 是比这更好的代码。
        • 这就是我的意思:它是否是对 try/finally 的“滥用”并不重要,如果它有效并解决了您的问题,那就去做吧。
        【解决方案10】:

        如果代码需要在任何其他代码抛出异常的情况下运行,那么finally 块是正确的解决方案。

        如果它不需要在异常情况下运行(即只需要“正常”返回),那么使用finally 将滥用该功能。

        我个人会以单返回点样式重写该方法。不是因为我虔诚地赞同这个想法(我不认同),而是因为它最适合这种方法结束代码。

        当代码变得过于复杂时(这是一种非常现实的可能性),是时候通过提取一个或多个方法来重构该方法了。

        最简单的重构是这样的:

        public boolean  myMethod() {
            boolean result = myExtractedMethod();
            /* ... code that must run at end of method ... */
            return result;
        }
        
        protected boolean myExtractedMethod() {
            /* ... code ... */
        
            if(thisCondition) {
                return false;
            }
        
            /* ... more code ... */
        
            if(thatCondition) {
                return false;
            }
        
            /* ... even more code ... */
            return lastCondition;
        }
        

        【讨论】:

        • 那我应该怎么做?这里也不例外。
        【解决方案11】:

        使用 try/finally 来控制流程对我来说就像使用 GOTO。

        【讨论】:

        • 当 goto 使程序流程更清晰且不易出错时,明智地使用 goto 有什么问题?毕竟,这就是 break、continue 和 return 的含义,尤其是标记为 break 和 continue。
        • 我认为休息没问题,继续没问题,返回也没问题,但如果你以同样的方法使用它们...... YIKES!
        • @Dolph:如果您没有在同一种方法中使用所有这些,那么您可能很久没有编写代码了。 (微笑)。 10 年或 20 年后回到我身边。
        • 我认为 GOTO 有时非常有用,但就像生活中的大多数事情一样,它们只是适度的好。在正确的时间使用该工具,您将获得回报,但如果您一直使用它,您可能会发现自己处境不佳。
        • @Software Monkey:如果我在 Java 中看到所有三合一的方法,就会头晕目眩。
        猜你喜欢
        • 1970-01-01
        • 2011-04-07
        • 2010-12-05
        • 1970-01-01
        • 2014-11-27
        • 2013-02-08
        • 2018-03-19
        • 1970-01-01
        • 2012-06-13
        相关资源
        最近更新 更多