O uso de blocos try-catch aninhados é um antipadrão?

74

Isso é um antipadrão? É uma prática aceitável?

    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?
            }
        }
    }
    
por Mister Smith 09.11.2011 / 17:16
fonte

14 respostas

69

Isso às vezes é inevitável, especialmente se seu código de recuperação puder gerar uma exceção.

Não é bonito, mas às vezes não há alternativas.

    
por 09.11.2011 / 17:28
fonte
37

Eu não acho que é um antipadrão, apenas amplamente usado.

A maioria das capturas de tentativas aninhadas é de fato evitável e feia para nós, o inferno, geralmente o produto de desenvolvedores júnior.

Mas há momentos em que você não consegue evitar.

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

Além disso, você precisará de um bool extra em algum lugar para indicar a falha na reversão ...

    
por 09.11.2011 / 18:33
fonte
16

A lógica está bem - pode fazer sentido em algumas situações tentar uma abordagem de fallback, que poderia experimentar eventos excepcionais ... por isso esse padrão é praticamente inevitável.

No entanto, sugiro o seguinte para melhorar o código:

  • Refaça a tentativa interna ... bloqueie as funções separadas, por ex. attemptFallbackMethod e attemptMinimalRecovery .
  • Seja mais específico sobre os tipos de exceção específicos que estão sendo capturados. Você realmente espera qualquer subclasse de exceção e, em caso afirmativo, você realmente quer lidar com eles da mesma maneira?
  • Considere se um bloco finally pode fazer mais sentido - geralmente é o caso de qualquer coisa que se pareça com "código de limpeza de recursos"
por 09.11.2011 / 17:30
fonte
11

Tudo bem. Uma refatoração a considerar é colocar o código em seu próprio método e usar saídas antecipadas para o sucesso, permitindo que você grave as diferentes tentativas de fazer algo no mesmo nível:

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?

Uma vez que você tenha quebrado assim, você poderia pensar em envolvê-lo em um padrão de estratégia.

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");
    }
}

Em seguida, use apenas o TrySeveralThingsStrategy , que é um tipo de estratégia composta (dois padrões para o preço de um!).

Uma enorme ressalva: não faça isso a menos que suas estratégias sejam suficientemente complexas, ou você queira ser capaz de usá-las de maneira flexível. Caso contrário, você está guardando algumas linhas de código simples com uma pilha enorme de orientação a objetos desnecessária.

    
por 09.04.2012 / 23:27
fonte
7

Eu não acho que seja automaticamente um antipadrão, mas eu o evitaria se conseguisse encontrar uma maneira mais fácil e mais limpa de fazer a mesma coisa. Se a linguagem de programação em que você está trabalhando tiver uma finally , isso pode ajudar a limpar isso, em alguns casos.

    
por 09.11.2011 / 17:26
fonte
6

Não é um anti-padrão em si, mas um padrão de código que diz que você precisa refatorar.

E é muito fácil, você só precisa saber uma regra que está escrevendo não mais que um bloco try do mesmo método. Se você sabe escrever bem um código relacionado, geralmente é apenas copiar e colar cada bloco try com seus blocos catch e colá-lo dentro de um novo método, e então substituir o bloco original por uma chamada para esse método.

Esta regra é baseada na sugestão de Robert C. Martin em seu livro 'Código Limpo':

if the keyword 'try' exists in a function, it should be the very first word in the function and that there should be nothing after the catch/finally blocks.

Um exemplo rápido em "pseudo-java". Suponha que tenhamos algo assim:

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;
        }
    }
}

Então poderíamos refatorar cada captura de tentativa e nesse caso cada bloco try-catch tenta a mesma coisa, mas em locais diferentes (quão conveniente: D), temos apenas que copiar e colar um dos blocos try-catch e fazer um método disso.

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

Agora usamos isso com o mesmo propósito de antes.

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

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

Espero que ajude:)

    
por 06.06.2015 / 01:51
fonte
4

Isso certamente está diminuindo a legibilidade do código. Eu diria, se você tiver chance , então evite aninhar try-catches.

Se você tiver que aninhar try-catches, sempre pare por um minuto e pense:

  • eu tenho a chance de combiná-los?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • devo simplesmente extrair a parte aninhada em um novo método? O código ficará muito mais limpo.

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

É óbvio que se você tiver que aninhar três ou mais níveis de try-catches, em um único método, isso é um sinal certo de tempo para refatorar.

    
por 25.06.2014 / 17:00
fonte
3

Eu vi esse padrão no código de rede e, na verdade, faz sentido. Aqui está a ideia básica, em pseudocódigo:

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

Basicamente é uma heurística. Uma falha na tentativa de conexão poderia ser apenas uma falha de rede, mas se isso acontecer duas vezes, isso provavelmente significa que a máquina que você está tentando se conectar realmente está inacessível. Provavelmente existem outras maneiras de implementar esse conceito, mas elas provavelmente seriam ainda mais feias do que as tentativas aninhadas.

    
por 09.04.2012 / 23:45
fonte
2

Eu resolvi essa situação assim (try-catch with fallback):

$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();
    
por 09.04.2012 / 16:19
fonte
2

Eu "tive" que fazer isso em uma classe de teste coincidencialmente (JUnit), onde o método setUp () tinha que criar objetos com parâmetros construtores inválidos em um construtor que emitia uma exceção.

Se eu tivesse que fazer a construção de 3 objetos inválidos falhar, por exemplo, eu precisaria de 3 blocos try-catch, aninhados. Em vez disso, criei um novo método, em que as exceções foram capturadas e o valor de retorno foi uma nova instância da classe que eu estava testando quando foi bem-sucedida.

Claro, eu só precisava de 1 método porque fiz o mesmo 3 vezes. Pode não ser uma solução tão boa para blocos aninhados que fazem coisas totalmente diferentes, mas pelo menos seu código se tornaria mais legível na maioria dos casos.

    
por 09.04.2012 / 19:13
fonte
0

Eu realmente acho que é um antipadrão.

Em alguns casos, você pode querer múltiplos try-catches, mas somente se você NÃO CONHECE que tipo de erro você está procurando, por exemplo:

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)
        {
        }
    }
}

Se você não sabe o que você está procurando, você tem que usar a primeira maneira, que é IMHO, feia e não funcional. Eu acho que o último é muito melhor.

Então, se você sabe qual tipo de erro você está procurando, seja específico . Não há necessidade de try-catches aninhados ou múltiplos dentro do mesmo método.

    
por 09.11.2011 / 17:33
fonte
0

Sim, se você usá-los assim. : -)

private ParseResult<Statement> parseStatement(int p) {
    try {
        return (ParseResult) parseIfElseStm(p);
    } catch (ParseException e1) {
    try {
        return (ParseResult) parseIfStm(p);
    } catch (ParseException e2) {
    try {
        return (ParseResult) parseWhileStm(p);
    } catch (ParseException e3) {
    try {
        return (ParseResult) parseIterationStm(p);
    } catch (ParseException e4) {
    try {
        return (ParseResult) parseForStm(p);
    } catch (ParseException e5) {
    try {
        return (ParseResult) parseReturnStm(p);
    } catch (ParseException e6) {
    try {
        return (ParseResult) parseLocalDefStm(p);
    } catch (ParseException e7) {
    try {
        return (ParseResult) parseExpStm(p);
    } catch (ParseException e8) {
    try {
        return (ParseResult) parseBlockStm(p);
    } catch (ParseException e9) {
    try {
        // Try the empty statement
        parseChar(p++, ';');
        return new ParseResult<Statement>(EmptyStm.INST, p);
    } catch (ParseException e10) {
        throw new ParseException("expecting statement");
    }}}}}}}}}}
}
    
por 09.04.2012 / 19:43
fonte
0

Em alguns casos, um Try-Catch aninhado é inevitável. Por exemplo, quando o próprio código de recuperação de erro pode lançar e exceção. Mas, para melhorar a legibilidade do código, você sempre pode extrair o bloco aninhado em um método próprio. Confira esta postagem no blog para mais exemplos sobre bloqueios Try-Catch-Finally aninhados.

    
por 15.04.2015 / 02:04
fonte
-1

Não há nada mencionado como Anti Pattern em java em qualquer lugar. Sim, nós chamamos algumas coisas boas práticas e más práticas.

Se for necessário um bloco try / catch dentro de um bloco de captura, é necessário que você não o ajude. E não há alternativa. Como um bloco catch não pode funcionar como parte tente se exceção é lançada.

Por exemplo:  String str = nulo; experimentar{    str = método (a); } pegar (exceção) { experimentar{    str = doMethod (a); } catch (exceção ex) {   jogue ex; }

Aqui no exemplo acima, o método lança uma exceção, mas o doMethod (usado para tratar a exceção do método) até lança uma exceção. Neste caso, temos que usar o try catch dentro de try catch.

alguma coisa que é sugerida a não fazer é ..

tente {   ..... 1 } catch (exceção ex) { } experimentar {   ..... 2 } catch (exceção ex) { } experimentar {   ..... 3 } catch (exceção ex) { } experimentar {   ..... 3 } catch (exceção ex) { } experimentar {   ..... 4 } catch (exceção ex) { }

    
por 06.06.2015 / 01:09
fonte