C #: Lanzamiento de mejores prácticas de excepción personalizada

He leído algunas de las otras preguntas sobre C # Exception Handling Practices, pero ninguna parece preguntar qué estoy buscando.

Si implemento mi propia Excepción personalizada para una clase o conjunto de clases en particular. ¿Deberían todos los errores que se relacionan con esas clases ser encapsulados en mi excepción usando la excepción interna o debería dejarlos pasar?

Estaba pensando que sería mejor detectar todas las excepciones para que la excepción pueda ser reconocida inmediatamente por mi fuente. Todavía estoy pasando la excepción original como una excepción interna. Por otro lado, estaba pensando que sería redundante volver a lanzar la excepción.

Excepción:

class FooException : Exception { //... } 

Opción 1: Foo encasula todas las excepciones:

 class Foo { DoSomething(int param) { try { if (/*Something Bad*/) { //violates business logic etc... throw new FooException("Reason..."); } //... //something that might throw an exception } catch (FooException ex) { throw; } catch (Exception ex) { throw new FooException("Inner Exception", ex); } } } 

Opción 2: Foo arroja FooExceptions específicos, pero permite que otras excepciones fallen:

 class Foo { DoSomething(int param) { if (/*Something Bad*/) { //violates business logic etc... throw new FooException("Reason..."); } //... //something that might throw an exception and not caught } } 

En función de mi experiencia con las bibliotecas, debe incluir todo lo que pueda anticipar en una FooException por varias razones:

  1. La gente sabe que vino de tus clases, o al menos, su uso de ellas. Si ven FileNotFoundException , pueden estar buscando todo. Los estás ayudando a reducirlo. (Ahora me doy cuenta de que el seguimiento de la stack sirve para este propósito, así que quizás puedas ignorar este punto).

  2. Puedes proporcionar más contexto. Envolviendo un FNF con su propia excepción, puede decir “Intenté cargar este archivo para este propósito y no pude encontrarlo. Esto sugiere posibles soluciones correctas.

  3. Su biblioteca puede manejar la limpieza correctamente. Si dejas que la excepción burbujee, estás obligando al usuario a limpiar. Si has encapsulado correctamente lo que estabas haciendo, ¡entonces no tienen idea de cómo manejar la situación!

Recuerde solo ajustar las excepciones que puede anticipar, como FileNotFound . No se limite a Exception y espere lo mejor.

Eche un vistazo a estas mejores prácticas de MSDN .

Considere utilizar throw lugar de throw ex si desea volver a lanzar excepciones capturadas, porque de esta manera la stacktrace original se conserva (números de línea, etc.).

Siempre agrego un par de propiedades cuando creo una excepción personalizada. Uno es el nombre de usuario o ID. Agrego una propiedad DisplayMessage para llevar el texto que se mostrará al usuario. Luego, utilizo la propiedad Mensaje para transmitir los detalles técnicos que se registrarán en el registro.

Capturo cada error en la Capa de acceso a datos en un nivel donde aún puedo capturar el nombre del procedimiento almacenado y los valores de los parámetros pasados. O el SQL en línea. Tal vez el nombre de la base de datos o la cadena de conexión parcial (sin credenciales, por favor). Esos pueden ir en Mensaje o en su propia nueva propiedad personalizada de DatabaseInfo.

Para las páginas web, utilizo la misma excepción personalizada. Pondré en la propiedad Mensaje la información del formulario: lo que el usuario ingresó en cada control de entrada de datos en la página web, el ID del artículo que se está editando (cliente, producto, empleado, lo que sea) y la acción que el usuario estaba tomando cuando ocurrió la excepción.

Entonces, mi estrategia según tu pregunta es: solo captar cuando puedo hacer algo con respecto a la excepción. Y bastante a menudo, todo lo que puedo hacer es registrar los detalles. Por lo tanto, solo capto en el punto donde esos detalles están disponibles, y luego vuelvo a lanzar para permitir que la excepción suba a la interfaz de usuario. Y conservo la excepción original en mi excepción personalizada.

El propósito de las excepciones personalizadas es proporcionar información detallada y contextual a la stack para ayudar a la depuración. La opción 1 es mejor porque sin ella, no se obtiene el “origen” de la excepción si se produce “más bajo” en la stack.

Si ejecuta el fragmento de código para ‘Excepción’ en Visual Studio, tiene una plantilla de escritura de excepción de buenas prácticas.

Nota Opción 1: su throw new FooException("Reason..."); no será atrapado ya que está fuera de try / catch block

  1. Solo debe capturar las excepciones que desea procesar.
  2. Si no agrega datos adicionales a la excepción, use throw; ya que no matará tu stack En la Opción 2, aún podría hacer algún procesamiento dentro de catch y simplemente invocar throw; para volver a lanzar la excepción original con la stack original.

Lo más importante que debe saber el código al detectar una excepción, que desafortunadamente falta por completo en el objeto Exception, es el estado del sistema en relación con lo que “debería” ser (presumiblemente la excepción se produjo porque había algo mal). Si se produce un error en un método LoadDocument, es de suponer que el documento no se cargó correctamente, pero hay al menos dos posibles estados del sistema:

  1. El estado del sistema puede ser como si la carga nunca se hubiera intentado. En este caso, sería completamente apropiado que la aplicación continúe si puede hacerlo sin el documento cargado.
  2. El estado del sistema puede estar lo suficientemente dañado como para que lo mejor sea guardar lo que se puede guardar en los archivos de “recuperación” (evite reemplazar los buenos archivos del usuario con datos posiblemente corruptos) y apague.

Obviamente, a menudo habrá otros estados posibles entre esos extremos. Sugeriría que uno debe esforzarse por tener una excepción personalizada que indique explícitamente que el estado # 1 existe, y posiblemente uno para el # 2 si es previsible pero las circunstancias inevitables pueden causarlo. Cualquier excepción que ocurra y resulte en el estado n. ° 1 debe envolverse en un objeto de excepción que indique el estado n. ° 1. Si las excepciones pueden ocurrir de tal manera que el estado del sistema podría verse comprometido, deberían estar envueltas como # 2 o permitidas para filtrarse.

La opción 2 es mejor. Creo que la mejor práctica es detectar excepciones solo cuando planeas hacer algo con la excepción.

En este caso, la Opción 1 simplemente está envolviendo una excepción con su propia excepción. No agrega ningún valor y los usuarios de su clase ya no pueden capturar ArgumentException, por ejemplo, también necesitan capturar su FooException y luego analizar la excepción interna. Si la excepción interna no es una excepción, pueden hacer algo útil que tendrán que volver a lanzar.