¿Quién debería leer Exception.Message si es que lo hace?

27

Al diseñar excepciones, ¿debo escribir mensajes que un usuario o un desarrollador deben entender? ¿Quién debería ser el lector de mensajes de excepción?

Creo que los mensajes de excepción no son útiles en absoluto y siempre me cuesta escribirlos. Por convención, el tipo de excepción ya debería decirnos por qué algo no funcionó y las propiedades personalizadas podrían agregar aún más información, como nombres de archivos, índices, claves, etc. ¿Entonces por qué repetirlo en el mensaje mismo? Un mensaje autogenerado también podría funcionar y todo lo que tenía que contener es el nombre de la excepción con una lista de propiedades adicionales. Esto sería exactamente tan útil como un texto escrito a mano.

¿No sería mejor no escribir mensajes, sino tener procesadores de excepciones especiales que se encarguen de crear mensajes significativos tal vez en diferentes idiomas en lugar de codificarlos en código?


Me preguntaron si alguna de esas preguntas proporciona una respuesta a mi pregunta:

Los he leído a ambos y no estaba contento con sus respuestas. Hablan de los usuarios en general y se centran en el contenido del mensaje en sí, en lugar del destinatario, y resulta que puede haber al menos dos de ellos: el usuario final y el desarrollador. Nunca sé con quién debo hablar al escribir mensajes de excepción.

Incluso creo que el famoso mensaje no tiene ningún valor real, ya que solo repite el nombre del tipo de excepción en diferentes palabras, así que ¿por qué molestarse en escribirlos? Puedo generarlos perfectamente automáticamente.

Para mí, el mensaje de excepción carece de una diferenciación de sus lectores. Una excepción perfecta debería proporcionar al menos dos versiones del mensaje: una para el usuario final y otra para el desarrollador. Llamarlo solo un mensaje es demasiado genérico. Un mensaje del desarrollador debe escribirse en inglés, pero el mensaje del usuario final puede necesitar ser traducido a otros idiomas. No es posible lograr todo esto con un solo mensaje, por lo que una excepción necesitaría proporcionar algún identificador al mensaje del usuario final que, como acabo de decir, podría estar disponible en diferentes idiomas.

Cuando leo todas las otras preguntas vinculadas, tengo la impresión de que un mensaje de excepción está destinado a ser leído por un usuario final y no por un desarrollador ... un solo mensaje es como tener un pastel y comérselo también.

t3chb0t
fuente
44
Hay otras dos preguntas que se hacen aquí sobre Programadores que vienen a la mente. No estoy seguro de si son duplicados o no, pero creo que debería leerlos y tal vez aborden algunas o incluso todas sus inquietudes: cómo escribir un buen mensaje de excepción y por qué muchos mensajes de excepción no contienen detalles útiles ?
Thomas Owens
2
@ThomasOwens Los he leído a ambos, pero no se dirigen al receptor específico de un mensaje de excepción. Hablan de usuarios en general, pero ¿quién es él? ¿Es el usuario del software el que ahora tiene idea sobre la programación o los desarrolladores que deberían analizar lo que salió mal? Nunca estoy seguro de a quién debo dirigirme al escribir mensajes.
t3chb0t
2
Creo que una buena respuesta aquí puede referirse a una o ambas preguntas, pero están lejos de ser duplicadas.
Lightness compite con Monica el
1
¿Por qué demonios se cerró esto como duplicado? Incluso si fuera un duplicado de esa otra pregunta (que no lo es), esta claramente tiene una mejor respuesta, debería ser la otra que esté marcada como duplicada.
Ben Aaronson
2
@MichaelT Todavía no veo mucha superposición allí a menos que presupongas que los mensajes de excepción son para los usuarios
Ben Aaronson,

Respuestas:

46

Esos mensajes son para otros desarrolladores.

Se espera que los desarrolladores lean esos mensajes para ayudarlos a depurar la aplicación. Esto puede tomar dos formas:

  • Depuración activa En realidad, está ejecutando un depurador mientras escribe código e intenta averiguar qué sucede. En este contexto, una excepción útil lo guiará al facilitarle la comprensión de lo que está sucediendo o, eventualmente, le sugerirá una solución alternativa (aunque esto es opcional).

  • Depuración pasiva. El código se ejecuta en producción y falla. La excepción se registra, pero solo obtiene el mensaje y el seguimiento de la pila. En este contexto, un mensaje de excepción útil lo ayudará a localizar rápidamente el error.

    Dado que esos mensajes a menudo se registran, también significa que no debe incluir información confidencial allí (como claves privadas o contraseñas, incluso si pudiera ser útil para depurar la aplicación).

Por ejemplo, el IOSecurityExceptiontipo de excepción que se produce al escribir un archivo no es muy explícito sobre el problema: ¿es porque no tenemos permisos para acceder a un archivo? ¿O tal vez podamos leerlo, pero no escribir? ¿O tal vez el archivo no existe y no tenemos permisos para crear archivos allí? O tal vez está bloqueado (con suerte, el tipo de excepción será diferente en este caso, pero en la práctica, los tipos a veces pueden ser crípticos). ¿O tal vez Code Access Security nos impide realizar cualquier operación de E / S?

En lugar:

IOSecurityException: se encontró el archivo pero se denegó el permiso para leer su contenido.

Es mucho más explícito. Aquí, sabemos de inmediato que los permisos en el directorio están configurados correctamente (de lo contrario, no podremos saber que el archivo existe), pero los permisos a nivel de archivo son problemáticos.

Esto también significa que si no puede proporcionar ninguna información adicional que no esté en el tipo de excepción, puede mantener el mensaje vacío. DivisionByZeroExceptionEs un buen ejemplo donde el mensaje es redundante. Por otro lado, el hecho de que la mayoría de los idiomas le permiten lanzar una excepción sin especificar su mensaje se hace por una razón diferente: ya sea porque el mensaje predeterminado ya está disponible o porque se generará más tarde si es necesario (y la generación de este mensaje). el mensaje está encerrado dentro del tipo de excepción, lo que tiene mucho sentido, "OOPly" hablando).

Tenga en cuenta que por razones técnicas (a menudo de rendimiento), algunos mensajes terminan siendo mucho más crípticos de lo que deberían ser. .NET's NullReferenceException:

Referencia a objeto no establecida como instancia de un objeto.

es un excelente ejemplo de un mensaje que no es útil. Un mensaje útil sería:

La referencia de objeto productno se establece en una instancia de un objeto cuando se llama product.Price.

¡Esos mensajes no son para usuarios finales!

No se espera que los usuarios finales vean mensajes de excepción. Nunca. Aunque algunos desarrolladores terminan mostrando esos mensajes a los usuarios, esto conduce a una experiencia de usuario deficiente y a la frustración. Mensajes como:

Referencia a objeto no establecida como instancia de un objeto.

no significa absolutamente nada para un usuario final y debe evitarse a toda costa.

El peor de los casos es tener un try / catch global que arroje la excepción al usuario y salga de la aplicación. Una aplicación que se preocupa por sus usuarios:

  • Maneja excepciones en primer lugar. La mayoría se pueden manejar sin molestar al usuario. ¿La red está inactiva? ¿Por qué no esperar unos segundos y volver a intentarlo?

  • Impide que un usuario dirija la aplicación a un caso excepcional. Si le pide al usuario que ingrese dos números y divida el primero por el segundo, ¿por qué permitiría al usuario ingresar cero en el segundo caso para culparlo unos segundos después? ¿Qué hay de resaltar el cuadro de texto en rojo (con una información útil que dice que el número debe ser diferente a cero) y deshabilitar el botón de validación hasta que el campo permanezca rojo?

  • Invita a un usuario a realizar una acción en un formulario que no sea un error. ¿No hay suficientes permisos para acceder a un archivo? ¿Por qué no pedirle al usuario que otorgue permisos administrativos o elegir un archivo diferente?

  • Si nada más funciona, muestra un error útil y amigable que está escrito específicamente para reducir la frustración del usuario, ayudarlo a comprender qué salió mal y eventualmente resolver el problema, y ​​también ayudarlo a prevenir el error en el futuro (cuando corresponda).

    En su pregunta, sugirió tener dos mensajes en una excepción: uno técnico para desarrolladores y otro para usuarios finales. Si bien esta es una sugerencia válida en algunos casos menores, la mayoría de las excepciones se producen a un nivel en el que es imposible producir un mensaje significativo para los usuarios. Tome DivisionByZeroExceptione imagine que no podríamos evitar que ocurra la excepción y no podemos manejarla nosotros mismos. Cuando ocurre la división, ¿sabe el marco (ya que es el marco, y no el código comercial, lo que arroja la excepción) cuál será un mensaje útil para un usuario? Absolutamente no:

    La división por cero ocurrió. [OKAY]

    En cambio, uno puede dejar que arroje la excepción, y luego atraparlo en un nivel superior donde conocemos el contexto comercial y podríamos actuar en consecuencia para ayudar realmente al usuario final, mostrando así algo como:

    El campo D13 no puede tener el valor idéntico al del campo E6, porque la resta de esos valores se usa como divisor. [OKAY]

    o tal vez:

    Los valores informados por el servicio ATP son inconsistentes con los datos locales. Esto puede deberse a que los datos locales no están sincronizados. ¿Desea sincronizar la información de envío y volver a intentarlo? [Si no]

Esos mensajes no son para analizar

Tampoco se espera que los mensajes de excepción sean analizados o utilizados mediante programación. Si cree que la persona que llama puede necesitar información adicional, inclúyala en la excepción junto con el mensaje. Esto es importante porque el mensaje está sujeto a cambios sin previo aviso. El tipo es parte de la interfaz, pero el mensaje no lo es: nunca confíe en él para el manejo de excepciones.

Imagine el mensaje de excepción:

La conexión al servidor de almacenamiento en caché expiró después de esperar 500 ms. Aumente el tiempo de espera o verifique la supervisión del rendimiento para identificar una caída en el rendimiento del servidor. El tiempo de espera promedio para el servidor de almacenamiento en caché fue de 6 ms. para el último mes, 4 ms. para la última semana y 377 ms. por la última hora

Desea extraer los valores "500", "6", "4" y "377". Piensa un poco en el enfoque que usarás para hacer el análisis, y solo entonces continúa leyendo.

Tienes la idea? Excelente.

Ahora, el desarrollador original descubrió un error tipográfico:

Connecting to the caching sever timed out after waiting [...]

debiera ser:

                            ↓
Connecting to the caching server timed out after waiting [...]

Además, el desarrollador considera que mes / semana / una hora no son particularmente relevantes, por lo que también realiza un cambio adicional:

El tiempo de espera promedio para el servidor de almacenamiento en caché fue de 6 ms. para el último mes, 5 ms. durante las últimas 24 horas y 377 ms. por la última hora

¿Qué pasa con tu análisis?

En lugar de analizar, podría estar utilizando propiedades de excepción (que pueden contener lo que uno quiera, tan pronto como se puedan serializar los datos):

{
    message: "Connecting to the caching [...]",
    properties: {
        "timeout": 500,
        "statistics": [
            { "timespan": 1, "unit": "month", "average-timeout": 6 },
            { "timespan": 7, "unit": "day", "average-timeout": 4 },
            { "timespan": 1, "unit": "hour", "average-timeout": 377 },
        ]
    }
}

¿Qué tan fácil es usar estos datos ahora?

A veces (como en .NET), el mensaje puede incluso traducirse al idioma del usuario (en mi humilde opinión, traducir esos mensajes es absolutamente incorrecto, ya que se espera que cualquier desarrollador pueda leer en inglés). Analizar tales mensajes es casi imposible.

Arseni Mourzenko
fuente
2
Buen punto sobre el usuario final. Agregaría que el usuario final no debería ver mensajes de excepción también por razones de seguridad. Conocer la naturaleza exacta de un error para un usuario no laico puede presentar una vulnerabilidad. El usuario final solo debe conocer el error en el contexto de lo que estaba haciendo.
Neil
1
@Neil: eso es un poco teórico. En la práctica, la complejidad del registro en línea supera el beneficio de ocultar los registros al usuario. Sin contar el aspecto amigable para el usuario. Imagine que está instalando Visual Studio en su nueva máquina virtual de Windows. De repente, la instalación falla. ¿Preferiría ir a los registros y diagnosticar el problema usted mismo (con la ayuda de Stack Exchange y blogs), o esperar hasta que Microsoft resuelva el problema y publique una revisión?
Arseni Mourzenko
44
Creo que todos saben que el mensaje "referencia de objeto ..." no es útil. Sin embargo, la pregunta relevante es ¿qué código de máquina le gustaría que generara la fluctuación que produce el mensaje que desea? Si realiza este ejercicio, descubrirá rápidamente que el mensaje no se puede generar fácilmente sin un enorme costo de rendimiento impuesto en todos los casos no excepcionales . El beneficio de hacer que el trabajo de un desarrollador descuidado sea un poco más fácil no se paga con el rendimiento alcanzado por el usuario final.
Eric Lippert
77
Con respecto a las excepciones de seguridad: cuanto menos información en la excepción, mejor. Mi anécdota favorita es que en una versión pre-beta de .NET 1.0 podría recibir un mensaje de excepción como "No tiene permiso para determinar el nombre del archivo C: \ foo.txt". ¡Genial, gracias por ese mensaje de excepción detallado!
Eric Lippert
2
No estoy de acuerdo con no mostrar nunca al usuario final un mensaje de error detallado. Me ha sucedido muchas veces que recibo un mensaje de error críptico que me impide iniciar mi juego / software, pero cuando lo busco en Google descubro que hay una solución fácil. Sin ese mensaje de error no habría forma de encontrar la solución. ¿Quizás es mejor ocultar los detalles debajo del botón "más detalles"?
BlueRaja - Danny Pflughoeft
2

La respuesta a esto depende completamente de la excepción.

La pregunta más importante es "¿quién puede solucionar el problema?" Si la excepción es causada por un error del sistema de archivos mientras está abriendo un archivo, puede tener sentido dar ese mensaje al usuario final. El mensaje puede contener más información sobre cómo solucionar el error. Puedo pensar en un caso definitivo en mi trabajo en el que tenía un sistema de complementos que cargaba archivos DLL. Si un usuario cometió un error tipográfico en la línea de comando para cargar el complemento incorrecto, el mensaje de error del sistema subyacente en realidad contenía información útil para permitirle corregir el problema.

Sin embargo, la mayoría de las veces, el usuario no puede solucionar una excepción no detectada. La mayoría de ellos implica hacer cambios en el código. En estos casos, el consumidor del mensaje es claramente un desarrollador.

El caso de las excepciones detectadas es más complicado porque tiene la oportunidad de procesar adecuadamente la excepción y lanzar una más amigable. Un lenguaje de scripting que escribí arrojó excepciones cuyo mensaje estaba claramente destinado a un desarrollador. Sin embargo, a medida que la pila se desenrollaba, agregué rastros de pila legibles por el usuario desde el lenguaje de scripting. Mis usuarios rara vez podían asimilar el mensaje, pero podían ver el rastro de la pila en su secuencia de comandos y averiguar qué sucedía el 95% del tiempo. Para el 5% restante, cuando me llamaron, no solo teníamos un seguimiento de la pila, sino un mensaje preparado por el desarrollador para ayudarme a descubrir qué estaba mal.

En general, trato el mensaje de excepción como un contenido desconocido. Alguien más arriba, que sabía más sobre las implementaciones, le ha dado a mi software una excepción. A veces tengo el presentimiento de que el contenido desconocido será útil para un usuario final, a veces no.

Cort Ammon - Restablece a Monica
fuente
1
"Si la excepción es causada por un error del sistema de archivos mientras está abriendo un archivo, puede tener sentido dar ese mensaje al usuario final": pero cuando arroja el error del sistema de archivos, no conoce el contexto: podría ser la acción del usuario o algo completamente ajeno (como que su aplicación realice una copia de seguridad de alguna configuración, sin que el usuario se dé cuenta). Esto implica que tendrá un bloque try / catch que muestra un mensaje de error , que es muy diferente de mostrar el mensaje de excepción directamente.
Arseni Mourzenko
@MainMa Puede que no conozca el contexto explícitamente, pero hay toneladas de pistas. Por ejemplo, si están en el proceso de abrir un archivo, y la excepción es "IOError: permiso denegado", existe una posibilidad razonable de que el usuario pueda juntar 2 y 2 y descifrar el archivo en cuestión. Mi editor favorito hace esto: solo muestra el texto de excepción en un cuadro de diálogo, sin pelusas. Por otro lado, si estaba cargando mi aplicación y recibí un "permiso denegado" mientras cargaba un montón de imágenes, es mucho menos razonable suponer que el usuario puede hacer algo con la información.
Cort Ammon - Restablece a Mónica el
Argumentando su punto, nunca he visto ningún valor en mostrarle a un usuario una NullReferenceException, aparte de eso, el solo hecho de mostrar ese mensaje muestra que su software no tenía idea de cómo recuperarse. Ese mensaje de excepción nunca es útil para un usuario final.
Cort Ammon - Restablece a Mónica el