tipos - todas las excepciones en java
Java: método estándar de envolver excepciones comprobadas (2)
En general, es mejor evitar envolver las excepciones marcadas con una excepción no verificada (consulte here y aquí ). Algunas formas de evitarlo:
- Use Callable lugar de Runnable
- Utilice el marco de
Executors
como Bohemian sugirió - Subclase
Runnable
(o cualquier interfaz / clase que esté usando) y agregue alguna forma de verificar las excepciones durante la ejecución posterior al hecho, tal vez un métodopublic Exception getRunException()
o similar.
Si debe ajustar una excepción marcada, creo que la mejor manera de hacerlo es como ya lo hizo, defina una subclase de RuntimeException
. Pero trataría de evitarlo si es posible.
Tengo una pregunta bastante detallada sobre la forma correcta de ajustar una excepción marcada y la forma en que Guava lo hace. (Disculpas por la duración, pero quiero bajar mi proceso de pensamiento)
La interfaz estándar de Runnable se ve así:
public interface Runnable
{
public void run();
}
donde run()
no puede lanzar una excepción marcada.
Entonces, si quiero tener un Runnable
que se usa para envolver tareas que arrojan excepciones comprobadas, y tengo la intención de que lo que se llama Runnable.run()
maneje esas excepciones, en lugar de hacerlo en Runnable.run()
, tengo que envolver la excepción en una excepción sin marcar.
Así que por un tiempo estuve usando:
Runnable r = new Runnable {
@Override public void run()
{
try {
doNastyStuff();
}
catch (NastyException e)
{
throw new RuntimeException(e);
}
}
};
y luego puedo manejar RuntimeException en un nivel superior. Excepto entonces, pensé que lo que realmente quiero es manejar una excepción envuelta por separado, ya que sé que su semántica es envolver una excepción marcada, así que escribí esta clase de ayuda:
/**
* Wrapped exception: the purpose of this is just to wrap another exception,
* and indicate that it is a wrapped exception
*/
public class WrappedException extends RuntimeException
{
/**
* @param t any throwable
*/
public WrappedException(Throwable t)
{
super(t);
}
}
y luego puedo hacer esto:
/* place that produces the exception */
...
catch (NastyException e)
{
throw new WrappedException(e);
}
...
/* upper level code that calls Runnable.run() */
try
{
...
SomeOtherNastyCode();
r.run();
...
}
catch (SomeOtherNastyException e)
{
logError(e);
}
catch (WrappedException e)
{
logError(e.getCause());
}
y parece que funciona muy bien.
Pero ahora estoy pensando, bueno, si quiero usar esto en una biblioteca así como en una aplicación que usa la biblioteca, ahora ambos dependen de WrappedException, por lo que debería estar en una biblioteca base que pueda incluir en todas partes.
Lo que me hace pensar, quizás Guava tenga una clase WrappedException estándar en algún lugar, ya que ahora incluyo Guava como una dependencia por defecto. Así que solo puedo hacer
throw new WrappedException(e);
o
throw Exceptions.wrap(e);
o
Exceptions.rethrow(e);
Simplemente miré a mi alrededor en Guava y encontré Throwables que tiene Throwables.propagate()
que parece similar, pero simplemente envuelve las excepciones verificadas en una RuntimeException
, en lugar de una subclase especial de RuntimeException.
¿Qué enfoque es mejor? ¿No debería utilizar una WrappedException especial en comparación con una RuntimeException? Mi código de nivel superior quiere saber la excepción más alta que agrega valor informativo.
Si tengo una RuntimeException que envuelve una NastyException que envuelve una NullPointerException, la envoltura RuntimeException no agrega valor informativo, y no me importa, por lo que el error que registraría sería la NastyException.
Si tengo una IllegalArgumentException que envuelve una NastyException, la IllegalArgumentException generalmente agrega valor informativo.
Así que en mi código superior que hace el registro de errores, tendría que hacer algo como esto:
catch (RuntimeException re)
{
logError(getTheOutermostUsefulException(re));
}
/**
* heuristics to tease out whether an exception
* is wrapped just for the heck of it, or whether
* it has informational value
*/
Throwable getTheOutermostUsefulException(RuntimeException re)
{
// subclasses of RuntimeException should be used as is
if (re.getClass() != RuntimeException)
return re;
// if a runtime exception has a message, it''s probably useful
else if (re.getMessage() != null)
return re;
// if a runtime exception has no cause, it''s certainly
// going to be more useful than null
else if (re.getCause() == null)
return re;
else
return re.getCause();
}
La filosofía se siente bien para mí, pero la implementación se siente mal. ¿Hay una mejor manera de manejar las excepciones envueltas?
preguntas relacionadas:
Spring es la única biblioteca que conozco con algo relativamente similar. Tienen excepciones anidadas: NestedRuntimeException y NestedCheckedException . Estas excepciones tienen métodos útiles, como getMostSpecificCause()
o contains(Class exType)
. Su método getMessage()
devuelve el mensaje de la causa (se adjunta si la excepción de ajuste ya tiene un mensaje).
Se utiliza en la jerarquía de excepciones de acceso a datos de Spring. La idea es que cada proveedor de base de datos exponga diferentes excepciones en sus controladores JDBC. Spring los captura y los traduce en DataAccessExceptions más genéricas. Otro beneficio de esto es que las excepciones comprobadas se convierten automáticamente en excepciones de tiempo de ejecución.
Dicho esto, no es un código muy complicado, y estoy seguro de que podrías hacer algo similar en tu base de código. No hay necesidad de agregar una dependencia Spring solo para eso.
Si yo fuera tú, no intentaría "desenvolver" las excepciones demasiado pronto, a menos que realmente puedas manejarlas en ese mismo momento. Los dejaría explotar (envueltos o no), con un ExceptionHandler global que analizaría su cadena causal, encontraría la primera excepción "significativa" y extraería un mensaje de error inteligente. Cuando no pueda hacerlo, simplemente imprimiría "error técnico" y agregaría todo el seguimiento de la pila en el detalle del mensaje de error o en algún tipo de registro, con el propósito de informes de errores. Luego corregirías el error y / o lanzarías una excepción más significativa en su lugar.
Throwables.getCausalChain() Guava también podría ser interesante para simplificar el manejo de excepciones:
Iterables.filter(Throwables.getCausalChain(e), IOException.class));
Editar:
Pensé un poco más sobre su problema, y creo que realmente no debería preocuparse por envolver sus excepciones con un tipo específico de "WrapperException". Debería envolverlos usando lo que tenga más sentido: ya sea una simple RuntimeException
(Guava''s Throwables.propagate()
podría ser útil), una RuntimeException
con un mensaje de error adicional, o un tipo de excepción más significativo cuando sea apropiado.
El mecanismo de cadena causal de Java te permitirá llegar a la causa raíz de todos modos. No debería tener que preocuparse por el ajuste de excepciones en todo el código. Escriba un controlador de excepciones global para administrarlo, y utilice el burbujeo de excepciones estándar en cualquier otro lugar.