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

95

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?
            }
        }
    }
Senhor Smith
fonte
Você pode nos dar um argumento para isso? Por que você não pode lidar com todos os tipos de erro na captura de nível superior?
95011 idiotas
2
Vi esse tipo de código recentemente, realizado por programadores inexperientes que realmente não sabem o que estão chamando nos blocos try e não querem se preocupar em testar o código. No exemplo de código que eu vi, era a mesma operação, mas sempre executada com parâmetros de fallback.
Mister Smith
@LokiAstari - Seu exemplo é uma tentativa na seção Finalmente. Onde não há Catch. Este é um aninhado na seção Try. É diferente.
95011 idiotas
4
Por que deveria ser um anti-padrão?
2
+1 em "mais tentativas de captura?"
JoelFan #

Respostas:

85

Às vezes, isso é inevitável, principalmente se o seu código de recuperação gerar uma exceção.

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

Oded
fonte
17
@MisterSmith - nem sempre.
Oded
4
Sim, isso é o tipo de coisa que eu estava tentando entender. É claro que chega um ponto em suas instruções de tentativa / captura aninhadas em que você apenas precisa dizer que basta. Eu estava defendendo o aninhamento, em vez de tentar / capturar sequencialmente, dizendo que há situações em que você só deseja que o código dentro da segunda tentativa execute, se a primeira tentativa cair.
AndrewC
5
@MisterSmith: Eu preferiria tentativas aninhadas a tentativas seqüenciais que são parcialmente controladas com variáveis ​​de flag (se fossem funcionalmente iguais).
FrustratedWithFormsDesigner
31
tente {transaction.commit (); } catch {try {transaction.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } É um exemplo de uma captura necessário tentar aninhada
Thanos Papathanasiou
3
Pelo menos extraia o bloco catch para um método, pessoal! Vamos pelo menos tornar isso legível.
Sr. Cochese
43

Eu não acho que seja um antipadrão, apenas amplamente utilizado.

A maioria das tentativas aninhadas é realmente evitável e feia para o inferno, geralmente o produto de desenvolvedores juniores.

Mas há momentos em que você não pode 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 ...

Thanos Papathanasiou
fonte
19

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

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

  • Refatorar a tentativa interna ... capturar blocos em funções separadas, por exemplo, attemptFallbackMethode attemptMinimalRecovery.
  • Seja mais específico sobre os tipos de exceção específicos que estão sendo capturados. Você realmente espera alguma subclasse de exceção e, em caso afirmativo, realmente deseja lidar com elas da mesma maneira?
  • Considere se um finallybloco pode fazer mais sentido - geralmente é o caso de qualquer coisa que pareça "código de limpeza de recursos"
Mikera
fonte
14

Está bem. Uma refatoração a considerar é colocar o código em seu próprio método e usar saídas antecipadas para obter sucesso, permitindo que você escreva 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?

Depois de dividi-lo assim, você pode pensar em agrupá-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, basta usar o TrySeveralThingsStrategy, que é um tipo de estratégia composta (dois padrões pelo preço de um!).

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

Tom Anderson
fonte
7

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

FrustratedWithFormsDesigner
fonte
6

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

E é bem fácil, você só precisa conhecer uma regra prática que está escrevendo não mais do que um bloco try no mesmo método. Se você conhece bem escrever códigos relacionados juntos, geralmente é apenas copiar e colar cada bloco try com seus blocos catch e colá-lo dentro de um novo método e, em seguida, substituir o bloco original por uma chamada para esse método.

Esta regra é baseada na sugestão de Robert C. Martin em seu livro 'Clean Code':

se a palavra-chave 'try' existir em uma função, ela deve ser a primeira palavra na função e que não deve haver nada após os blocos catch / finalmente.

Um exemplo rápido de "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;
        }
    }
}

Podemos refatorar cada tentativa de captura e, nesse caso, cada bloco de tentativa e captura tenta a mesma coisa, mas em locais diferentes (como conveniente: D), temos apenas que copiar e colar um dos blocos de tentativa e captura e fazer um método dele .

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 objetivo 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 :)

Adrián Pérez
fonte
bom exemplo. este exemplo é realmente o tipo de código que precisamos refatorar. no entanto, algumas outras vezes, é necessário o try-catch aninhado.
linehrr
4

Certamente está diminuindo a legibilidade do código. Eu diria que, se você tiver chance , evite aninhar tentativas.

Se você precisar aninhar tentativas, 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 será muito mais limpo.

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

É óbvio se você precisar aninhar três ou mais níveis de try-catchs, em um único método, que é um sinal claro de tempo para refatoração.

CsBalazsHungary
fonte
3

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

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

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

Mason Wheeler
fonte
2

Resolvi essa situação assim (try-catch com 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();
Adrian
fonte
2

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

Se eu tivesse que fazer a construção de 3 objetos inválidos falhar, por exemplo, 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 era uma nova instância da classe que eu estava testando quando conseguiu.

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.

MarioDS
fonte
0

Na verdade, acho que é um antipadrão.

Em alguns casos, você pode querer várias tentativas, mas apenas se NÃO SABER que tipo de erro 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 está procurando, DEVE usar a primeira maneira, que é IMHO, feia e não funcional. Eu acho que o último é muito melhor.

Portanto, se você souber que tipo de erro está procurando, seja específico . Não há necessidade de tentativas aninhadas ou múltiplas dentro do mesmo método.

George Silva
fonte
2
Um código como o que você mostrou realmente não faz sentido na maioria, se não em todos os casos. No entanto, o OP estava se referindo a try-catchs aninhados , que é uma pergunta bem diferente da de várias declarações consecutivas.
JimmyB
0

Em alguns casos, um Try-Catch aninhado é inevitável. Por exemplo, quando o próprio código de recuperação de erro pode gerar uma 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 obter mais exemplos sobre blocos aninhados Try-Catch-Finalmente.

codelion
fonte
0

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

Se for necessário um bloco try / catch dentro de um bloco catch, você não poderá ajudá-lo. E não há alternativa. Como um bloco de captura não pode funcionar como parte de tentativa se a exceção for lançada.

Por exemplo :

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

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

algo que é sugerido não fazer é ..

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)
{
}
gauravprasad
fonte
este não parece oferecer nada substancial sobre anteriores 12 respostas
mosquito