¿El uso de los bloques anidados try-catch es un anti-patrón?

74

¿Esto es un antipatrón? ¿Es una práctica aceptable?

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

14 respuestas

69

Esto a veces es inevitable, especialmente si su código de recuperación puede generar una excepción.

No es bonito, pero a veces no hay alternativas.

    
respondido por el Oded 09.11.2011 - 17:28
37

No creo que sea un antipatrón, sino que se usa demasiado.

La mayoría de las capturas de prueba anidadas son, de hecho, evitables y desagradables, usualmente el producto de desarrolladores junior.

Pero hay veces en que no puedes evitarlo.

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

Además, necesitarás un bool extra en algún lugar para indicar la reversión fallida ...

    
respondido por el Thanos Papathanasiou 09.11.2011 - 18:33
16

La lógica está bien: puede tener mucho sentido en algunas situaciones intentar un enfoque alternativo, que podría experimentar eventos excepcionales ... por lo tanto, este patrón es bastante inevitable.

Sin embargo, sugeriría lo siguiente para mejorar el código:

  • Refactoriza el intento interno ... atrapa bloques en funciones separadas, por ejemplo. attemptFallbackMethod y attemptMinimalRecovery .
  • Sea más específico acerca de los tipos de excepción particulares que se detectan. ¿Realmente espera alguna subclase de excepción y, si es así, realmente quiere manejarlas de la misma manera?
  • Considere si un bloque finally puede tener más sentido; este suele ser el caso de cualquier cosa que parezca un "código de limpieza de recursos".
respondido por el mikera 09.11.2011 - 17:30
11

Está bien. Una refactorización a considerar es empujar el código a su propio método y usar salidas tempranas para tener éxito, lo que le permite escribir los diferentes intentos de hacer algo al mismo nivel:

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?

Una vez que lo hayas hecho así, podrías pensar en envolverlo en un patrón de Estrategia.

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

Luego solo usa TrySeveralThingsStrategy , que es un tipo de estrategia compuesta (¡dos patrones por el precio de uno!).

Una gran advertencia: no hagas esto a menos que tus estrategias sean lo suficientemente complejas o quieras poder usarlas de manera flexible. De lo contrario, está cargando algunas líneas de código simple con una enorme pila de orientación a objetos innecesaria.

    
respondido por el Tom Anderson 09.04.2012 - 23:27
7

No creo que sea automáticamente un anti-patrón, pero lo evitaría si pudiera encontrar una forma más fácil y limpia de hacer lo mismo. Si el lenguaje de programación en el que está trabajando tiene una construcción finally , en algunos casos eso podría ayudar a solucionarlo.

    
respondido por el FrustratedWithFormsDesigner 09.11.2011 - 17:26
6

No es un anti-patrón en sí mismo, sino un patrón de código que le dice que necesita refactorizar.

Y es bastante fácil, solo tienes que conocer una regla de oro que no escribe más que un bloque try en el mismo método. Si sabe bien escribir código relacionado juntos, por lo general, solo está copiando y pegando cada bloque de prueba con sus bloques de captura y pegándolo dentro de un nuevo método, y luego reemplace el bloque original con una llamada a este método.

Esta regla de oro se basa en la sugerencia de Robert C. Martin de su libro 'Clean Code':

  

si la palabra clave 'try' existe en una función, debería ser la primera   palabra en la función y que no debería haber nada después de la   captura / finalmente bloques.

Un ejemplo rápido sobre "pseudo-java". Supongamos que tenemos algo como esto:

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

Luego podríamos refactorizar cada captura de prueba y, en este caso, cada bloque de prueba de captura intenta lo mismo, pero en diferentes ubicaciones (lo conveniente: D), solo tenemos que copiar y pegar uno de los bloques de captura de prueba y hacer una método de la misma.

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

Ahora usamos esto con el mismo propósito que 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 ayude :)

    
respondido por el Adrián Pérez 06.06.2015 - 01:51
4

Seguramente está disminuyendo la legibilidad del código. Yo diría, si tiene oportunidad , luego evite anidar intentos de captura.

Si tiene que anidar las capturas de prueba, siempre pare por un minuto y piense:

  • ¿Tengo la oportunidad de combinarlos?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • ¿debería simplemente extraer la parte anidada en un nuevo método? El código será mucho más limpio.

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

Es obvio si tiene que anidar tres o más niveles de try-catches, en un solo método, eso es una señal segura de tiempo para refactor.

    
respondido por el CsBalazsHungary 25.06.2014 - 17:00
3

He visto este patrón en el código de red, y en realidad tiene sentido. Aquí está la idea básica, en pseudocódigo:

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

Básicamente es una heurística. Un intento fallido de conexión podría ser simplemente una falla de la red, pero si ocurre dos veces, eso probablemente significa que la máquina a la que intentas conectarte realmente no es accesible. Probablemente hay otras formas de implementar este concepto, pero es probable que sean incluso más feas que los intentos anidados.

    
respondido por el Mason Wheeler 09.04.2012 - 23:45
2

Resolví esta situación de esta manera (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();
    
respondido por el Adrian 09.04.2012 - 16:19
2

He "tenido" que hacer esto en una clase de prueba coincidencialmente (JUnit), donde el método setUp () tuvo que crear objetos con parámetros de constructor no válidos en un constructor que lanzó una excepción.

Si tuviera que hacer que la construcción de 3 objetos no válidos falle, por ejemplo, necesitaría 3 bloques try-catch, anidados. Creé un nuevo método en su lugar, donde se detectaron las excepciones y el valor de retorno fue una nueva instancia de la clase que estaba probando cuando tuvo éxito.

Por supuesto, solo necesitaba 1 método porque hice lo mismo 3 veces. Puede que no sea una solución tan buena para los bloques anidados que hacen cosas totalmente diferentes, pero al menos su código sería más legible en la mayoría de los casos.

    
respondido por el MarioDS 09.04.2012 - 19:13
0

En realidad creo que es un antipattern.

En algunos casos, es posible que desee varios intentos de captura, pero solo si NO SABE qué tipo de error está buscando, por ejemplo:

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

Si no sabes lo que estás buscando, TIENES que usar la primera manera, que es IMHO, fea y no funcional. Supongo que este último es mucho mejor.

Entonces, si sabe qué tipo de error está buscando, sea específico . No es necesario realizar capturas de prueba anidadas o múltiples dentro del mismo método.

    
respondido por el George Silva 09.11.2011 - 17:33
0

Sí, si los usas así. :-)

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");
    }}}}}}}}}}
}
    
respondido por el Daniel Lubarov 09.04.2012 - 19:43
0

En algunos casos, un Try-Catch anidado es inevitable. Por ejemplo, cuando el código de recuperación de error en sí puede lanzar y excepción. Pero para mejorar la legibilidad del código, siempre puede extraer el bloque anidado en un método propio. Consulte esta publicación del blog para ver más ejemplos de los bloques anidados Try-Catch-Finally.

    
respondido por el codelion 15.04.2015 - 02:04
-1

No hay nada mencionado como Anti Pattern en java en cualquier lugar. Sí, llamamos pocas cosas buenas prácticas y malas prácticas.

Si se requiere un bloque try / catch dentro de un bloque catch, es necesario que no puedas ayudarlo. Y no hay alternativa. Como bloque de captura no puede funcionar como parte de prueba si se lanza una excepción.

Por ejemplo:  String str = null; tratar{    str = método (a); } captura (Excepción) { tratar{    str = doMethod (a); } atrapar (excepción ex) {   tirar ex }

Aquí, en el ejemplo anterior, el método arroja una excepción, pero el doMethod (usado para manejar la excepción del método) incluso arroja una excepción. En este caso, tenemos que usar la captura try dentro de la captura try.

algo que se sugiere no hacer es ...

prueba {   ..... 1 } atrapar (excepción ex) { } tratar {   ..... 2 } atrapar (excepción ex) { } tratar {   ..... 3 } atrapar (excepción ex) { } tratar {   ..... 3 } atrapar (excepción ex) { } tratar {   ..... 4 } atrapar (excepción ex) { }

    
respondido por el gauravprasad 06.06.2015 - 01:09

Lea otras preguntas en las etiquetas

Comentarios Recientes

No estrictamente en el sentido de JavaScript pero de todos modos en el sentido de la programación. Esto está un poco más allá del alcance de una publicación, pero creo que sería interesante que otra publicación de blog lo descomponga. Cuando piense en los bloques try-catch en Ruby como una extensión / método de módulo, también tendrá un nodo vinculado a la ubicación del código en lugar del iterador es en tiempo real. Estas son las dos caras de la moneda: a veces Try-Captures: puede hacer IOS sin procesar... Lee mas