使用嵌套try-catch块是否是反模式?


95

这是反模式吗?这是可以接受的做法吗?

    try {
        //do something
    } catch (Exception e) { 
        try {
            //do something in the same line, but being less ambitious
        } catch (Exception ex) {
            try {
                //Do the minimum acceptable
            } catch (Exception e1) {
                //More try catches?
            }
        }
    }

您能为此给我们案例吗?为什么不能处理顶级catch中的每种错误类型?
莫伦斯

2
我最近看到过这类代码,这些代码是由没有经验的程序员执行的,他们实际上并不知道他们在try块中调用了什么,而且他们也不想打扰测试代码。在我看到的代码示例中,它是相同的操作,但是每次都使用后备参数执行。
史密斯先生

@LokiAstari-您的示例是在“最终部分”中尝试的方法。这是嵌套在“尝试”部分中的。这是不同的。
白痴”

4
为什么应该是反模式?

2
+1表示“更多尝试捕获?”
JoelFan 2012年

Answers:


85

有时这是不可避免的,尤其是在您的恢复代码可能引发异常的情况下。

不漂亮,但有时别无选择。


17
@MisterSmith-并非总是如此。
Oded

4
是的,这就是我想要达到的目的。当然,在嵌套的try / catch语句中有一个要点,您只需要说足够就可以了。我提供的是嵌套而不是顺序try / catch的理由,说在某些情况下,如果第一次尝试失败,则只希望第二次尝试中的代码执行。
AndrewC 2011年

5
@MisterSmith:我宁愿嵌套try-catching而不是顺序性try-catches,后者由标志变量部分控制(如果它们在功能上相同)。
FrustratedWithFormsDesigner

31
试试{transaction.commit(); }捕获{尝试{transaction.rollback(); } {严重的捕获()} notsoseriouslogging(); }是必要的嵌套尝试捕获的示例
Thanos Papathanasiou

3
伙计们,至少将catch块提取为一种方法!让我们至少使它可读。
Cochese先生2014年

43

我不认为这是一种反模式,只是被广泛滥用。

实际上,大多数嵌套的try catch都是可以避免的,并且在我们看来是丑陋的,通常是初级开发人员的产品。

但是有时候您无能为力。

try{
     transaction.commit();
   }catch{
     logerror();
     try{
         transaction.rollback(); 
        }catch{
         seriousLogging();
        }
   }

另外,在某处需要额外的布尔值来表示失败的回滚...


19

逻辑很好-在某些情况下尝试回退方法可能很有意义,因为回退方法本身可能会遇到特殊事件。...因此,这种模式几乎是不可避免的。

但是,我建议采取以下措施使代码更好:

  • 将内部try ... catch重构为单独的函数,例如attemptFallbackMethodattemptMinimalRecovery
  • 更具体地说明要捕获的特定异常类型。您是否真的希望有任何 Exception子类,如果真的这样,您是否真的想以相同的方式处理它们?
  • 考虑一个finally块是否可能更有意义-对于任何感觉像“资源清理代码”的情况,通常都是这种情况

14

没关系。要考虑的重构是将代码推入其自己的方法,并使用早期退出来获得成功,从而使您可以编写不同的尝试来在同一级别上做某事:

try {
    // do something
    return;
} catch (Exception e) {
    // fall through; you probably want to log this
}
try {
    // do something in the same line, but being less ambitious
    return;
} catch (Exception e) {
    // fall through again; you probably want to log this too
}
try {
    // Do the minimum acceptable
    return;
} catch (Exception e) {
    // if you don't have any more fallbacks, then throw an exception here
}
//More try catches?

一旦这样破裂,就可以考虑将其包装为“战略”模式。

interface DoSomethingStrategy {
    public void doSomething() throws Exception;
}

class NormalStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something
    }
}

class FirstFallbackStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something in the same line, but being less ambitious
    }
}

class TrySeveralThingsStrategy implements DoSomethingStrategy {
    private DoSomethingStrategy[] strategies = {new NormalStrategy(), new FirstFallbackStrategy()};
    public void doSomething() throws Exception {
        for (DoSomethingStrategy strategy: strategies) {
            try {
                strategy.doSomething();
                return;
            }
            catch (Exception e) {
                // log and continue
            }
        }
        throw new Exception("all strategies failed");
    }
}

然后只需使用TrySeveralThingsStrategy,这是一种复合策略(两种模式的价格为一个!)。

一个巨大的警告:除非您的策略本身足够复杂,或者您希望能够灵活地使用它们,否则请不要这样做。否则,您将花费几行简单的代码,并带有大量不必要的面向对象的代码。


7

我不认为它会自动成为反模式,但是如果我能找到一种更简单,更干净的方法来做同样的事情,我会避免使用它。如果您使用的编程语言具有某种finally构造,那么在某些情况下,这可能会帮助您进行清理。


6

本身不是反模式,而是告诉您需要重构的代码模式。

这很容易,您只需要知道一条经验法则即可以相同的方法编写try块。如果您知道如何一起编写相关代码,通常只需复制和粘贴每个try块及其catch块,然后将其粘贴到新方法中,然后用对该方法的调用替换原始块。

这个经验法则是基于罗伯特·C·马丁(Robert C. Martin)在他的《清洁代码》一书中的建议:

如果函数中存在关键字“ try”,则它应该是函数中的第一个单词,并且在catch / finally块之后应该没有任何内容。

关于“ pseudo-java”的快速示例。假设我们有这样的东西:

try {
    FileInputStream is = new FileInputStream(PATH_ONE);
    String configData = InputStreamUtils.readString(is);
    return configData;
} catch (FileNotFoundException e) {
    try {
        FileInputStream is = new FileInputStream(PATH_TWO);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        try {
            FileInputStream is = new FileInputStream(PATH_THREE);
            String configData = InputStreamUtils.readString(is);
            return configData;
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}

然后我们可以重构每个try catch,在这种情况下,每个try-catch块都尝试相同的事情,但是在不同的位置(多么方便:D),我们只需要复制粘贴一个try-catch块并制作一个方法即可。 。

public String loadConfigFile(String path) {
    try {
        FileInputStream is = new FileInputStream(path);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        return null;
    }
}

现在,我们将其用于与以前相同的目的。

String[] paths = new String[] {PATH_ONE, PATH_TWO, PATH_THREE};

String configData;
for(String path : paths) {
    configData = loadConfigFile(path);
    if (configData != null) {
        break;
    }
}

希望对您有所帮助:)


好例子。这个例子确实是我们必须重构的那种代码。但是在其他时候,嵌套try-catch是必要的。
linehrr18年

4

这肯定会降低代码的可读性。我会说,如果有机会,请避免嵌套try-catches。

如果必须嵌套尝试,请始终停一分钟,然后考虑:

  • 我有机会将它们合并吗?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • 我是否应该简单地将嵌套部分提取到新方法中?该代码将更加干净。

    ...  
    try {  
      ... code  
    } catch (FirstKindOfException e) {  
       panicMethod();  
    }   
    ...
    
    private void panicMethod(){   
    try{  
    ... do the nested things  
    catch (SecondKindOfException e) {  
      ... do something else    
      }  
    }
    

很明显,如果您必须在一个方法中嵌套三个或更多级别的try-catching,那肯定是重构时间的迹象。


3

我已经在网络代码中看到了这种模式,这实际上是有意义的。这是伪代码的基本思想:

try
   connect;
catch (ConnectionFailure)
   try
      sleep(500);
      connect;
   catch(ConnectionFailure)
      return CANT_CONNECT;
   end try;
end try;

基本上,这是一种启发式方法。连接失败的一次尝试可能只是网络故障,但如果发生两次,则可能意味着您尝试连接的计算机确实无法访问。可能还有其他方法可以实现此概念,但它们最有可能比嵌套尝试更丑陋。


2

我解决了这种情况(带回退的try-catch):

$variableForWhichINeedFallback = null;
$fallbackOptions = array('Option1', 'Option2', 'Option3');
while (!$variableForWhichINeedFallback && $fallbackOptions){
    $fallbackOption = array_pop($fallbackOptions);
    try{
        $variableForWhichINeedFallback = doSomethingExceptionalWith($fallbackOption);
    }
    catch{
        continue;
    }
}
if (!$variableForWhichINeedFallback)
    raise new ExceptionalException();

2

我“必须”在测试类(JUnit)中同时进行此操作,在该类中,setUp()方法必须在引发异常的构造函数中创建具有无效构造函数参数的对象。

例如,如果必须使3个无效对象的构造失败,则需要3个嵌套的try-catch块。相反,我创建了一个新方法,捕获了异常,返回值是成功时正在测试的类的新实例。

当然,我只需要1种方法,因为我进行了3次相同的操作。对于做完全不同的事情的嵌套块来说,这可能不是一个好的解决方案,但是至少在大多数情况下,您的代码将变得更具可读性。


0

我实际上认为这是一种反模式。

在某些情况下,您可能需要多次尝试,但前提是您不知道所要查找的错误类型,例如:

public class Test
{
    public static void Test()
    {            
        try
        {
           DoOp1();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp2();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp3();
        }
        catch(Exception ex)
        {
            // treat
        }
    }

    public static void Test()
    {
        try
        {
            DoOp1();
            DoOp2();
            DoOp3();
        }
        catch (DoOp1Exception ex1)
        {
        }
        catch (DoOp2Exception ex2)
        {
        }
        catch (DoOp3Exception ex3)
        {
        }
    }
}

如果您不知道要查找的内容,则必须使用第一种方式,即恕我直言,丑陋且不起作用。我想后者要好得多。

因此,如果您知道要查找的错误类型,请具体说明。在同一方法内无需嵌套或多个try-catching。


2
在大多数情况下(即使不是所有情况),您展示的代码确实没有任何意义。但是,OP所指的是嵌套的 try-catches,与多个连续语句相比,这是一个完全不同的问题。
JimmyB,2011年

0

在某些情况下,嵌套的try-catch是不可避免的。例如,当错误恢复代码本身可以抛出异常时。但是,为了提高代码的可读性,您始终可以将嵌套块提取到自己的方法中。查看博客文章,以获取有关嵌套的Try-Catch-Finally块的更多示例。


0

在Java的任何地方都没有提到反模式。是的,我们称很少的东西是好的做法和坏的做法。

如果在catch块内需要try / catch块,则您不能帮助它。别无选择。如果抛出异常,则catch块不能作为try部分工作。

例如 :

String str=null;
try{
   str = method(a);
}
catch(Exception)
{
try{
   str = doMethod(a);
}
catch(Exception ex)
{
  throw ex;
}

在上面的示例中,方法抛出异常,但是doMethod(用于处理方法异常)甚至抛出异常。在这种情况下,我们必须在try catch中使用try catch。

建议不要做的某事是..

try 
{
  .....1
}
catch(Exception ex)
{
}
try 
{
  .....2
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....4
}
catch(Exception ex)
{
}

这似乎并不能提供超过先前12个答案的实质性内容
gnat 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.