Cómo escribir un buen mensaje de excepción

96

Actualmente estoy haciendo una revisión de código y una de las cosas que estoy notando es el número de excepciones donde el mensaje de excepción parece reiterar donde se produjo la excepción. por ejemplo

throw new Exception("BulletListControl: CreateChildControls failed.");

Los tres elementos de este mensaje los puedo resolver del resto de la excepción. Conozco la clase y el método del seguimiento de pila y sé que falló (porque tengo una excepción).

Me hizo pensar en qué mensaje puse en los mensajes de excepción. Primero creo una clase de excepción, si no existe una, por la razón general (por ejemplo, PropertyNotFoundException - el por qué ), y luego, cuando lo lanzo, el mensaje indica qué fue lo que falló (por ejemplo, " No se puede encontrar la propiedad 'IDontExist' en el Nodo 1234 "- qué ). El donde está en el StackTrace . El cuando puede terminar en el registro (si corresponde). El cómo es para que el desarrollador trabaje (y arregle)

¿Tiene algún otro consejo para lanzar excepciones? Específicamente con respecto a la creación de nuevos tipos y el mensaje de excepción.

    
pregunta Colin Mackay 23.12.2010 - 12:43

7 respuestas

67

Dirigiré mi respuesta más a lo que viene después de una excepción: ¿para qué sirve y cómo debe comportarse el software? ¿Qué deben hacer los usuarios con la excepción? Una excelente técnica que encontré al principio de mi carrera fue reportar problemas y errores en 3 partes: contexto, problema y amplificador; solución. El uso de esta disciplina modifica enormemente el manejo de errores y hace que el software sea mucho mejor para los operadores.

Aquí hay algunos ejemplos.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

En este caso, el operador sabe exactamente qué hacer y a qué archivo debe verse afectado. También saben que los cambios en la agrupación de conexiones no se realizaron y deberían repetirse.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Escribo sistemas del lado del servidor y mis operadores son generalmente expertos en soporte de primera línea. Escribiría los mensajes de manera diferente para el software de escritorio que tiene una audiencia diferente pero incluye la misma información.

Varias cosas maravillosas suceden si uno usa esta técnica. El desarrollador de software suele estar en la mejor posición para saber cómo resolver los problemas en su propio código, por lo que las soluciones de codificación de esta manera a medida que se escribe el código son de gran beneficio para los usuarios finales que se encuentran en desventaja al encontrar soluciones, ya que a menudo les falta información sobre Qué es exactamente lo que el software estaba haciendo. Cualquiera que haya leído un mensaje de error de Oracle sabrá lo que quiero decir.

La segunda cosa maravillosa que viene a la mente es cuando te encuentras tratando de describir una solución en tu excepción y estás escribiendo "Verificar X y si A luego B más C”. Esta es una señal muy clara y obvia de que su excepción se está revisando en el lugar equivocado. Usted el programador tiene la capacidad de comparar cosas en código, por lo que las declaraciones "if" deben ejecutarse en código, ¿por qué involucrar al usuario en algo que pueda automatizarse? Lo más probable es que esté en el código más profundo y alguien haya hecho la pereza y haya lanzado la excepción IOException desde cualquier número de métodos y haya detectado errores potenciales de todos ellos en un bloque de código de llamada que no puede describir adecuadamente lo que salió mal, lo que El contexto específico es y cómo solucionarlo. Esto lo alienta a escribir errores de grano más fino, capturarlos y manejarlos en el lugar correcto en su código para que pueda articular correctamente los pasos que debe tomar el operador.

En una empresa teníamos operadores de primer nivel que conocían muy bien el software y mantenían su propio "libro de ejecución" que aumentaba nuestros informes de errores y las soluciones sugeridas. Para reconocer esto, el software comenzó a incluir los enlaces wiki al libro de ejecución en excepciones para que hubiera una explicación básica disponible, así como enlaces a discusiones más avanzadas y observaciones de los operadores a lo largo del tiempo.

Si has tenido la disciplina para probar esta técnica, se vuelve mucho más obvio cómo deberías nombrar tus excepciones en el código cuando crees el tuyo. NonRecoverableConfigurationReadFailedException se convierte en un poco de taquigrafía para lo que está a punto de describir más completamente al operador. Me gusta ser verboso y creo que será más fácil de interpretar para el siguiente desarrollador que toque mi código.

    
respondido por el Sir Wobin 23.12.2010 - 13:14
22

En esta pregunta más reciente hice hincapié en que las excepciones no deberían contener ningún mensaje. En mi opinión, el hecho de que lo hagan es un gran error. Lo que estoy proponiendo es que

  

El "mensaje" de la excepción es el nombre de clase (completamente calificado) de la excepción.

Una excepción debe contener dentro de sus propias variables miembro tantos detalles como sea posible acerca de lo que sucedió; por ejemplo, un IndexOutOfRangeException debe contener el valor de índice que se encontró que no es válido, así como los valores superior e inferior que eran válidos en el momento en que se lanzó la excepción. De esta manera, utilizando la reflexión, puede hacer que se construya automáticamente un mensaje que lea así: IndexOutOfRangeException: index = -1; min=0; max=5 y esto, junto con el seguimiento de la pila, debe ser toda la información objetiva que necesita para solucionar el problema. Formatearlo en un mensaje bonito como "el índice -1 no estaba entre 0 y 5" no agrega ningún valor.

En su ejemplo particular, la clase NodePropertyNotFoundException contendría el nombre de la propiedad que no se encontró y una referencia al nodo que no contenía la propiedad. Esto es importante: debe no contener el nombre del nodo; debe contener una referencia al nodo real. En su caso particular, esto puede no ser necesario, pero es una cuestión de principios y una forma de pensar preferida: la principal preocupación al construir una excepción es que debe ser utilizable por código que pueda atraparla. La usabilidad por parte de los humanos es una preocupación importante, pero solo secundaria.

Esto se encarga de la muy frustrante situación que pudo haber presenciado en algún momento de su carrera, en el que pudo haber detectado una excepción que contiene información vital sobre lo que sucedió dentro del texto del mensaje, pero no dentro de sus variables miembro , por lo que tuvo que hacer un análisis de cadena del texto para averiguar qué sucedió, esperando que el texto del mensaje se mantuviera igual en las versiones futuras de la capa subyacente, y rezando para que el texto del mensaje no esté en algún idioma extranjero cuando su programa se ejecuta en otros países.

Por supuesto, dado que el nombre de clase de la excepción es el mensaje de la excepción (y las variables miembro de la excepción son los detalles específicos), esto significa que necesita muchas y muchas excepciones diferentes para transmitir todas las diferentes mensajes, y eso está bien.

Ahora, a veces, mientras escribimos código, nos encontramos con una situación errónea por la cual solo queremos codificar rápidamente una declaración throw y continuar escribiendo nuestro código en lugar de tener que interrumpir lo que estamos haciendo para crear una nueva clase de excepción para que podamos tirar allí. Para estos casos, tengo una clase GenericException que de hecho acepta un mensaje de cadena como un parámetro de tiempo de construcción, pero el constructor de esta clase de excepción está adornado con un gran gran gran púrpura brillante FIXME XXX TODO comentario que indica que cada uno la creación de instancias de esta clase debe reemplazarse con una instanciación de alguna clase de excepción más especializada antes de que se lance el sistema de software, preferiblemente antes de que se confirme el código.

    
respondido por el Mike Nakis 14.04.2015 - 13:31
13

Como regla general, una excepción debería ayudar a los desarrolladores a identificar la causa proporcionando información útil (valores esperados, valor real, posibles causas / soluciones, etc.).

Se deben crear nuevos tipos de excepción cuando ninguno de los tipos integrados tenga sentido . Un tipo específico permite a otros desarrolladores capturar una excepción específica y manejarla. Si el desarrollador sabría cómo manejar su excepción pero el tipo es Exception , no podrá manejarlo correctamente.

    
respondido por el mbillard 23.12.2010 - 13:55
4

En .NET no siempre throw new Exception("...") (como el autor de la pregunta ha mostrado). La excepción es el tipo de excepción raíz y no debe lanzarse directamente. En su lugar, lance uno de los tipos de excepción .NET derivados o cree su propia excepción personalizada que se deriva de la Excepción (u otro tipo de excepción).

¿Por qué no lanzar Excepción? ¡Porque lanzar Exception no hace nada para describir su excepción y obliga a su código de llamada a escribir código como catch(Exception ex) { ... } que generalmente no es bueno! :-).

    
respondido por el bytedev 10.10.2016 - 17:48
2

Las cosas que desea buscar para "agregar" a la excepción son aquellos elementos de datos que no son inherentes a la excepción o al seguimiento de la pila. Si son parte del "mensaje" o deben adjuntarse cuando se registra es una pregunta interesante.

Como ya ha señalado, la excepción probablemente le dice qué, el seguimiento de la pila probablemente le dice dónde, pero el "por qué" puede ser más complicado (debería ser, uno lo esperaría) que solo mirar una línea o dos y diciendo "doh! Por supuesto". Esto es aún más cierto cuando se registran errores en el código de producción: con demasiada frecuencia me han mordido datos erróneos que han llegado a un sistema en vivo que no existe en nuestros sistemas de prueba. Algo tan simple como saber cuál es el ID del registro en la base de datos que está causando (o contribuyendo) al error puede ahorrar una cantidad significativa de tiempo.

Entonces ... ya sea en la lista o, para .NET, agregado a la recopilación de datos de excepciones registradas (c.f. @Plip!):

  • Parámetros (esto puede ser un poco interesante: no se puede agregar a la recopilación de datos si no se serializa y, a veces, un solo parámetro puede ser sorprendentemente complejo)
  • Los datos adicionales devueltos por ADO.NET o Linq a SQL o similar (¡esto también puede ser un poco interesante!).
  • Cualquier otra cosa podría no ser aparente.

Algunas cosas, por supuesto, no sabrá que necesita hasta que no las tenga en su informe / registro de error inicial. Algunas cosas que no te das cuenta que puedes obtener hasta que descubras que las necesitas.

    
respondido por el Murph 23.12.2010 - 16:55
0

¿Qué son las excepciones para ?

(1) ¿Le dijo al usuario que algo salió mal?

Este debe ser un último recurso, ya que su código debería interceder y mostrarles algo "más agradable" que una Excepción.

El mensaje de "error" debe indicar clara y sucintamente qué salió mal y qué puede hacer el usuario para recuperar de la condición de error.

por ejemplo "Por favor, no vuelva a presionar este botón"

(2) ¿Le dice a un desarrollador cuándo salió mal?

Este es el tipo de cosas en las que inicia sesión en un archivo para su posterior análisis.
El seguimiento de pila indicará al desarrollador dónde se rompió el código; el mensaje debería, nuevamente, indicar qué salió mal.

(3) ¿Le dice a un controlador de excepciones (es decir, un código) que algo salió mal?

El Tipo de la Excepción decidirá qué administrador de excepciones podrá verlo y las propiedades definidas en el objeto Excepción permitirán que el controlador se ocupe de ello.

El mensaje de la Excepción es totalmente irrelevante .

    
respondido por el Phill W. 11.10.2016 - 13:14
-3

No crees nuevos tipos si puedes evitarlo. Pueden causar confusión adicional, complejidad y llevar a más código para mantener. Ellos pueden hacer que su código se haya extendido. Diseñar una jerarquía de excepciones requiere mucho análisis y pruebas. No es un pensamiento posterior. A menudo es mejor usar la jerarquía de excepciones de idioma incorporada.

El contenido del mensaje de excepción depende del destinatario del mensaje, por lo que debe ponerse en el lugar de esa persona.

Un ingeniero de soporte deberá poder identificar la fuente del error lo más rápido posible. Incluya una breve cadena descriptiva más cualquier información que pueda ayudar a solucionar el problema. Incluya siempre el seguimiento de la pila; si puede, esta será la única fuente de información verdadera.

La presentación de errores a los usuarios generales de su sistema depende del tipo de error: si el usuario puede solucionar el problema, al proporcionar una entrada diferente, por ejemplo, se requiere un mensaje descriptivo conciso. Si el usuario no puede solucionar el problema, es mejor indicar que se ha producido un error y registrar / enviar un error para que lo admita (según las pautas anteriores).

También, no lance un gran "¡ERROR DE HALT!" icono. Es un error, no es el fin del mundo.

En resumen: piense en los actores y en los casos de uso de su sistema. Ponte en los zapatos de esos usuarios. Ser de ayuda Se bueno. Piense en esto desde el principio en el diseño de su sistema. Desde la perspectiva de sus usuarios, estos casos de excepción y cómo el sistema los maneja son tan importantes como los casos normales en su sistema.

    
respondido por el Conor 23.12.2010 - 13:26

Lea otras preguntas en las etiquetas