2010-08-31 8 views
7

He visto este tema surgir a veces en el pasado, pero incluso después de buscar en Google about it, todavía no puedo entender cuál sería una buena y elegante forma de manejarlo, así que aquí va.Múltiples capturas específicas o una captura todas?

Decir que tengo un código que lanza varias excepciones ...

try { 
    /* some code that throws these exceptions */ 
} catch (NoSuchAuthorityCodeException e) { 
    throw new MyAPIException("Something went wrong", e); 
} catch (FactoryException e) { 
    throw new MyAPIException("Something went wrong", e); 
} catch (MismatchedDimensionException e) { 
    throw new MyAPIException("Something went wrong", e); 
} catch (TransformException e) { 
    throw new MyAPIException("Something went wrong", e); 
} 

... y como podemos ver, acabo de envolver estas excepciones y lanzar uno nuevo decir que algo ha ido mal dentro de mi API .

Esto me parece un código excesivamente repetitivo, por lo que uno simplemente cogerá un tipo de excepción y manejará wrap y lanzará uno nuevo.

try { 
    /* some code that throws these exceptions */ 
} catch (Exception e) { 
    throw new MyAPIException("Something went wrong", e); 
} 

En este caso, sigue siendo mucho más simple, pero la cuestión es que también captaríamos todas las RuntimeException. Teniendo esto en cuenta, podríamos atrapar y volver a lanzar la RuntimeException para poder evitar esto.

try { 
    /* some code that throws some exceptions */ 
} catch (RuntimeException e) { 
    throw e; 
} catch (Exception e) { 
    throw new MyAPIException("Something went wrong", e); 
} 

Es un poco abultado pero funciona. Ahora hay otro pequeño problema sobre la captura (Excepción e), que si mi API interna arroja otra MyAPIException también sería atrapada, envuelta y lanzada dentro de otra MyAPIException. En este caso particular, también podríamos capturar MyAPIException y volver a lanzarlo.

try { 
    /* some code that throws some exceptions */ 
} catch (RuntimeException e) { 
    throw e; 
} catch (MyAPIException e) { 
    throw e; 
} catch (Exception e) { 
    throw new MyAPIException("Something went wrong", e); 
} 

Bueno, es ensuciarse MyAPIException de nuevo, pero en este caso evitar el ajuste y simplemente volver a lanzarla. Pero, también hay otro problema con el bloque catch (Exception e) que es si la API interna cambia y comienza a lanzar otro tipo de excepción (alguna que no sea la mencionada anteriormente 4), el compilador no diría nada al respecto y nosotros no lo haríamos. Tienes una pista. No es que esto sea un problema importante, ya que probablemente lo trataría de la misma manera.

Con este escenario, creo que la pregunta es, ¿cuál es mejor y hay mejores opciones?

Respuesta

2

Tiene más de un MyApiException. Si tiene todas las excepciones como MyApiException, se vuelve un poco difícil para usted y para otros leer su código. Nombrarlo correctamente también sería crucial. Además, no siempre quieres atraparlo y volver a lanzarlo. Si ese es el caso, simplemente declare throws en la firma del método.

Además, no se puede fijar ni en una captura simple ni en varias capturas. Es más una decisión juiciosa que la OMI, ya que hay excepciones que son fatales (como en todo el programa tiene que detenerse) a tipos más fáciles de manejar.

No veo nada malo cuando tienes un montón de throws cláusulas en tu API. Es una implementación mucho más ordenada. Entonces, ¿qué pasa si la persona que llama tiene que manejar las excepciones que ha definido? Si un método en su API puede arrojar una excepción y hay que hacer algo al respecto, la persona que llama debería tener que manejarlo. De todos modos, su documentación de esa excepción particular tiene que ser clara para no confundir a la persona que llama.

Un factor más que decide esto es la complejidad de la API. Hace algún tiempo escribí una API que era moderadamente compleja y tuve que lidiar con los mismos problemas que presentó. Había escrito algunas excepciones personalizadas para algunos métodos. Estoy seguro de que no terminarás lanzando excepciones para todo el método público que expone la API, pero hay lugares que son inevitables.

Por fin, si cree que tener demasiadas excepciones personalizadas es una opción amarga, puede tener una única excepción con códigos de error claramente documentados. De esta forma, la persona que llama puede manejar la excepción y tratar los códigos de error como lo considere oportuno.

+0

La cosa es que si simplemente empezar a tirar todas las demás excepciones, otra persona tendría que hacer frente a todas esas excepciones y yo no creo que es muy bueno tener un método que lanza tipos montón de excepciones. Además, si tuviera que tener un tipo de excepción para cada excepción envuelta, sería simplemente una tontería ... ¿por qué no simplemente tirarla? (y ahora tenemos una recursión) –

+0

@Pablo Cabrera: ver mi respuesta editada – bragboy

+0

Bueno, en este caso, solo quiero mostrarle al usuario de esta API que algo salió mal y que no pudo completarse para que pueda lidiar con esto en un simple bloque catch (catch (MyAPIException e)) en lugar de atrapar múltiples tipos de excepciones o tener que lidiar con este mismo problema de captura presentado aquí. En caso de que el usuario quisiera saber la razón por la cual falló, (s) él todavía puede ver la causa dentro de la excepción. Creo que algo como esto sería una API mucho más ordenada, pero como dijiste, podría ser una decisión crítica. ¿Alguna idea? –

2

en Java 7 va a ser capaz de hacer algo como

try { 
    /* some code that throws these exceptions */ 
} catch (NoSuchAuthorityCodeException , FactoryException, MismatchedDimensionException , TransformException e) { 
    throw new MyAPIException("Something went wrong", e); 
} 

Así que tal vez la mejor respuesta es para relajarse por un tiempo y luego actualizar a Java 7 cuando esté disponible.

+0

Sí, me enteré, pero la cosa es que en la empresa en la que trabajo todavía utilizamos Java5 (!!!), y me gustaría hacer algo elegante en Java5 o Java6 por el asunto –

5

Dado que está atascado con Java5 y utiliza una excepción de propiedad, ¿por qué no poner toda esa lógica de excepción en la clase de excepción.

Uso del

try 
{ 
    // some code that might throw one of several exceptions 
} 
catch (Exception cause) 
{ 
    MyAPIException . handle (cause) ; 
} 

MyAPIException contiene la lógica

class MyAPIException extends Exception 
{ 
    private MyAPIException (String message , Throwable cause) { super (message , cause) ; } 

    private static void myAPIException (Exception cause) throws MyAPIException 
    { 
     throw new MyAPIException ("Something Went Wrong" , cause) ; 
    } 

    public static void handle (Exception e) throws MyAPIException 
    { 
      try 
      { 
       throw (e) ; 
      } 
      catch (RuntimeException cause) 
      { 
       throw cause ; 
      } 
      catch (MyAPIException cause) 
      { 
       throw cause ; 
      } 
      catch (NoSuchAuthorityCodeException cause) // repeat for other exceptions 
      { 
       myAPIException (cause) ; 
      } 
      catch (Exception cause) // this should not happen 
      { 
        assert false ; // or log it or throw a RuntimeException ... somehow signal that programming logic has failed 
      } 
    } 
} 
+0

No quiero atrapar realmente RuntimeException y MyAPIException –

+0

sería bueno si pusiera un ejemplo de uso. –

+0

@Pablo Cabrera Agregué un ejemplo de uso. Lanzará RuntimeException y MyAPIException. Puse todo en un método estático en la clase MyAPIException. Puede llamarlo desde su código con un simple try/catch. – emory

0

Todo depende de lo que usted quiere hacer. Si todo lo que quiere hacer es throw new MyException("Something went wrong", e);, entonces un catch all hará.

+2

Como dije antes, no quiero capturar RuntimeException o MyAPIException ... RTFQ –

1

OK chicos, esto es lo que se me ocurrió ... No es tan elegante, pero creo que es un poco mejor que tener varias capturas.

Primero, tenemos una clase abstracta llamada ExceptionHandler.

package my.api.pack; 

import java.lang.reflect.ParameterizedType; 
import java.util.LinkedHashSet; 
import java.util.Set; 

public abstract class ExceptionHandler<E extends Exception> { 

    Set<Class<? extends Exception>> exceptionClasses = new LinkedHashSet<Class<? extends Exception>>(); 

    protected abstract void handler(Throwable t) throws E; 

    public ExceptionHandler<E> catches(Class<? extends Exception> clazz) { 
     exceptionClasses.add(clazz); 
     return this; 
    } 

    @SuppressWarnings("unchecked") 
    private Class<E> getGenericsClass() { 
     ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); 
     return (Class<E>) parameterizedType.getActualTypeArguments()[0]; 
    } 

    @SuppressWarnings("unchecked") 
    public final void handle(Throwable t) throws E, UnhandledException { 
     if (getGenericsClass().isInstance(t)) { 
      throw (E) t; 
     } 

     for (Class<? extends Exception> clazz : exceptionClasses) { 
      if (clazz.isInstance(t)) { 
       handler(t); 
       return; 
      } 
     } 

     throw new UnhandledException("Unhandled exception", t); 
    } 

} 

Junto con ello, tenemos este sencillo excepción de ejecución denominado UnhandledException

package my.api.pack; 

public class UnhandledException extends RuntimeException { 

    private static final long serialVersionUID = -3187734714363068446L; 

    public UnhandledException(String message, Throwable cause) { 
     super(message, cause); 
    } 


} 

Con ella podemos utilizar éstos para manejar excepciones como esta ...

try { 
    /* some code that throws these exceptions */ 
} catch (Exception e) { 
    new ExceptionHandler<MyAPIException>() { 
     @Override 
     protected void handler(Throwable t) throws MyAPIException { 
      throw new MyAPIException("Something went wrong", t); 
     } 
    }. 
     catches(MismatchedDimensionException.class). 
     catches(NoSuchAuthorityCodeException.class). 
     catches(FactoryException.class). 
     catches(TransformException.class). 
     handle(e); 
    return null; 
} 

Lo ustedes piensan?

+0

La idea es muy buena . (1) Use isAssignableFrom en lugar de isInstanceOf. (2) Realmente no entiendo el método getGenericsClass. Creo que sería más limpio para la clase ExceptionHandler solo tener una instancia de la clase . (3) Si usa un marco de trabajo de registro, tal vez pueda agregar un comando btwn de instrucciones de registro (t) y regresar; esto lo ayudará en caso de que el manejador (t) come erróneamente una excepción importante. (4) Si haces que la clase ExceptionHandler sea inmutable, creo que la programación y la eliminación de fallas serán más fáciles, sin necesidad de adivinar qué excepciones maneja en algún momento. – emory

+0

(5) Reemplazar UnhandledException con "assert false:" Excepción no controlada; "No me gusta la excepción UnhandledException. Si se ejemplifica correctamente el ExceptionHandler, no debe haber excepciones no manejadas. Por lo tanto, la presencia de UnhandledException indica un error de programación. – emory

+0

Ahora, yo Creo que entiendo mejor lo que quieres. Así que tengo otra respuesta a continuación. – emory

0

A continuación se muestra otro enfoque y un ejemplo de uso. La clase MyAPIException tiene un método de control. El método handle manejará cualquier excepción.

  1. Si la excepción es Runnable o MyAPIException, el controlador lo lanzará sin ajustarlo.
  2. De lo contrario, el método de control probará si las aserciones están habilitadas. Si las aserciones están habilitadas, el método de manejo probará para ver si la excepción se puede asignar desde uno de los tipos de excepción admitidos, arrojando un AssertionError si no es asignable. (Si las aserciones no están habilitadas, el método de manejo ignora el parámetro de tipos de excepciones admitidos).
  3. Finalmente, si llega a este punto, el método de asa envolverá la excepción en MyAPIException y la arrojará.

Cuando pruebe su código, ejecútelo con aserciones habilitadas. En producción, ejecútelo con aserciones desactivadas.

<soapbox> Si utiliza esta técnica, deberá probar los errores que el compilador hubiera detectado de otra forma.</caja de jabón >

class MyAPIException extends Exception 
{ 
    private static final long serialVersionUID = 0 ; 

    MyAPIException (Throwable cause) 
    { 
     super (cause) ; 
    } 

    static void handle (Exception cause , Class <?> ... supportedExceptionTypes) throws MyAPIException 
    { 
     try 
     { 
     throw (cause) ; 
     } 
     catch (RuntimeException e) 
     { 
     throw (e) ; 
     } 
     catch (MyAPIException e) 
     { 
     throw (e) ; 
     } 
     catch (Exception e) 
     { 
     search : 
     try 
      { 
      assert false ; 
      } 
     catch (AssertionError a) 
      { 
      for (Class <?> c : supportedExceptionTypes) 
       { 
       if (c . isAssignableFrom (e . getClass ())) 
        { 
        break search ; 
        } 
       } 
      assert false : e + " is not one of the supportedExceptionTypes : " + supportedExceptionTypes ; 
      } 
     MyAPIException error = new MyAPIException (e) ; 
     throw (error) ; 
     } 
    } 
} 

Este es un ejemplo de uso. Puede ejecutarlo con aserciones habilitadas/deshabilitadas y con los parámetros 1, 2, 3, 4 para ver cómo maneja diferentes situaciones.

class Usage 
{ 
    public static void main (String [ ] args) throws MyAPIException 
    { 
    try 
     { 
     switch (Integer . parseInt (args [ 0 ])) 
      { 
      case 1 : 
      throw new RuntimeException () ; 
      case 2 : 
      throw new SQLException () ; 
      case 3 : 
      throw new MyAPIException (null) ; 
      case 4 : 
      throw new IOException () ; 
      } 
     } 
    catch (Exception cause) 
     { 
     System . out . println (cause . getMessage ()) ; 
     System . out . println (cause . getCause ()) ; 
     MyAPIException . handle (cause , IOException . class) ; 
     } 
    } 
} 
Cuestiones relacionadas