¿Capturar excepciones generales es realmente algo malo?

58

Normalmente estoy de acuerdo con la mayoría de las advertencias de análisis de código, y trato de cumplirlas. Sin embargo, estoy teniendo más dificultades con este:

CA1031: no capturar tipos de excepción general

Entiendo la razón de esta regla. Pero, en la práctica, si quiero tomar la misma acción independientemente de la excepción lanzada, ¿por qué manejaría cada una específicamente? Además, si manejo excepciones específicas, ¿qué sucede si el código al que llamo cambia para lanzar una nueva excepción en el futuro? Ahora tengo que cambiar mi código para manejar esa nueva excepción. Mientras que si simplemente capturo Exceptionmi código no tiene que cambiar.

Por ejemplo, si Foo llama a Bar, y Foo necesita detener el procesamiento, independientemente del tipo de excepción lanzada por Bar, ¿hay alguna ventaja en ser específico sobre el tipo de excepción que estoy captando?

Quizás un mejor ejemplo:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Si no detecta una excepción general aquí, entonces debe detectar todo tipo de excepción posible. Patrick tiene un buen punto que OutOfMemoryExceptionno debería tratarse de esta manera. Entonces, ¿qué pasa si quiero ignorar todas las excepciones pero OutOfMemoryException?

Bob Horn
fuente
12
¿Qué hay de OutOfMemoryException? ¿El mismo código de manejo que todo lo demás?
Patrick
@Patrick Buen punto. Agregué un nuevo ejemplo en mi pregunta para cubrir su pregunta.
Bob Horn
1
Capturar excepciones generales es tan malo como hacer declaraciones generales sobre lo que todos deberían hacer. En general, no existe un enfoque único para todo.
44
@Patrick sería OutOfMemoryError, que está separado del Exceptionárbol de herencia por esa misma razón
Darkhogg
¿Qué pasa con las excepciones de teclado, como Ctrl-Alt-Del?
Mawg

Respuestas:

33

Estas reglas son generalmente una buena idea y, por lo tanto, deben seguirse.

Pero recuerde que estas son reglas genéricas. No cubren todas las situaciones. Cubren las situaciones más comunes. Si tiene una situación específica y puede argumentar que su técnica es mejor (y debería poder escribir un comentario en el código para articular su argumento para hacerlo), hágalo (y luego haga que lo revisen por pares).

En el lado opuesto de la discusión.

No veo su ejemplo anterior como una buena situación para hacerlo. Si el sistema de registro está fallando (presumiblemente registrando alguna otra excepción), entonces probablemente no quiera que la aplicación continúe. Salga e imprima la excepción a la salida para que el usuario pueda ver lo que sucedió.

Martin York
fuente
2
Concurrir. Por lo menos , ahora que el registro regular está fuera de línea, realice algún tipo de provisión que brinde algún tipo de salida de depuración, a STDERR, si nada más.
Shadur
66
Los voté a ambos, pero creo que piensan como desarrolladores, no como usuarios. A un usuario generalmente no le importa si está ocurriendo el registro, siempre que la aplicación sea utilizable.
Mawg
1
Interesante, ya que log4net explícitamente no quiere detener la aplicación solo porque falla el registro. Y creo que depende de lo que estés registrando; auditorías de seguridad, claro, matan el proceso. ¿Pero depurar registros? No es tan importante
Andy
1
@Andy: Sí, todo depende de lo que estés haciendo.
Martin York
2
¿Por qué no los entiendes? ¿Y no deberías esforzarte por hacerlo?
Mawg
31

Sí, atrapar excepciones generales es algo malo. Una excepción generalmente significa que el programa no puede hacer lo que usted le pidió.

Hay algunos tipos de excepciones que puede manejar:

  • Excepciones fatales : falta de memoria, desbordamiento de pila, etc. Una fuerza sobrenatural acaba de estropear su universo y el proceso ya está muriendo. No puedes mejorar la situación, así que solo ríndete
  • Se produce una excepción porque hay errores en el código que está utilizando : no intente solucionarlos, sino que solucione el origen del problema. No use excepciones para el control de flujo
  • Situaciones excepcionales : solo maneje excepciones en estos casos. Aquí podemos incluir: cable de red desconectado, conexión a Internet deja de funcionar, falta de permisos, etc.

Ah, y como regla general: si no sabe qué hacer con una excepción si la detecta, es mejor simplemente fallar rápidamente (pasar la excepción a la persona que llama y dejar que se ocupe)

Victor Hurdugaci
fuente
1
¿Qué pasa con el caso específico en la pregunta? Si hay un error en el código de registro, probablemente esté bien ignorar la excepción, porque el código que realmente importa no debería verse afectado por eso.
svick
44
¿Por qué no corregir el error en el código de registro en su lugar?
Victor Hurdugaci
3
Victor, probablemente no sea un error. Si el disco en el que vive el registro se queda sin espacio, ¿es eso un error en el código de registro?
Carson63000
44
@ Carson63000 - bueno, sí. Los registros no se escriben, por lo tanto: error. Implementar troncos rotativos de tamaño fijo.
desvío
55
Esta es la mejor respuesta de la OMI, pero le falta un aspecto crucial: la seguridad . Hay algunas excepciones que ocurren como resultado de que los chicos malos intentan acelerar su programa. Si se produce una excepción que no puede manejar, entonces ya no necesariamente puede confiar en ese código. A nadie le gusta escuchar que escribieron el código que deja a los malos en.
riwalk
15

El bucle externo superior debe tener uno de estos para imprimir todo lo que pueda, y luego morir de una muerte horrible, violenta y ruidosa (ya que esto no debería suceder y alguien necesita escuchar).

De lo contrario, generalmente debe tener mucho cuidado, ya que es muy probable que no haya anticipado todo lo que podría suceder en este lugar y, por lo tanto, lo más probable es que no lo trate correctamente. Sea lo más específico posible para que solo atrape a los que sabe que sucederán, y deje que los que no se hayan visto antes lleguen a la muerte ruidosa mencionada anteriormente.


fuente
1
Estoy de acuerdo con esto en teoría. Pero, ¿qué pasa con mi ejemplo de registro anterior? ¿Qué sucede si simplemente desea ignorar las excepciones de registro y continuar? ¿Debería tener que atrapar todas las excepciones que podrían lanzarse y manejar cada una de ellas mientras deja que surja la excepción más grave?
Bob Horn el
66
@BobHorn: Dos formas de ver eso. Sí, puede decir que el registro no es particularmente importante y si falla, no debería detener su aplicación. Y ese es un punto bastante justo. Pero, ¿qué pasa si su aplicación no puede iniciar sesión durante cinco meses y no tenía idea? He visto que esto suceda. Y luego necesitábamos los registros. Desastre. Sugeriría que hacer todo lo que se te ocurra para detener el error de registro es mejor que ignorarlo cuando lo hace. (Pero no vas a llegar a un gran consenso sobre eso.)
pdr
@pdr En mi ejemplo de registro, normalmente enviaría una alerta por correo electrónico. O bien, tenemos sistemas para monitorear registros, y si un registro no se está rellenando activamente, nuestro equipo de soporte recibirá una alerta. Entonces nos daríamos cuenta del problema de registro de otras maneras.
Bob Horn el
@BobHorn su ejemplo de registro es bastante artificial: la mayoría de los marcos de registro que conozco, hacen todo lo posible para no lanzar ninguna excepción y no se invocarán de esa manera (ya que haría inútil cualquier "declaración de registro en la línea X en el archivo Y" )
@pdr He planteado la pregunta en la lista de inicio de sesión (Java) sobre cómo hacer que el marco de registro detenga la aplicación si falla la configuración de registro, simplemente porque es TAN importante que tengamos los registros, y cualquier falla silenciosa es inaceptable. Una buena solución aún está pendiente.
9

No es que sea malo, es solo que las capturas específicas son mejores. Cuando eres específico, significa que realmente entiendes, más concretamente, lo que está haciendo tu aplicación y tienes más control sobre ella. En general, si te encuentras con una situación en la que solo captas una excepción, la registras y continúas, probablemente hay algunas cosas malas que están sucediendo de todos modos. Si está captando específicamente las excepciones que sabe que puede arrojar un bloque de código o método, entonces hay una mayor probabilidad de que realmente pueda recuperarse en lugar de simplemente iniciar sesión y esperar lo mejor.

Ryan Hayes
fuente
5

Las dos posibilidades no son mutuamente excluyentes.

En una situación ideal, detectaría todos los tipos posibles de excepción que podría generar su método, los manejaría por excepción y, al final, agregaría una catchcláusula general para detectar cualquier excepción futura o desconocida. De esta manera obtienes lo mejor de ambos mundos.

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

Tenga en cuenta que para detectar las excepciones más específicas, debe ponerlas primero.

Editar: por las razones indicadas en los comentarios, se agregó una nueva versión en la última captura

Rotem
fuente
2
Espere. ¿Por qué querrías registrar una excepción desconocida en la consola y continuar? Si es una excepción que ni siquiera sabía que el código podría arrojar, probablemente esté hablando de una falla del sistema. Continuar en este escenario es probablemente una muy mala idea.
pdr
1
@pdr Seguramente te das cuenta de que es un ejemplo artificial. El punto era demostrar la captura de excepciones específicas y generales, no cómo manejarlas.
Rotem
@Rotem Creo que tienes una buena respuesta aquí. Pero pdr tiene un punto. El código, tal como está, hace que parezca que atrapar y continuar está bien. Algunos principiantes, al ver este ejemplo, podrían pensar que es un buen enfoque.
Bob Horn
Contribuido o no, hace que su respuesta sea incorrecta. Tan pronto como comience a detectar excepciones como Memoria insuficiente y Disco lleno y simplemente las trague, está demostrando por qué capturar excepciones generales es realmente malo.
pdr
1
Si detecta una excepción general solo para iniciar sesión, debe volver a lanzarla después de iniciar sesión.
Victor Hurdugaci
5

He estado reflexionando sobre lo mismo recientemente, y mi conclusión tentativa es que la mera pregunta surge porque la jerarquía de excepciones .NET está gravemente desordenada.

Tomemos, por ejemplo, el humilde ArgumentNullExceptionque podría ser un candidato razonable para una excepción que no desea detectar, porque tiende a indicar un error en el código en lugar de un error legítimo de tiempo de ejecución. Ah, sí. También lo hace NullReferenceException, excepto que NullReferenceExceptionse deriva SystemExceptiondirectamente, por lo que no hay un depósito en el que pueda colocar todos los "errores lógicos" para atrapar (o no atrapar).

Luego está el problema principal de IMNSHO de haber SEHExceptionderivado (vía ExternalException) SystemExceptiony, por lo tanto, hacerlo "normal".SystemException Cuando obtienes unSEHException, quieres escribir un volcado y terminar lo más rápido posible, y comenzando con .NET 4 al menos algunos Las excepciones SEH se consideran excepciones estatales corruptas que no se detectarán. Una cosa buena, y un hecho que hace que laCA1031regla sea aún más inútil, porque ahora tu perezosocatch (Exception)no atrapará estos peores tipos de todos modos.

Entonces, parece que las otras cosas de Framework derivan de manera bastante inconsistente, ya sea Exceptiondirectamente o por medio SystemException, haciendo cualquier intento de agrupar las cláusulas de captura por algo como discutible de gravedad.

Hay una pieza de C#fama del Sr. Lippert , llamada Vexing Exception , donde expone una categorización útil de las excepciones: se podría argumentar que solo quiere atrapar las "exógenas", excepto ... C#el lenguaje y el diseño de .NET Las excepciones marco hacen imposible "atrapar solo a los exógenos" de manera sucinta. (y, por ejemplo, una OutOfMemoryExceptionpuede muy bien ser un error recuperable totalmente normal para una API que tiene que asignar memorias intermedias que son de alguna manera grande)

En resumen, para mí es que la forma en que C#funcionan los bloques catch y la forma en que se diseña la jerarquía de excepciones de Framework, la regla CA1031es más que totalmente inútil . Se pretende que ayuda con la raíz del problema de "no tragar excepciones", pero tragar excepciones tiene cero que ver con lo que captura, pero con lo que se hace:

Hay al menos 4 formas de manejar legítimamente un atrapado Exception, y CA1031solo parece manejar superficialmente una de ellas (es decir, el caso de relanzamiento).


Como nota al margen, hay una función C # 6 llamada Filtros de excepción que volverá a ser un CA1031poco más válida desde entonces, podrá filtrar adecuadamente, correctamente, las excepciones que desea capturar, y hay menos razones para escribir un filtro sin filtrar catch (Exception).

Martin Ba
fuente
1
Un problema importante OutOfMemoryExceptiones que no hay una buena manera de que el código pueda estar seguro de que "solo" indica el fallo de una asignación particular que uno estaba preparado para fallar. Es posible que se haya lanzado alguna otra excepción más grave, y se haya OutOfMemoryExceptionproducido durante el desenrollado de la pila de esa otra excepción. Puede que Java haya llegado tarde a la fiesta con su "prueba con recursos", pero maneja las excepciones durante el desenrollado de la pila un poco mejor que .NET.
supercat
1
@supercat: lo suficientemente cierto, pero eso es más o menos cierto para cualquier otra excepción general del sistema, cuando las cosas salen mal en cualquier bloque finalmente.
Martin Ba
1
Eso es cierto, pero uno puede (y generalmente debería) proteger los finallybloques contra excepciones que uno puede esperar de manera realista que ocurran de tal manera que las excepciones originales y nuevas se registren. Desafortunadamente, hacer eso a menudo requerirá la asignación de memoria, lo que podría causar fallas propias.
supercat
4

El manejo de excepciones de Pokémon (¡tengo que atraparlos a todos!) Ciertamente no siempre es malo. Cuando expones un método a un cliente, especialmente a un usuario final, a menudo es mejor atrapar cualquier cosa y todo en lugar de que tu aplicación se bloquee y se queme.

En general, deben evitarse donde pueda. A menos que pueda tomar medidas específicas según el tipo de excepción, es mejor no manejarla y permitir que la excepción brote en lugar de tragarse la excepción o manejarla incorrectamente.

Echa un vistazo a esta respuesta SO para leer más.

Tom Squires
fuente
1
Esta situación (cuando un desarrollador debe ser un Entrenador Pokemon) aparece cuando usted mismo hace un software completo: creó las capas, las conexiones de la base de datos y la GUI del usuario. Su aplicación debe recuperarse de situaciones como la pérdida de conectividad, la carpeta del usuario eliminada, los datos dañados, etc. A los usuarios finales no les gustan las aplicaciones que se bloquean y se queman. ¡Les gustan las aplicaciones que pueden funcionar en una computadora que explota y explota en llamas!
Broken_Window
No había pensado en esto hasta ahora, pero en Pokémon, generalmente se supone que 'atraparlos a todos' quiere exactamente uno de cada uno, y tiene que manejar atraparlo específicamente, que es lo opuesto al uso común de esta frase entre programadores.
Magus
2
@Magus: con un método como LoadDocument(), es esencialmente imposible identificar todas las cosas que podrían salir mal, pero el 99% de las excepciones que podrían lanzarse simplemente significarán "No fue posible interpretar el contenido de un archivo con el nombre dado como un documento; trátelo ". Si alguien intenta abrir algo que no es un archivo de documento válido, eso no debería bloquear la aplicación y eliminar otros documentos abiertos. El manejo de errores de Pokémon en estos casos es feo, pero no conozco ninguna buena alternativa.
supercat
@supercat: solo estaba haciendo un punto lingüístico. Sin embargo, no creo que el contenido de un archivo no válido parezca algo que deba generar más de un tipo de excepción.
Magus
1
@ Magus: Puede arrojar todo tipo de excepciones, ese es el problema. A menudo es muy difícil anticipar todos los tipos de excepciones que se pueden generar como consecuencia de datos no válidos en un archivo. Si no se utiliza el manejo de PokeMon, se corre el riesgo de que una aplicación muera porque, por ejemplo, el archivo que se estaba cargando contenía una cadena de dígitos excepcionalmente larga en un lugar que requería un número entero de 32 bits con formato decimal, y se produjo un desbordamiento numérico en La lógica de análisis.
supercat
1

Capturar excepciones generales es malo porque deja su programa en un estado indefinido. No sabes dónde salió mal, así que no sabes qué ha hecho o qué no ha hecho realmente tu programa.

Donde permitiría atrapar todo es al cerrar un programa. Siempre y cuando puedas limpiarlo bien. Nada tan molesto como un programa que cierra, que solo arroja un cuadro de diálogo de error que no hace nada más que sentarse allí, no desaparecer y evitar que su computadora se cierre.

En un entorno distribuido, su método de registro podría ser contraproducente: detectar una excepción general podría significar que su programa aún mantiene un bloqueo en el archivo de registro evitando que otros usuarios realicen registros.

Pieter B
fuente
0

Entiendo la razón de esta regla. Pero, en la práctica, si quiero tomar la misma acción independientemente de la excepción lanzada, ¿por qué manejaría cada una específicamente?

Como han dicho otros, es realmente difícil (si no imposible) imaginar alguna acción que quieras tomar independientemente de la excepción lanzada. Solo un ejemplo son situaciones en las que el estado del programa se ha dañado y cualquier procesamiento posterior puede generar problemas (esta es la razón subyacente Environment.FailFast).

Además, si manejo excepciones específicas, ¿qué sucede si el código al que llamo cambia para lanzar una nueva excepción en el futuro? Ahora tengo que cambiar mi código para manejar esa nueva excepción. Mientras que si simplemente capturo Exception, mi código no tiene que cambiar.

Para el código de pasatiempo está bien captarlo Exception, pero para el código de nivel profesional, la introducción de un nuevo tipo de excepción debe tratarse con el mismo respeto que un cambio en la firma del método, es decir, considerarse un cambio de última hora. Si se suscribe a este punto de vista, es inmediatamente obvio que volver a cambiar (o verificar) el código del cliente es el único curso de acción correcto.

Por ejemplo, si Foo llama a Bar, y Foo necesita detener el procesamiento, independientemente del tipo de excepción lanzada por Bar, ¿hay alguna ventaja en ser específico sobre el tipo de excepción que estoy captando?

Claro, porque no capturará solo las excepciones lanzadas por Bar. También habrá excepciones que los clientes de Bar o incluso el tiempo de ejecución podrían lanzar durante el tiempo que Barestá en la pila de llamadas. Un bien escrito Bardebe definir su propio tipo de excepción si es necesario para que las personas que llaman puedan capturar específicamente las excepciones emitidas por sí mismo.

Entonces, ¿qué pasa si quiero ignorar todas las excepciones excepto OutOfMemoryException?

En mi humilde opinión, esta es la forma incorrecta de pensar sobre el manejo de excepciones. Debería estar operando en listas blancas (capturar los tipos de excepción A y B), no en las listas negras (capturar todas las excepciones excepto X).

Jon
fuente
A menudo es posible especificar una acción que debe realizarse para todas las excepciones que indican que un intento de operación falló sin efectos secundarios y / o implicaciones adversas sobre el estado del sistema. Por ejemplo, si un usuario selecciona un archivo de documento para cargar, un programa pasa su nombre a un método de "crear objeto de documento con datos de un archivo de disco", y ese método falla por alguna razón en particular que la aplicación no había previsto (pero que cumple con los criterios anteriores), el comportamiento adecuado debe ser mostrar un mensaje "No se puede cargar el documento" en lugar de bloquear la aplicación.
supercat
Desafortunadamente, no hay un medio estándar por el cual una excepción puede indicar lo más importante que el código del cliente querría saber: si implica algo malo sobre el estado del sistema más allá de lo que estaría implicado por la incapacidad de realizar la operación solicitada. Por lo tanto, a pesar de que las situaciones en las que se deben manejar todas las excepciones son raras, hay muchas situaciones en las que muchas excepciones se deben manejar de la misma manera, y no hay ningún criterio que se cumpla con todas esas excepciones que también serían satisfechas por algunas raras que debe manejarse de manera diferente.
supercat
0

Tal vez . Hay excepciones para cada regla, y ninguna regla debe seguirse sin crítica. Su ejemplo podría ser uno de los casos en los que tiene sentido tragarse todas las excepciones. Por ejemplo, si desea agregar el rastreo a un sistema de producción crítico y quiere asegurarse absolutamente de que los cambios no interrumpan la tarea principal de la aplicación.

Sin embargo , debe pensar cuidadosamente sobre las posibles razones del fracaso antes de decidir ignorarlas en silencio. Por ejemplo, ¿qué pasa si la razón de la excepción es:

  • un error de programación en el método de registro que hace que siempre arroje una excepción particular
  • una ruta no válida al archivo de registro en la configuración
  • el disco está lleno

¿No desea que se le notifique de inmediato que este problema está presente para que pueda solucionarlo? Tragar la excepción significa que nunca se sabe que algo salió mal.

Algunos problemas (como que el disco está lleno) también pueden hacer que otras partes de la aplicación fallen, pero esta falla no se registra ahora, ¡así que nunca se sabe!

JacquesB
fuente
0

Me gustaría abordar esto desde una perspectiva lógica en lugar de una técnica,

Ahora tengo que cambiar mi código para manejar esa nueva excepción.

Bueno, alguien tendría que manejarlo. Esa es la idea. Los escritores de código de biblioteca serían prudentes al agregar nuevos tipos de excepción, ya que es probable que rompa clientes, no debería encontrarse con esto muy a menudo.

Su pregunta es básicamente "¿Qué pasa si no me importa lo que salió mal? ¿Realmente tengo que pasar por la molestia de descubrir qué fue?"

Aquí está la parte de belleza: no, no lo haces.

"Entonces, ¿puedo mirar hacia otro lado y hacer que cualquier cosa desagradable aparezca automáticamente debajo de la alfombra y termine con eso?"

No, tampoco es así como funciona.

El punto es que la colección de posibles excepciones siempre es mayor que la colección que espera y está interesado en el contexto de su pequeño problema local. Manejas a aquellos que anticipas y si sucede algo inesperado, lo dejas a los manejadores de nivel superior en lugar de tragárselo. Si no te importa una excepción que no esperabas, apostaste a alguien en la pila de llamadas y sería un sabotaje matar una excepción antes de que llegue a su controlador.

"Pero ... pero ... ¡entonces una de esas otras excepciones que no me importan podría hacer que mi tarea falle!"

Si. Pero siempre serán más importantes que los manejados localmente. Como una alarma de incendio o el jefe diciéndole que deje de hacer lo que está haciendo y retome una tarea más urgente que apareció.

Martin Maat
fuente
-3

Debe detectar excepciones generales en el nivel superior de cada proceso y manejarlo informando el error lo mejor posible y luego finalizando el proceso.

No debe atrapar excepciones generales e intentar continuar la ejecución.

ddyer
fuente