Estoy luchando con una pregunta muy simple:
Ahora estoy trabajando en una aplicación de servidor, y necesito inventar una jerarquía para las excepciones (algunas excepciones ya existen, pero se necesita un marco general). ¿Cómo empiezo a hacer esto?
Estoy pensando en seguir esta estrategia:
1) ¿Qué va mal?
- Se pregunta algo, que no está permitido.
- Se pregunta algo, está permitido, pero no funciona debido a parámetros incorrectos.
- Se pregunta algo, está permitido, pero no funciona debido a errores internos.
2) ¿Quién está lanzando la solicitud?
- La aplicación cliente
- Otra aplicación de servidor
3) Entrega de mensajes: como se trata de una aplicación de servidor, se trata de recibir y enviar mensajes. ¿Y qué pasa si el envío de un mensaje sale mal?
Como tal, podríamos obtener los siguientes tipos de excepción:
- ServerNotAllowedException
- ClientNotAllowedException
- ServerParameterException
- ClientParameterException
- InternalException (en caso de que el servidor no sepa de dónde proviene la solicitud)
- ServerInternalException
- ClientInternalException
- MessageHandlingException
Este es un enfoque muy general para definir la jerarquía de excepciones, pero me temo que me pueden faltar algunos casos obvios. ¿Tiene ideas sobre qué áreas no estoy cubriendo, conoce algún inconveniente de este método o hay un enfoque más general para este tipo de preguntas (en el último caso, ¿dónde puedo encontrarlo?)
Gracias por adelantado
fuente
catch
bloques que uso, no tengo mucho más uso para la excepción que el mensaje de error que contiene. Realmente no tengo nada diferente que pueda hacer por una excepción involucrada en no leer un archivo, ya que no se puede asignar memoria durante el proceso de lectura, por lo que tiendo a captarstd::exception
e informar el mensaje de error que contiene, tal vez decorar con"Failed to open file: %s", ex.what()
un buffer de pila antes de imprimirlo.catch
bloques diferentes en un solo sitio de recuperación, pero a menudo es solo ignorar el mensaje dentro de la excepción e imprimir un mensaje más localizado ...Respuestas:
Observaciones generales
(un poco sesgado de opinión)
Por lo general, no elegiría una jerarquía de excepción detallada.
Lo más importante: una excepción le dice a su interlocutor que su método no pudo completar su trabajo. Y su interlocutor debe recibir un aviso al respecto, para que no simplemente continúe. Eso funciona con cualquier excepción, sin importar la clase de excepción que elija.
El segundo aspecto es el registro. Desea encontrar entradas de registro significativas cuando algo sale mal. Eso tampoco necesita diferentes clases de excepción, solo mensajes de texto bien diseñados (supongo que no necesita un autómata para leer sus registros de errores ...).
El tercer aspecto es la reacción de su interlocutor. ¿Qué puede hacer su interlocutor cuando recibe una excepción? Aquí puede tener sentido tener diferentes clases de excepción, por lo que la persona que llama puede decidir si volver a intentar la misma llamada, usar una solución diferente (por ejemplo, usar una fuente alternativa) o darse por vencido.
Y tal vez desee utilizar sus excepciones como base para informar al usuario final sobre el problema. Eso significa crear un mensaje fácil de usar además del texto de administración para el archivo de registro, pero no necesita diferentes clases de excepción (aunque tal vez eso pueda facilitar la generación de texto ...).
Un aspecto importante para el registro (y para los mensajes de error del usuario) es la capacidad de enmendar la excepción con información de contexto al capturarla en alguna capa, agregar cierta información de contexto, por ejemplo, parámetros del método, y volver a lanzarla.
Su jerarquía
¿Quién está lanzando la solicitud? No creo que necesite la información sobre quién inició la solicitud. Ni siquiera puedo imaginar cómo sabes eso en el fondo de una pila de llamadas.
Manejo de mensajes : ese no es un aspecto diferente, sino solo casos adicionales para "¿Qué va mal?".
En un comentario, habla de un indicador de " no iniciar sesión " al crear una excepción. No creo que en el lugar donde cree y arroje una excepción, pueda tomar una decisión confiable sobre si registrar o no esa excepción.
La única situación que puedo imaginar es que alguna capa superior usa su API de una manera que a veces producirá excepciones, y esta capa sabe que no necesita molestar a ningún administrador con la excepción, por lo que se traga la excepción en silencio. Pero ese es un olor a código: una excepción esperada es una contradicción en sí misma, una pista para cambiar la API. Y es la capa superior la que debería decidir, no el código generador de excepciones.
fuente
Lo principal a tener en cuenta al diseñar un patrón de respuesta de error es asegurarse de que sea útil para las personas que llaman. Esto se aplica si está utilizando excepciones o códigos de error definidos, pero nos limitaremos a ilustrar con excepciones.
Si su lenguaje o marco ya proporciona clases de excepción general, úselas donde sean apropiadas y donde sea razonable esperarlas . No defina sus propias clases
ArgumentNullException
o lasArgumentOutOfRange
clases de excepción. Las personas que llaman no esperarán atraparlos.Defina una
MyClientServerAppException
clase base para abarcar los errores que son únicos dentro del contexto de su aplicación. Nunca arrojes una instancia de la clase base. Una respuesta de error ambigua es LO PEOR NUNCA . Si hay un "error interno", explique cuál es ese error.En su mayor parte, la jerarquía debajo de la clase base debe ser amplia, no profunda. Solo necesita profundizarlo en situaciones en las que es útil para la persona que llama. Por ejemplo, si hay 5 razones por las que un mensaje puede fallar del cliente al servidor, puede definir una
ServerMessageFault
excepción y luego definir una clase de excepción para cada uno de esos 5 errores debajo de eso. De esa manera, la persona que llama puede capturar la superclase si lo necesita o quiere. Intente limitar esto a casos específicos y razonables.No intente definir todas sus clases de excepción antes de que realmente se usen. Terminarás volviendo a hacer la mayor parte. Cuando encuentre un caso de error al escribir código, decida cuál es la mejor manera de describir ese error. Idealmente, debería expresarse en el contexto de lo que la persona que llama está tratando de hacer.
En relación con el punto anterior, recuerde que solo porque use excepciones para responder a los errores, eso no significa que tenga que usar solo excepciones para los estados de error. Tenga en cuenta que lanzar excepciones es usualmente costoso, y el costo de rendimiento puede variar de un idioma a otro. Con algunos idiomas, el costo es mayor dependiendo de la profundidad de la pila de llamadas, por lo que si un error es profundo dentro de una pila de llamadas, verifique si no puede usar tipos primitivos simples (códigos de error enteros o banderas booleanas) para empujar el error hace una copia de seguridad de la pila, por lo que puede arrojarse más cerca de la invocación de la persona que llama.
Si incluye el registro como parte de la respuesta de error, debería ser fácil para las personas que llaman agregar información de contexto a un objeto de excepción. Comience desde donde se usa la información en el código de registro. Decida cuánta información se necesita para que el registro sea útil (sin ser un muro gigante de texto). Luego trabaje hacia atrás para asegurarse de que las clases de excepción puedan proporcionarse fácilmente con esa información.
Por último, a menos que su aplicación puede absolutamente lidiar con gracia con un error fuera de la memoria, no se trata de hacer frente a esos, u otras excepciones de tiempo de ejecución catastróficos. Simplemente deje que el sistema operativo lo solucione, porque en realidad, eso es todo lo que puede hacer.
fuente
Bueno, le recomendaría que, ante todo, cree una
Exception
clase base para todas las excepciones marcadas que su aplicación pueda generar . Si se llamó a su aplicaciónDuckType
, haga unaDuckTypeException
clase base.Esto le permite detectar cualquier excepción de su
DuckTypeException
clase base para el manejo. A partir de aquí, sus excepciones deben ramificarse con nombres descriptivos que puedan resaltar mejor el tipo de problema. Por ejemplo, "DatabaseConnectionException".Permítanme ser claro, todas estas excepciones deben verificarse para situaciones que podrían ocurrir y que probablemente desearían manejar con gracia en su programa. En otras palabras, no puede conectarse a la base de datos, por lo que
DatabaseConnectionException
se genera un mensaje que luego puede atrapar para esperar y volver a intentar después de un período de tiempo.Se podría no ver una excepción comprobada para un problema muy inesperado, como una consulta SQL válida o una excepción de puntero nulo, y les animo a dejar que estas excepciones trascienden la mayoría de las cláusulas de captura (o capturados y relanza como sea necesario) hasta que llegue a la principal controlador en sí mismo, que luego puede capturarlo
RuntimeException
únicamente con fines de registro.Mi preferencia personal es no volver a lanzar una
RuntimeException
excepción no verificada como otra excepción, ya que por la naturaleza de una excepción no verificada, no lo esperaría y, por lo tanto, volver a lanzarla bajo otra excepción es ocultar información. Sin embargo, si esa es su preferencia, aún puede atraparRuntimeException
y lanzar unDuckTypeInternalException
que, a diferencia de lo que seDuckTypeException
deriva, noRuntimeException
está marcado.Si lo prefiere, puede subcategorizar sus excepciones para fines organizativos, como
DatabaseException
cualquier cosa relacionada con la base de datos, pero aún así recomendaría que dichas sub-excepciones se deriven de su excepción baseDuckTypeException
y que sean abstractas y, por lo tanto, derivadas con nombres descriptivos explícitos.Como regla general, sus capturas de prueba deberían ser cada vez más genéricas a medida que avanza en la pila de llamadas de los llamantes para manejar excepciones, y nuevamente, en su controlador principal, no manejaría un,
DatabaseConnectionException
sino el simpleDuckTypeException
de que derivan todas sus excepciones marcadas.fuente
Intenta simplificar eso.
Lo primero que lo ayudará a pensar en otra estrategia es: detectar muchas excepciones es muy similar al uso de excepciones comprobadas de Java (lo siento, no soy desarrollador de C ++). Esto no es bueno por muchas razones, así que siempre trato de no usarlas, y su estrategia de excepción de jerarquía me recuerda mucho de eso.
Por lo tanto, le recomiendo otra estrategia flexible: use excepciones no verificadas y errores de código.
Por ejemplo, vea este código Java:
Y tu única excepción:
Esta estrategia la encontré en este enlace y puede encontrar la implementación de Java aquí , donde puede ver más detalles al respecto, porque el código anterior está simplificado.
Como necesita separar las diferentes excepciones entre las aplicaciones "cliente" y "otro servidor", puede tener múltiples clases de códigos de error que implementen la interfaz ErrorCode.
fuente
Las excepciones son gotos sin restricciones y deben usarse con cuidado. La mejor estrategia para ellos es restringirlos. La función de llamada debe manejar todas las excepciones lanzadas por las funciones que llama o el programa muere. Solo la función de llamada tiene el contexto correcto para manejar la excepción. Tener funciones más arriba en el árbol de llamadas que las maneja son gotos sin restricciones.
Las excepciones no son errores. Ocurren cuando las circunstancias impiden que el programa complete una rama del código e indican otra rama para que siga.
Las excepciones deben estar en el contexto de la función llamada. Por ejemplo: una función que resuelve ecuaciones cuadráticas . Debe manejar dos excepciones: division_by_zero y square_root_of_negative_number. Pero estas excepciones no tienen sentido para las funciones que llaman a este solucionador de ecuaciones cuadráticas. Suceden debido al método utilizado para resolver ecuaciones y simplemente volver a lanzarlas expone las partes internas y rompe la encapsulación. En su lugar, division_by_zero se debe volver a generar como not_quadratic y square_root_of_negative_number y no_real_roots.
No hay necesidad de una jerarquía de excepciones. Una enumeración de las excepciones (que las identifica) lanzadas por una función es suficiente porque la función de llamada debe manejarlas. Permitirles procesar el árbol de llamadas es un goto fuera de contexto (sin restricciones).
fuente