todas tipos propagacion programacion para numeros negativos manejo las lanzar excepciones ejemplos clase arbol java exception logging error-handling

tipos - ¿Cuál es una buena forma de pasar información de estado útil a una excepción en Java?



tipos de excepciones en programacion (11)

Al principio noté cierta confusión con mi pregunta. No estoy preguntando cómo configurar un registrador ni cómo usar un registrador correctamente, sino más bien cómo capturar toda la información que se habría registrado en un nivel de registro inferior al nivel de registro actual en el mensaje de excepción.

He estado notando dos patrones en Java para registrar información que puede ser útil para un desarrollador cuando ocurre una excepción.

El siguiente patrón parece muy común. Básicamente, solo tiene la información de registro de registrador en línea según sea necesario, de modo que cuando ocurre una excepción, tiene el rastreo de registro.

try { String myValue = someObject.getValue(); logger.debug("Value: {}", myValue); doSomething(myValue); } catch (BadThingsHappenException bthe) { // consider this a RuntimeException wrapper class throw new UnhandledException(bthe); }

El inconveniente del enfoque anterior es que si los usuarios requieren registros relativamente silenciosos y necesitan un alto nivel de confiabilidad hasta el punto en que simplemente no pueden "intentarlo de nuevo en modo de depuración", el mensaje de excepción contiene datos insuficientes por sí mismo para ser útil para el desarrollador.

El siguiente patrón es uno que he visto que trata de mitigar este problema, pero parece feo:

String myValue = null; try { myValue = someObject.getValue(); doSomething(myValue); } catch (BadThingsHappenException bthe) { String pattern = "An error occurred when setting value. [value={}]"; // note that the format method below doesn''t barf on nulls String detail = MessageFormatter.format(pattern, myValue); // consider this a RuntimeException wrapper class throw new UnhandledException(detail, bthe); }

El patrón anterior parece resolver de alguna manera el problema, sin embargo, no estoy seguro de que quiera declarar tantas variables fuera del alcance del bloque try. Especialmente, cuando tengo que lidiar con estados muy complicados.

El único otro enfoque que he visto es el uso de un mapa para almacenar los pares de valores clave que luego se vuelcan en el mensaje de excepción. No estoy seguro de que me guste ese enfoque tampoco, ya que parece crear código hinchado.

¿Hay algo de Java vodoo por ahí que me falta? ¿Cómo maneja su información de estado de excepción?


¿Por qué no mantener una copia / lista local de todos los mensajes que hubieran ido al registro de depuración si estuviera habilitado, y pasarlo a la excepción personalizada cuando lo arroje? Algo como:

static void logDebug(String message, List<String> msgs) { msgs.add(message); log.debug(message); } //... try { List<String> debugMsgs = new ArrayList<String>(); String myValue = someObject.getValue(); logDebug("Value: " + myValue, debugMsgs); doSomething(myValue); int x = doSomething2(); logDebug("doSomething2() returned " + x, debugMsgs); } catch (BadThingsHappenException bthe) { // at the point when the exception is caught, // debugMsgs contains some or all of the messages // which should have gone to the debug log throw new UnhandledException(bthe, debugMsgs); }

Su clase de excepción puede hacer uso de este parámetro List al formar getMessage() :

public class UnhandledException extends Exception { private List<String> debugMessages; public UnhandledException(String message, List<String> debugMessages) { super(message); this.debugMessages = debugMessages; } @Override public String getMessage() { //return concatentation of super.getMessage() and debugMessages } }

El uso de esto sería tedioso, ya que tendrías que declarar la variable local en cada intento / captura donde quisieras este tipo de información, pero podría valer la pena si solo tienes unas pocas secciones críticas de código en las que le gustaría mantener esta información de estado en una excepción.


Además de su ejemplo que declara campos locales fuera del bloque try para poder acceder dentro del bloque catch , una manera muy simple de manejar esto es volcar el estado de la clase en la Exception usando el método toString anulado de la clase. Por supuesto, esto solo es útil en las Class que mantienen el estado.

try { setMyValue(someObject.getValue()); doSomething(getMyValue()); } catch (BadThingsHappenException bthe) { // consider this a RuntimeException wrapper class throw new UnhandledException(toString(), bthe); }

Su toString() necesitaría ser anulado:

public String toString() { return super.toString() + "[myValue: " + getMyValue() +"]"; }

editar:

otra idea:

Puede mantener el estado en un contexto de depuración de ThreadLocal . Supongamos que crea una clase llamada MyDebugUtils que contiene un ThreadLocal que contiene un mapa por subproceso. Permite el acceso estático a este ThreadLocal y a los métodos de mantenimiento (es decir, borrar el contexto cuando finaliza la depuración).

La interfaz podría ser:

public static void setValue(Object key, Object value) public static void clearContext() public static String getContextString()

y en nuestro ejemplo:

try { MyDebugUtils.setValue("someObeject.value", someObject.getValue()); doSomething(someObject.getValue()); } catch (BadThingsHappenException bthe) { // consider this a RuntimeException wrapper class throw new UnhandledException(MyDebugUtils.getContextString(), bthe); } finally { MyDebugUtils.clearContext(); }

Puede haber algunos problemas que desee solucionar, como el manejo de casos en los que su método doSomething también contiene un conjunto try/catch/finally que borra el contexto de depuración. Esto podría ser manejado al permitir una granularidad más fina en el Mapa de contexto que solo el Subproceso en el proceso:

public static void setValue(Object contextID, Object key, Object value) public static void clearContext(Object contextID) public static String getContextString(Object contextID)


Creé una combinación de teclas en eclipse para crear un bloque catch.

logmsg como clave y el resultado será

catch(SomeException se){ String msg = ""; //$NON-NLS-1$ Object[] args = new Object[]{}; throw new SomeException(Message.format(msg, args), se); }

Puede poner tanta información como desee en el Mensaje como:

msg = "Dump:/n varA({0}), varB({1}), varC({2}), varD({3})"; args = new Object[]{varA, varB, varC, varD};

O algunas informaciones de usuario

msg = "Please correct the SMTP client because ({0}) seems to be wrong"; args = new Object[]{ smptClient };

Debería pensar en usar log4j como registrador, para que pueda imprimir sus estados donde quiera. Con las opciones DEBUG, INFO, ERROR puede definir cuántos registros desea ver en su archivo de registro.

Cuando entregue su aplicación, establecerá el nivel de registro en ERROR, pero cuando quiera debutar su aplicación, puede usar DEBUG como predeterminado.

Cuando está utilizando un registrador, solo tiene que imprimir una mano llena de información en su mensaje, debido al estado de algunas variables que imprimiría en el archivo de registro, antes de llamar al crítico try ... catch block.

String msg = "Dump:/n varA({0}), varB({1}), varC({2}), varD({3})"; Object[] args = new Object[]{varA, varB, varC, varD}; logger.debug(Message.format(msg, args)); try{ // do action }catch(ActionException ae){ msg = "Please correct the SMTP client because ({0}) seems to be wrong"; args = new Object[]{ smptClient }; logger.error(Message.format(msg, args), se); throw new SomeException(Message.format(msg, args), se); }


Eche un vistazo a la clase MemoryHandler de java.util.logging. Actúa como un búfer entre sus invocaciones log $ $ () y la salida real, y pasará su contenido de búfer a la salida solo si se cumple alguna condición.

Por ejemplo, puede configurarlo para volcar contenido solo si ve un mensaje de nivel de ERROR. A continuación, puede enviar mensajes de nivel DEBUG de forma segura y nadie los verá a menos que ocurra un error real y luego todos los mensajes se escriban en el archivo de registro.

Supongo que hay implementaciones similares para otros marcos de registro.

EDITAR: Un posible problema con este enfoque es la pérdida de rendimiento al generar todos los mensajes de depuración (ver @djna comment). Debido a esto, puede ser una buena idea hacer que el nivel de registro vaya al búfer configurable; en producción, debe ser INFO o superior, y solo si está buscando activamente un problema, se lo puede bajar a DEPURAR.


En cuanto al tipo de información de depuración que necesita, ¿por qué no siempre registra el valor y no se molesta tanto con un try / catch local? Simplemente use el archivo de configuración de Log4J para apuntar sus mensajes de depuración a un registro diferente, o use una motosierra para que pueda seguir de forma remota los mensajes de registro. Si todo eso falla, tal vez necesite un nuevo tipo de mensaje de registro para agregar a debug () / info () / warn () / error () / fatal () para que tenga más control sobre qué mensajes se envían a dónde. Este sería el caso cuando definir appenders en el archivo de configuración log4j no es práctico debido al alto número de lugares donde este tipo de registro de depuración necesita ser insertado.

Mientras hablamos sobre el tema, has tocado uno de mis temas favoritos. La construcción de una nueva excepción en el bloque catch es un olor a código.

Catch(MyDBException eDB) { throw new UnhandledException("Something bad happened!", eDB); }

Coloque el mensaje en el registro y luego vuelva a lanzar la excepción. Construir excepciones es costoso y puede ocultar fácilmente información de depuración útil.

En primer lugar, los codificadores inexpertos y aquellos a los que les gusta cortar y pegar (o begin-mark-bug, end-mark-bug, copy-bug, copy-bug, copy-bug) se pueden transformar fácilmente en esto:

Catch(MyDBException eDB) { throw new UnhandledException("Something bad happened!"); }

Ahora has perdido la stacktrace original. Incluso en el primer caso, a menos que la excepción de ajuste maneje correctamente la excepción envuelta, aún puede perder detalles de la excepción original, siendo la stacktrace la más probable.

Es posible que sea necesario volver a tirar las excepciones, pero descubrí que debería tratarse de manera más general y como una estrategia para comunicarse entre capas, por ejemplo, entre el código de su empresa y la capa de persistencia:

Catch(SqlException eDB) { throw new UnhandledAppException("Something bad happened!", eDB); }

y en este caso, el bloque catch para UnhandledAppException está mucho más arriba en la pila de llamadas, donde podemos indicarle al usuario que debe volver a intentar su acción, informar un error o lo que sea.

Esto permite que nuestro código principal () haga algo como esto

catch(UnhandledAppException uae) { //notify user //log exception } catch(Throwable tExcp) { //for all other unknown failures //log exception } finally { //die gracefully }

Hacerlo de esta manera significaba que el código local podía detectar las excepciones inmediatas y recuperables en las que se podían realizar los registros de depuración y no era necesario volver a lanzar la excepción. Esto sería como para DivideByZero o tal vez una ParseException de algún tipo.

En cuanto a las cláusulas "throws", tener una estrategia de excepción basada en capas significaba poder limitar la cantidad de tipos de excepciones que se deben enumerar para cada método.


Ha respondido a su propia pregunta. Si desea pasar el estado a la excepción, debe almacenar su estado en algún lugar.

Ha mencionado agregar variables adicionales para hacer esto, pero no le han gustado todas las variables adicionales. Alguien más mencionó un MemoryHandler como un buffer (mantiene el estado) entre el registrador y la aplicación.

Estas son todas la misma idea. Cree un objeto que contendrá el estado que desea que muestre en su excepción. Actualice ese objeto a medida que se ejecuta su código. Si ocurre un error, pase ese objeto a la excepción.

Las excepciones ya hacen esto con StackTraceElements. Cada hilo mantiene una lista del seguimiento de la pila (método, archivo, línea) que representa su ''estado''. Cuando ocurre la excepción, pasa el seguimiento de la pila a la excepción.

Lo que pareces querer, es una copia de todas las variables locales también.

Esto significaría crear un objeto para contener a todos tus locales y usar ese objeto, en lugar de los locales directamente. Luego pasando el objeto a la excepción.


Otra buena API de registro es SLF4J. Se puede configurar para que también intercepte las API de registro para Log4J, Java Util Logging y Jakarta Commons Logging. Y también se puede configurar para usar varias implementaciones de registro, incluidos Log4J, Logback, Java Util Logging y uno o dos más. Esto le da una enorme flexibilidad. Fue desarrollado por el autor de Log4J para ser su sucesor.

De relevancia para esta pregunta, la API SLF4J tiene un mecanismo para concatenar expresiones de cadena de valor en un mensaje de registro. Las siguientes llamadas son equivalentes, pero la segunda es aproximadamente 30 veces más rápida de procesar si no está enviando mensajes de nivel de depuración, ya que se evita la concatenación:

logger.debug("The new entry is " + entry + "."); logger.debug("The new entry is {}.", entry);

También hay una versión de dos argumentos:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

Y para más de dos, puede pasar una matriz de objetos como esta:

logger.debug("Value {} was inserted between {} and {}.", new Object[] {newVal, below, above});

Este es un bonito y escueto formato que elimina el desorden.

La fuente de ejemplo es de las preguntas frecuentes de SLF4J .

Editar: Aquí hay una posible refactorización de tu ejemplo:

try { doSomething(someObject.getValue()); } catch (BadThingsHappenException bthe) { throw new UnhandledException( MessageFormatter.format("An error occurred when setting value. [value={}]", someObject.getValue()), bthe); }

O si este patrón se produce en más de unos pocos lugares, podría escribir un conjunto de métodos estáticos que capturen los elementos comunes, algo así como:

try { doSomething(someObject.getValue()); } catch (BadThingsHappenException bthe) { throwFormattedException(logger, bthe, "An error occurred when setting value. [value={}]", someObject.getValue())); }

y, por supuesto, el método también pondría el mensaje formateado en el registrador por usted.


Quizás me falta algo, pero si los usuarios realmente requieren un archivo de registro relativamente silencioso, ¿por qué no configuran los registros de depuración para ir a un lugar diferente?

Si eso es insuficiente, capture una cantidad fija de los registros de depuración en la RAM. Por ejemplo, las últimas 500 entradas. Luego, cuando algo feo sucede, volcar los registros de depuración junto con el informe del problema. No menciona su marco de trabajo de registro, pero esto sería bastante fácil de hacer en Log4J.

Aún mejor, suponiendo que tenga el permiso del usuario, simplemente envíe un informe de error automático en lugar de iniciar sesión. Recientemente ayudé a algunas personas a encontrar un error difícil de encontrar e hizo que el informe de error fuera automático. Tenemos 50 veces la cantidad de informes de errores, lo que hace que el problema sea bastante fácil de encontrar.


Si desea procesar de algún modo los detalles del mensaje de error, puede:

  • Use un texto XML como mensaje, para que obtenga una forma estructurada:

    throw new UnhandledException(String.format( "<e><m>Unexpected things</m><value>%s</value></e>", value), bthe);

  • Utilice sus propios tipos de excepción (y uno para cada caso) para capturar información variable en propiedades con nombre:

    throw new UnhandledValueException("Unexpected value things", value, bthe);

O bien, podría incluirlo en el mensaje en bruto, como lo sugirieron otros.


Tendemos a crear nuestras clases de excepciones de tiempo de ejecución más importantes para aplicaciones específicas con algunos constructores especiales, algunas constantes y un ResourceBundle.

Fragmento de ejemplo:

public class MyException extends RuntimeException { private static final long serialVersionUID = 5224152764776895846L; private static final ResourceBundle MESSAGES; static { MESSAGES = ResourceBundle.getBundle("....MyExceptionMessages"); } public static final String NO_CODE = "unknown"; public static final String PROBLEMCODEONE = "problemCodeOne"; public static final String PROBLEMCODETWO = "problemCodeTwo"; // ... some more self-descriptive problem code constants private String errorCode = NO_CODE; private Object[] parameters = null; // Define some constructors public MyException(String errorCode) { super(); this.errorCode = errorCode; } public MyException(String errorCode, Object[] parameters) { this.errorCode = errorCode; this.parameters = parameters; } public MyException(String errorCode, Throwable cause) { super(cause); this.errorCode = errorCode; } public MyException(String errorCode, Object[] parameters, Throwable cause) { super(cause); this.errorCode = errorCode; this.parameters = parameters; } @Override public String getLocalizedMessage() { if (NO_CODE.equals(errorCode)) { return super.getLocalizedMessage(); } String msg = MESSAGES.getString(errorCode); if(parameters == null) { return msg; } return MessageFormat.format(msg, parameters); } }

En el archivo de propiedades especificamos las descripciones de las excepciones, por ejemplo:

problemCodeOne=Simple exception message problemCodeTwo=Parameterized exception message for {0} value

Usando este enfoque

  • Podemos usar cláusulas throw bastante legibles y comprensibles ( throw new MyException(MyException.PROBLEMCODETWO, new Object[] {parameter}, bthe) )
  • Los mensajes de excepción son "centralizados", pueden mantenerse fácilmente e "internacionalizarse"

EDITAR: cambie getMessage por getLocalizedMessage como Elijah sugirió.

EDIT2: se olvidó de mencionar: este enfoque no es compatible con el cambio de configuración regional "sobre la marcha", pero es intencional (se puede implementar si lo necesita).


Una opción que nadie parece haber mencionado aún es usar un registrador que se registra en un búfer de memoria, y solo empuja la información en el objetivo de registro real bajo ciertas circunstancias (por ejemplo, se registra un mensaje de nivel de error).

Si está utilizando las funciones de registro de JDK 1.4, MemoryHandler hace exactamente esto. No estoy seguro de si el sistema de registro que está utilizando lo hace, pero imagino que debería poder implementar su propio appender / handler / lo que sea que haga algo similar.

Además, solo quiero señalar que en su ejemplo original, si su preocupación es el alcance variable, siempre puede definir un bloque para reducir el alcance de su variable:

{ String myValue = null; try { myValue = someObject.getValue(); doSomething(myValue); } catch (BadThingsHappenException bthe) { String pattern = "An error occurred when setting value. [value={}]"; // note that the format method below doesn''t barf on nulls String detail = MessageFormatter.format(pattern, myValue); // consider this a RuntimeException wrapper class throw new UnhandledException(detail, bthe); } }