Actualmente estoy en el proceso de escribir mi primera aplicación de Windows Forms. He leído algunos libros de C # ahora, así que tengo una comprensión relativamente buena de qué características del lenguaje C # tiene que lidiar con las excepciones. Sin embargo, todos son bastante teóricos, por lo que todavía no tengo una idea de cómo traducir los conceptos básicos en un buen modelo de manejo de excepciones en mi aplicación.
¿Alguien quisiera compartir alguna perla de sabiduría sobre el tema? Publique cualquier error común que haya visto cometer a novatos como yo, y cualquier consejo general sobre el manejo de excepciones de una manera que haga que mi aplicación sea más estable y sólida.
Las principales cosas que estoy tratando de resolver actualmente son:
- ¿Cuándo debería volver a lanzar una excepción?
- ¿Debería intentar tener un mecanismo central de manejo de errores de algún tipo?
- ¿El manejo de las excepciones que podrían producirse tiene un impacto en el rendimiento en comparación con las pruebas preventivas como si existe un archivo en el disco?
- ¿Debería incluirse todo el código ejecutable en bloques try-catch-finalmente?
- ¿Hay ocasiones en las que un bloque de captura vacío podría ser aceptable?
¡Todos los consejos recibidos con gratitud!
fuente
ApplicationException
para aquellos casos de falla que la aplicación debería poder diferenciar y manejar razonablemente.Hay un excelente artículo de CodeProject sobre código aquí . Aquí hay un par de aspectos destacados:
fuente
Tenga en cuenta que Windows Forms tiene su propio mecanismo de manejo de excepciones. Si se hace clic en un botón en el formulario y su controlador genera una excepción que no se detecta en el controlador, Windows Forms mostrará su propio cuadro de diálogo de excepción no controlada.
Para evitar que se muestre el cuadro de diálogo de excepción no controlada y detectar tales excepciones para el registro y / o para proporcionar su propio cuadro de diálogo de error, puede adjuntar el evento Application.ThreadException antes de la llamada a Application.Run () en su método Main ().
fuente
Todos los consejos publicados aquí hasta ahora son buenos y vale la pena seguirlos.
Una cosa que me gustaría ampliar es su pregunta "¿El manejo de las excepciones que pueden producirse tiene un impacto en el rendimiento en comparación con las pruebas preventivas de cosas como si existe un archivo en el disco?"
La ingenua regla general es que "los bloques de prueba / captura son caros". Eso no es realmente cierto. Probar no es caro. Es la captura, donde el sistema tiene que crear un objeto de excepción y cargarlo con el seguimiento de la pila, eso es caro. Hay muchos casos en los que la excepción es, bueno, lo suficientemente excepcional como para que esté perfectamente bien envolver el código en un bloque try / catch.
Por ejemplo, si está rellenando un Diccionario, esto:
suele ser más rápido que hacer esto:
para cada elemento que está agregando, porque la excepción solo se lanza cuando agrega una clave duplicada. (Las consultas agregadas de LINQ hacen esto).
En el ejemplo que diste, usaría try / catch casi sin pensar. Primero, el hecho de que el archivo exista cuando lo verifica no significa que va a existir cuando lo abra, por lo que realmente debería manejar la excepción de todos modos.
En segundo lugar, y creo que lo más importante, a menos que a) su proceso esté abriendo miles de archivos yb) las probabilidades de que un archivo que está tratando de abrir no exista no sean trivialmente bajas, el impacto en el rendimiento de crear la excepción no es algo que usted ' alguna vez lo vas a notar. En términos generales, cuando su programa intenta abrir un archivo, solo intenta abrir un archivo. Ese es un caso en el que escribir un código más seguro es casi seguro que será mejor que escribir el código más rápido posible.
fuente
dict[key] = value
que debería ser tan rápido si no más rápido ..Aquí hay algunas pautas que sigo
Fail-Fast: esta es más una pauta de generación de excepciones.Por cada suposición que haga y cada parámetro que ingrese en una función, haga una verificación para asegurarse de que está comenzando con los datos correctos y que las suposiciones están haciendo son correctos. Las verificaciones típicas incluyen, argumento no nulo, argumento en el rango esperado, etc.
Al volver a lanzar preservar el seguimiento de pila: esto simplemente se traduce en usar throw al volver a lanzar en lugar de lanzar una nueva Exception (). Alternativamente, si cree que puede agregar más información, envuelva la excepción original como una excepción interna. Pero si está detectando una excepción solo para registrarla, definitivamente use throw;
No detecte excepciones que no pueda manejar, así que no se preocupe por cosas como OutOfMemoryException porque si ocurren, no podrá hacer mucho de todos modos.
Enganche a los manejadores de excepciones globales y asegúrese de registrar tanta información como sea posible. Para winforms, enganche tanto el dominio de aplicación como los eventos de excepción no controlados del hilo.
El rendimiento solo debe tenerse en cuenta cuando haya analizado el código y haya visto que está causando un cuello de botella en el rendimiento; de forma predeterminada, optimice la legibilidad y el diseño. Entonces, sobre su pregunta original sobre la verificación de existencia del archivo, diría que depende, si puede hacer algo para que el archivo no esté allí, entonces sí, haga esa verificación de lo contrario si todo lo que va a hacer es lanzar una excepción si el archivo está no ahí, entonces no veo el punto.
Definitivamente hay ocasiones en las que se requieren bloques de captura vacíos, creo que las personas que dicen lo contrario no han trabajado en bases de código que han evolucionado en varias versiones. Pero deben comentarse y revisarse para asegurarse de que sean realmente necesarios. El ejemplo más típico es que los desarrolladores usan try / catch para convertir cadenas en números enteros en lugar de usar ParseInt ().
Si espera que la persona que llama a su código pueda manejar las condiciones de error, cree excepciones personalizadas que detallen cuál es la situación inesperada y brinden información relevante. De lo contrario, limítese a los tipos de excepción incorporados tanto como sea posible.
fuente
Application.ThreadException
evento cuando hizo referencia al evento "excepción de subproceso no controlado"?Me gusta la filosofía de no captar nada que no tenga la intención de manejar, sea lo que sea el manejo en mi contexto particular.
Odio cuando veo un código como:
He visto esto de vez en cuando y es bastante difícil encontrar problemas cuando alguien 'come' las excepciones. Un compañero de trabajo que tuve hace esto y tiende a terminar contribuyendo a un flujo constante de problemas.
Vuelvo a lanzar si hay algo que mi clase en particular necesita hacer en respuesta a una excepción, pero el problema debe resolverse sin embargo, se debe llamar al método donde sucedió.
Creo que el código debe escribirse de forma proactiva y que las excepciones deben ser para situaciones excepcionales, no para evitar las pruebas de condiciones.
fuente
Estoy a punto de salir, pero le daré un breve resumen sobre dónde usar el manejo de excepciones. Intentaré abordar sus otros puntos cuando regrese :)
*Dentro de lo razonable. No es necesario verificar si, digamos, un rayo cósmico golpeó sus datos y provocó que un par de bits se voltearan. Comprender lo que es "razonable" es una habilidad que adquiere un ingeniero. Es difícil de cuantificar, pero fácil de intuir. Es decir, puedo explicar fácilmente por qué utilizo un try / catch en cualquier caso particular, pero me cuesta imbuir a otro con este mismo conocimiento.
Por mi parte, tiendo a alejarme de las arquitecturas basadas en gran medida en excepciones. try / catch no tiene un impacto de rendimiento como tal, el hit se produce cuando se lanza la excepción y es posible que el código tenga que subir varios niveles de la pila de llamadas antes de que algo lo maneje.
fuente
La regla de oro que han tratado de cumplir es manejar la excepción lo más cerca posible de la fuente.
Si debe volver a lanzar una excepción, intente agregarla, volver a lanzar una FileNotFoundException no ayuda mucho, pero lanzar una ConfigurationFileNotFoundException permitirá que se capture y se actúe en algún lugar de la cadena.
Otra regla que trato de seguir es no usar try / catch como una forma de flujo de programa, así que verifico archivos / conexiones, me aseguro de que los objetos se hayan iniciado, etc., antes de usarlos. Try / catch debería ser para Excepciones, cosas que no puede controlar.
En cuanto a un bloque catch vacío, si está haciendo algo de importancia en el código que generó la excepción, debe volver a lanzar la excepción como mínimo. Si no hay consecuencias de que el código que lanzó la excepción no se ejecute, ¿por qué lo escribió en primer lugar?
fuente
puede capturar el evento ThreadException.
Seleccione un proyecto de aplicación de Windows en el Explorador de soluciones.
Abra el archivo Program.cs generado haciendo doble clic en él.
Agregue la siguiente línea de código en la parte superior del archivo de código:
En el método Main (), agregue lo siguiente como primera línea del método:
Agregue lo siguiente debajo del método Main ():
Agregue código para manejar la excepción no controlada dentro del controlador de eventos. Cualquier excepción que no se maneje en ningún otro lugar de la aplicación se maneja mediante el código anterior. Por lo general, este código debe registrar el error y mostrar un mensaje al usuario.
referencia: https://blogs.msmvps.com/deborahk/global-exception-handler-winforms/
fuente
Las excepciones son caras pero necesarias. No necesita envolver todo en una captura de prueba, pero debe asegurarse de que las excepciones siempre se detecten eventualmente. Gran parte dependerá de su diseño.
No vuelvas a lanzar si dejar que la excepción se eleve funcionará igual de bien. Nunca dejes que los errores pasen desapercibidos.
ejemplo:
Si DoStuff sale mal, querrás que salga bajo fianza de todos modos. La excepción se lanzará a main y verá el tren de eventos en el seguimiento de pila de ex.
fuente
En todas partes, pero con métodos de usuario final ... como controladores de clic de botón
Escribo un archivo de registro ... bastante fácil para una aplicación WinForm
No estoy seguro de esto, pero creo que es una buena práctica hacer excepciones ... Quiero decir, puede preguntar si existe un archivo y si no arroja una FileNotFoundException
yeap
Sí, digamos que quiere mostrar una fecha, pero no tiene idea de cómo se almacenó esa fecha (dd / mm / aaaa, mm / dd / aaaa, etc.), intente analizarla, pero si falla, continúe. . si es irrelevante para usted ... yo diría que sí, hay
fuente
Lo único que aprendí muy rápidamente fue encerrar absolutamente cada fragmento de código que interactúa con cualquier cosa fuera del flujo de mi programa (es decir, sistema de archivos, llamadas a bases de datos, entrada de usuario) con bloques try-catch. Try-catch puede generar un impacto en el rendimiento, pero generalmente en estos lugares de su código no se notará y se amortizará con seguridad.
He usado catch-blocks vacíos en lugares donde el usuario podría hacer algo que no es realmente "incorrecto", pero puede generar una excepción ... un ejemplo que me viene a la mente es en GridView si el usuario hace doble clic en el marcador de posición gris celda en la parte superior izquierda activará el evento CellDoubleClick, pero la celda no pertenece a una fila. En ese caso, no Realmente necesito publicar un mensaje, pero si no lo capta, arrojará un error de excepción no controlado al usuario.
fuente
Al volver a lanzar una excepción, la palabra clave se lanza sola. Esto lanzará la excepción detectada y aún podrá usar el seguimiento de la pila para ver de dónde vino.
hacer esto hará que el seguimiento de la pila termine en la declaración de captura. (evita esto)
fuente
En mi experiencia, he considerado apropiado detectar excepciones cuando sé que las voy a crear. Para los casos en que estoy en una aplicación web y estoy haciendo un Response.Redirect, sé que obtendré un System.ThreadAbortException. Como es intencional, solo tengo una captura para el tipo específico y me lo trago.
fuente
Estoy profundamente de acuerdo con la regla de:
La razón es que:
ForceAssert.AlwaysAssert es mi forma personal de Trace.Assert independientemente de si la macro DEBUG / TRACE está definida.
El ciclo de desarrollo tal vez: noté el feo diálogo de Assert o alguien más se quejó de él, luego vuelvo al código y descubro la razón para generar la excepción y decidir cómo procesarla.
De esta manera puedo anotar MI código en poco tiempo y protegerme de dominios desconocidos, pero siempre notando si sucedían cosas anormales, de esta manera el sistema se volvió seguro y más seguro.
Sé que muchos de ustedes no estarán de acuerdo conmigo porque un desarrollador debería conocer cada detalle de su código, francamente, también soy un purista en los viejos tiempos. Pero hoy en día aprendí que la política anterior es más pragmática.
Para el código de WinForms, una regla de oro que siempre obedezco es:
esto protegerá su interfaz de usuario siempre que se pueda utilizar.
Para el impacto en el rendimiento, la penalización del rendimiento solo ocurre cuando el código alcanza la captura, la ejecución del código de prueba sin que se produzca la excepción real no tiene un efecto significativo.
La excepción debe ocurrir con pocas posibilidades, de lo contrario, no son excepciones.
fuente
Tienes que pensar en el usuario. El bloqueo de la aplicación es el últimocosa que el usuario quiere. Por lo tanto, cualquier operación que pueda fallar debe tener un bloque try catch en el nivel de la interfaz de usuario. No es necesario usar try catch en todos los métodos, pero cada vez que el usuario hace algo, debe poder manejar excepciones genéricas. Eso de ninguna manera te libera de revisar todo para evitar excepciones en el primer caso, pero no existe una aplicación compleja sin errores y el sistema operativo puede agregar fácilmente problemas inesperados, por lo que debes anticipar lo inesperado y asegurarte si un usuario quiere usar uno. operación no habrá pérdida de datos porque la aplicación se bloquea. No hay necesidad de dejar que su aplicación se bloquee, si detecta excepciones, nunca estará en un estado indeterminado y el usuario SIEMPRE se verá afectado por un bloqueo. Incluso si la excepción está en el nivel más alto, no fallar significa que el usuario puede reproducir rápidamente la excepción o al menos registrar el mensaje de error y, por lo tanto, ayudarlo enormemente a solucionar el problema. Ciertamente, mucho más que recibir un simple mensaje de error y luego ver solo el cuadro de diálogo de error de Windows o algo así.
Es por eso que NUNCA debe ser engreído y pensar que su aplicación no tiene errores, eso no está garantizado. Y es un esfuerzo muy pequeño envolver algunos bloques try catch sobre el código apropiado y mostrar un mensaje de error / registrar el error.
Como usuario, ciertamente me cabreo seriamente cada vez que una aplicación de Office o un navegador falla. Si la excepción es tan alta que la aplicación no puede continuar, es mejor mostrar ese mensaje y decirle al usuario qué hacer (reiniciar, corregir algunas configuraciones del sistema operativo, informar el error, etc.) que simplemente fallar y eso es todo.
fuente