Cómo usar try catch para el manejo de excepciones es la mejor práctica

mientras mantengo el código de mi colega, incluso de alguien que dice ser un desarrollador sénior, a menudo veo el siguiente código:

try { //do something } catch { //Do nothing } 

o a veces escriben información de registro para registrar archivos como los siguientes try catch block

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception); } 

Me pregunto si lo que han hecho es la mejor práctica. Me hace confundir porque, en mi opinión, los usuarios deben saber qué ocurre con el sistema.

Por favor, dame un consejo.

Mi estrategia de manejo de excepciones es:

  • Para capturar todas las excepciones no controladas conectando al Application.ThreadException event , entonces decide:

    • Para una aplicación de interfaz de usuario: mostrarlo al usuario con un mensaje de disculpa (winforms)
    • Para una aplicación de servicio o consola: conéctela a un archivo (servicio o consola)

Luego siempre encierre cada fragmento de código que se ejecuta externamente en try/catch :

  • Todos los eventos activados por la infraestructura de Winforms (Load, Click, SelectedChanged …)
  • Todos los eventos activados por componentes de terceros

Luego encierro en ‘try / catch’

  • Todas las operaciones que conozco pueden no funcionar todo el tiempo (operaciones IO, cálculos con una división cero potencial …). En tal caso, lanzo una nueva ApplicationException("custom message", innerException) para hacer un seguimiento de lo que realmente sucedió

Además, hago mi mejor esfuerzo para ordenar las excepciones correctamente . Hay excepciones que:

  • debe mostrarse al usuario de inmediato
  • requieren un procesamiento adicional para unir las cosas cuando pasan, para evitar problemas en cascada (es decir: poner .EndUpdate en la sección finally durante un relleno TreeView )
  • al usuario no le importa, pero es importante saber qué sucedió. Así que siempre los registro:

    • En el registro de eventos
    • o en un archivo .log en el disco

Es una buena práctica diseñar algunos métodos estáticos para manejar excepciones en los manejadores de errores de nivel superior de la aplicación.

También me esfuerzo por intentar:

  • Recuerde que TODAS las excepciones se borran hasta el nivel superior . No es necesario poner manejadores de excepciones en todas partes.
  • Las funciones llamadas reutilizables o profundas no necesitan mostrar o registrar excepciones: se borran automáticamente o se vuelven a lanzar con algunos mensajes personalizados en mis manejadores de excepciones.

Así que finalmente :

Malo:

 // DON'T DO THIS, ITS BAD try { ... } catch { // only air... } 

Inútil:

 // DONT'T DO THIS, ITS USELESS try { ... } catch(Exception ex) { throw ex; } 

Tener una oportunidad finalmente sin trampas es perfectamente válido:

 try { listView1.BeginUpdate(); // If an exception occurs in the following code, then the finally will be executed // and the exception will be thrown ... } finally { // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT listView1.EndUpdate(); } 

Lo que hago en el nivel superior:

 // ie When the user clicks on a button try { ... } catch(Exception ex) { ex.Log(); // Log exception -- OR -- ex.Log().Display(); // Log exception, then show it to the user with apologies... } 

Lo que hago en algunas funciones llamadas:

 // Calculation module try { ... } catch(Exception ex) { // Add useful information to the exception throw new ApplicationException("Something wrong happened in the calculation module :", ex); } // IO module try { ... } catch(Exception ex) { throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex); } 

Hay mucho que ver con el manejo de excepciones (Excepciones personalizadas), pero esas reglas que trato de tener en cuenta son suficientes para las aplicaciones simples que hago.

Aquí hay un ejemplo de métodos de extensiones para manejar las excepciones detectadas de una manera cómoda. Se implementan de una manera que pueden encadenarse entre sí, y es muy fácil agregar su propio proceso de excepción atrapado.

 // Usage: try { // boom } catch(Exception ex) { // Only log exception ex.Log(); -- OR -- // Only display exception ex.Display(); -- OR -- // Log, then display exception ex.Log().Display(); -- OR -- // Add some user-friendly message to an exception new ApplicationException("Unable to calculate !", ex).Log().Display(); } // Extension methods internal static Exception Log(this Exception ex) { File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n"); return ex; } internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error) { MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img); return ex; } 

La mejor práctica es que el manejo de excepciones nunca debe ocultar problemas . Esto significa que try-catch bloques try-catch deberían ser extremadamente raros.

Hay 3 circunstancias en try-catch tiene sentido usar un try-catch .

  1. Siempre trate con las excepciones conocidas lo más bajo posible. Sin embargo, si está esperando una excepción, generalmente es una mejor práctica probarla primero. Por ejemplo, las excepciones de análisis, formato y aritmética casi siempre se manejan mejor mediante verificaciones lógicas primero, en lugar de un try-catch específico.

  2. Si necesita hacer algo en una excepción (por ejemplo, iniciar sesión o retrotraer una transacción), vuelva a lanzar la excepción.

  3. Siempre trate con excepciones desconocidas tan alto como pueda: el único código que debe consumir una excepción y no relanzarlo debe ser la interfaz de usuario o la API pública.

Supongamos que se está conectando a una API remota, aquí sabe esperar ciertos errores (y tener cosas que hacer en esas circunstancias), así que este es el caso 1:

 try { remoteApi.Connect() } catch(ApiConnectionSecurityException ex) { // User's security details have expired return false; } return true; 

Tenga en cuenta que no se detectan otras excepciones, ya que no se esperan.

Supongamos ahora que está intentando guardar algo en la base de datos. Tenemos que devolverlo si falla, entonces tenemos el caso 2:

 try { DBConnection.Save(); } catch { // Roll back the DB changes so they aren't corrupted on ANY exception DBConnection.Rollback(); // Re-throw the exception, it's critical that the user knows that it failed to save throw; } 

Tenga en cuenta que volvemos a lanzar la excepción: el código más arriba todavía necesita saber que algo ha fallado.

Finalmente tenemos la IU: aquí no queremos tener excepciones no controladas por completo, pero tampoco queremos ocultarlas. Aquí tenemos un ejemplo del caso 3:

 try { // Do something } catch(Exception ex) { // Log exception for developers WriteException2LogFile(ex); // Display message to users DisplayWarningBox("An error has occurred, please contact support!"); } 

Sin embargo, la mayoría de los marcos API o UI tienen formas genéricas de presentar el caso 3. Por ejemplo, ASP.Net tiene una pantalla de error amarilla que vuelca los detalles de la excepción, pero puede ser reemplazada por un mensaje más genérico en el entorno de producción. Seguir estas es una buena práctica porque te ahorra una gran cantidad de código, pero también porque el registro de errores y la visualización deberían ser decisiones de configuración en lugar de código fijo.

Todo esto significa que el caso 1 (excepciones conocidas) y el caso 3 (manejo único de la UI) tienen mejores patrones (evite el error esperado o el manejo manual de errores en la UI).

Incluso el caso 2 puede ser reemplazado por mejores patrones, por ejemplo, los ámbitos de transacción (que using bloques que revierten cualquier transacción no comprometida durante el bloque) dificultan que los desarrolladores obtengan el patrón de mejores prácticas incorrecto.

Por ejemplo, supongamos que tiene una aplicación ASP.Net a gran escala. El registro de errores puede ser a través de ELMAH , la visualización de errores puede ser un YSoD informativo a nivel local y un buen mensaje localizado en producción. Las conexiones a la base de datos pueden ser a través de scopes de transacción y using bloques. No necesita un solo bloque try-catch .

TL; DR: la mejor práctica es en realidad no usar bloques try-catch en absoluto.

Una excepción es un error de locking .

Antes que nada, la mejor práctica debería ser no lanzar excepciones para ningún tipo de error, a menos que sea un error de locking .

Si el error es de locking , entonces lanza la excepción. Una vez que la excepción ya ha sido lanzada, no hay necesidad de ocultarla porque es excepcional; informe al usuario al respecto (debe volver a formatear la excepción completa a algo útil para el usuario en la interfaz de usuario).

Su trabajo como desarrollador de software es esforzarse para prevenir un caso excepcional donde algún parámetro o situación de tiempo de ejecución puede terminar en una excepción. Es decir, las excepciones no deben silenciarse, pero deben evitarse .

Por ejemplo, si sabe que alguna entrada entera podría tener un formato no válido, use int.TryParse lugar de int.Parse . Hay muchos casos en los que puedes hacer esto en lugar de solo decir “si falla, simplemente lanza una excepción”.

Lanzar excepciones es costoso.

Si, después de todo, se lanza una excepción, en lugar de escribir la excepción en el registro una vez que se ha lanzado, una de las mejores prácticas es atraparla en un manejador de excepciones de primera oportunidad . Por ejemplo:

  • ASP.NET: Global.asax Application_Error
  • Otros: evento AppDomain.FirstChanceException .

Mi opinión es que los try / catch locales son más adecuados para manejar casos especiales en los que puedes traducir una excepción a otra, o cuando quieres “silenciarla” para un caso muy, muy, muy, muy especial (un error de la biblioteca lanzando una excepción no relacionada que necesita silenciar para resolver todo el error).

Para el rest de los casos:

  • Intenta evitar excepciones
  • Si esto no es posible: manejadores de excepciones de primera oportunidad.
  • O use un aspecto PostSharp (AOP).

Respondiendo a @thewhiteambit sobre algún comentario …

@thewhiteambit dijo:

Las excepciones no son errores fatales, son excepciones. A veces ni siquiera son Errores, pero considerarlos como Errores Fatales es completamente falso, es decir, entender qué son las Excepciones.

Antes que nada, ¿cómo una excepción no puede ser siquiera un error?

  • Sin conexión de base de datos => excepción.
  • Formato de cadena inválido para analizar con algún tipo => excepción
  • Tratar de analizar JSON y mientras la entrada no es en realidad JSON => excepción
  • Argumento null mientras se esperaba el objeto => excepción
  • Algunas bibliotecas tienen un error => arroja una excepción inesperada
  • Hay una conexión de socket y se desconecta. Luego intenta enviar un mensaje => excepción

Podríamos enumerar 1k casos de cuando se lanza una excepción, y después de todo, cualquiera de los posibles casos será un error .

Una excepción es un error, porque al final del día es un objeto que recostack información de diagnóstico: tiene un mensaje y sucede cuando algo sale mal.

Nadie lanzaría una excepción cuando no hay un caso excepcional. Las excepciones deben ser errores de locking porque una vez lanzadas, si no intenta caer en el uso de try / catch y excepciones para implementar el flujo de control , significan que su aplicación / servicio detendrá la operación que entró en un caso excepcional .

Además, sugiero a todos que comprueben el paradigma fallido publicado por Martin Fowler (y escrito por Jim Shore) . Así es como siempre entendí cómo manejar excepciones, incluso antes de llegar a este documento hace algún tiempo.

[…] considérelos Fatal-Errors es completamente falso entender cuáles son las excepciones.

Por lo general, las excepciones reducen el flujo de operación y se manejan para convertirlas en errores comprensibles para los humanos. Por lo tanto, parece que una excepción es en realidad un mejor paradigma para manejar casos de error y trabajar en ellos para evitar un locking completo de la aplicación / servicio y notificar al usuario / consumidor que algo salió mal.

Más respuestas sobre preocupaciones de @thewhiteambit

Por ejemplo, en el caso de una conexión de base de datos faltante, el progtwig excepcionalmente podría continuar escribiendo en un archivo local y enviar los cambios a la base de datos una vez que esté disponible nuevamente. Se podría intentar volver a analizar su conversión inválida de cadena a número con la interpretación local del lenguaje en Exception, al igual que cuando intenta utilizar el idioma inglés predeterminado para Parse (“1,5”) falla y lo intenta con la interpretación alemana nuevamente, que es completamente bien porque usamos la coma en lugar del punto como separador. Usted ve que estas Excepciones ni siquiera deben estar bloqueando, solo necesitan algún manejo de Excepción.

  1. Si su aplicación puede funcionar sin conexión sin datos persistentes en la base de datos, no debe usar excepciones , ya que la implementación del flujo de control utilizando try/catch se considera un antipatrón. El trabajo fuera de línea es un posible caso de uso, por lo que implementa un flujo de control para verificar si la base de datos es accesible o no, no espera hasta que no esté accesible .

  2. El análisis también es un caso esperado ( no CASO EXCEPCIONAL ). Si espera esto, ¡no use excepciones para controlar el flujo! . ¡Obtienes algunos metadatos del usuario para saber cuál es su cultura y usas formateadores para esto! .NET también admite este y otros entornos, y una excepción porque se debe evitar el formato de números si espera un uso específico de su aplicación / servicio cultural .

Una excepción no controlada generalmente se convierte en un error, pero las excepciones en sí mismas no son codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

Este artículo es solo una opinión o un punto de vista del autor.

Dado que Wikipedia también puede ser solo la opinión de los autores del articulo, no diría que es el dogma , pero verifique qué artículo de Codificación por excepción dice en algún lugar de algún párrafo:

[…] Usar estas excepciones para manejar errores específicos que surgen para continuar el progtwig se llama encoding por excepción. Este antipatrón puede degradar rápidamente el software en rendimiento y capacidad de mantenimiento.

También dice en alguna parte:

Uso de excepción incorrecta

A menudo, la encoding por excepción puede ocasionar problemas adicionales en el software con el uso de excepciones incorrectas. Además de utilizar el manejo de excepciones para un problema único, el uso de excepciones incorrectas lleva esto más lejos al ejecutar el código incluso después de que se produce la excepción. Este método de progtwigción deficiente se asemeja al método goto en muchos lenguajes de software, pero solo ocurre después de que se detecta un problema en el software.

Honestamente, creo que el software no se puede desarrollar, no tomando en serio los casos de uso. Si sabes eso …

  • Su base de datos puede estar fuera de línea …
  • Algunos archivos pueden ser bloqueados …
  • Es posible que algunos formatos no sean compatibles …
  • Algunas validaciones de dominio pueden fallar …
  • Tu aplicación debería funcionar en modo fuera de línea …
  • cualquiera que sea el caso de uso

no usarás excepciones para eso . Respaldaría estos casos de uso utilizando un flujo de control regular.

Y si algún caso de uso inesperado no está cubierto, su código fallará rápidamente, porque emitirá una excepción . Correcto, porque una excepción es un caso excepcional .

Por otro lado, y finalmente, algunas veces cubre casos excepcionales arrojando excepciones esperadas , pero no los arroja para implementar el flujo de control. Lo haces porque quieres notificar a las capas superiores que no admites algún caso de uso o que tu código no funciona con algunos argumentos dados o datos / propiedades del entorno.

La única ocasión en la que debería preocupar a los usuarios sobre algo que sucedió en el código es si hay algo que puedan o necesiten hacer para evitar el problema. Si pueden cambiar los datos en un formulario, presione un botón o cambie la configuración de una aplicación para evitar el problema, hágales saber. Pero las advertencias o los errores que el usuario no tiene la capacidad de evitar solo los hace perder confianza en su producto.

Las excepciones y los registros son para usted, el desarrollador, no su usuario final. Comprender lo que se debe hacer cuando se detecta cada excepción es mucho mejor que simplemente aplicar alguna regla de oro o confiar en una red de seguridad para toda la aplicación.

La encoding sin sentido es el ÚNICO tipo de encoding errónea. El hecho de que sientas que hay algo mejor que se puede hacer en esas situaciones muestra que estás interesado en una buena encoding, pero evita el bash de estampar alguna regla genérica en estas situaciones y comprender el motivo por el cual arrojar algo y qué puedes hacer para recuperarte de eso.

Sé que esta es una pregunta antigua, pero aquí nadie mencionó el artículo de MSDN, y fue el documento el que realmente lo resolvió, MSDN tiene un documento muy bueno sobre esto, debe detectar excepciones cuando se cumplen las siguientes condiciones:

  • Tiene una buena comprensión de por qué podría lanzarse la excepción y puede implementar una recuperación específica, como solicitar al usuario que ingrese un nuevo nombre de archivo cuando capture un objeto FileNotFoundException.

  • Puede crear y lanzar una nueva excepción más específica.

 int GetInt(int[] array, int index) { try { return array[index]; } catch(System.IndexOutOfRangeException e) { throw new System.ArgumentOutOfRangeException( "Parameter index is out of range."); } } 
  • Desea manejar parcialmente una excepción antes de pasarla para un manejo adicional. En el siguiente ejemplo, se usa un bloque catch para agregar una entrada a un registro de error antes de volver a lanzar la excepción.
  try { // Try to access a resource. } catch (System.UnauthorizedAccessException e) { // Call a custom error logging procedure. LogError(e); // Re-throw the error. throw; } 

Sugiero leer toda la sección ” Excepciones y manejo de excepciones ” y también las Mejores prácticas para excepciones .

El mejor enfoque es el segundo (en el que especifica el tipo de excepción). La ventaja de esto es que usted sabe que este tipo de excepción puede ocurrir en su código. Usted está manejando este tipo de excepción y puede reanudar. Si surgió alguna otra excepción, eso significa que algo está mal y te ayudará a encontrar errores en tu código. La aplicación eventualmente se bloqueará, pero llegará a saber que hay algo que omitió (error) que necesita ser reparado.

El segundo enfoque es bueno.

Si no desea mostrar el error y confundir al usuario de la aplicación mostrando la excepción de tiempo de ejecución (es decir, error) que no está relacionada con ellos, simplemente registre el error y el equipo técnico puede buscar el problema y resolverlo.

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception);//it will write the or log the error in a text file } 

Le recomiendo que busque el segundo enfoque para toda su aplicación.

Dejar el bloque catch en blanco es lo peor que se puede hacer. Si hay un error, la mejor manera de manejarlo es:

  1. Inicie sesión en el archivo \ base de datos, etc.
  2. Intenta arreglarlo sobre la marcha (tal vez intentando una forma alternativa de hacer esa operación)
  3. Si no podemos solucionarlo, notifique al usuario que hay algún error y, por supuesto, interrumpa la operación

Para mí, manejar la excepción se puede ver como una regla comercial. Obviamente, el primer enfoque es inaceptable. El segundo es mejor y puede ser 100% correcto si lo dice el contexto. Ahora, por ejemplo, está desarrollando un complemento de Outlook. Si agrega una excepción no controlada, el usuario de Outlook ahora podría saberlo, ya que la perspectiva no se destruirá a sí misma debido a un error en un complemento. Y tienes dificultades para descubrir qué salió mal. Por lo tanto, el segundo enfoque en este caso, para mí, es correcto. Además de registrar la excepción, puede decidir mostrar un mensaje de error al usuario; lo considero una regla comercial.

La mejor práctica es arrojar una excepción cuando se produce el error. Porque se ha producido un error y no debe ocultarse.

Pero en la vida real puede tener varias situaciones cuando quiere ocultar esto

  1. Confía en el componente de un tercero y desea continuar con el progtwig en caso de error.
  2. Tiene un caso comercial que necesita continuar en caso de error

Debes considerar estas Pautas de diseño para excepciones

  • Excepción de lanzamiento
  • Uso de tipos de excepciones estándar
  • Excepciones y rendimiento

https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions

La catch sin argumentos es simplemente comer la excepción y no sirve de nada. ¿Qué pasa si ocurre un error fatal? No hay forma de saber qué pasó si usas catch sin argumento.

Una statement de captura debería capturar excepciones más específicas como FileNotFoundException y, al final , debería capturar Exception que capturaría cualquier otra excepción y las registraría.

A veces debe tratar excepciones que no dicen nada a los usuarios.

Mi manera es:

  • Para capturar excepciones no encausadas en el nivel de la aplicación (es decir, en global.asax) para excepciones críticas (la aplicación no puede ser útil). Estas excepciones no estoy entendiendo el lugar. Simplemente inicie sesión en el nivel de la aplicación y deje que el sistema haga su trabajo.
  • Capture “en el lugar” y muestre información útil al usuario (ingresó el número incorrecto, no puede analizar).
  • Observe el lugar y no haga nada en problemas marginales como “Verificaré la información de actualización en segundo plano, pero el servicio no se está ejecutando”.

Definitivamente no tiene que ser la mejor práctica. 😉

Con excepciones, bash lo siguiente:

Primero, capturo tipos especiales de excepciones como división por cero, operaciones IO, etc. y escribo el código de acuerdo con eso. Por ejemplo, una división por cero, dependiendo de la procedencia de los valores, podría alertar al usuario (por ejemplo, una calculadora simple que en un cálculo intermedio (no los argumentos) llega en una división por cero) o tratar en silencio esa excepción, registrando y continuar procesando.

Luego trato de atrapar las excepciones restantes y registrarlas. De ser posible, permita la ejecución del código, de lo contrario, advierta al usuario que ocurrió un error y solicite que envíen un informe de error.

En código, algo como esto:

 try{ //Some code here } catch(DivideByZeroException dz){ AlerUserDivideByZerohappened(); } catch(Exception e){ treatGeneralException(e); } finally{ //if a IO operation here i close the hanging handlers for example }