Argumentos contra la supresión de errores

35

Encontré un código como este en uno de nuestros proyectos:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Según tengo entendido, suprimir errores como este es una mala práctica, ya que destruye información útil de la excepción del servidor original y hace que el código continúe cuando realmente debería terminar.

¿Cuándo es apropiado suprimir completamente todos los errores como este?

usuario626528
fuente
13
Eric Lippert escribió una excelente publicación de blog llamada excepciones molestas donde clasifica las excepciones en 4 categorías diferentes y sugiere cuál es el enfoque correcto para cada una. Escrito desde una perspectiva de C # pero aplicable en todos los idiomas, creo.
Damien_The_Unbeliever
3
Creo que el código viola el principio de "falla rápida". Hay una alta probabilidad de que el error real esté oculto en él.
Eufórico
44
Estás atrapando demasiado. También estaría detectando errores, como NRE, índice de matriz fuera de límites, error de configuración, ... Eso le impide encontrar esos errores y causarán daños continuamente.
Usr
1
No claves tu programa en la posición vertical
Kevin - Restablece a Monica

Respuestas:

52

Imagine código con miles de archivos usando un montón de bibliotecas. Imagine que todos ellos están codificados de esta manera.

Imagine, por ejemplo, que una actualización de su servidor hace que un archivo de configuración desaparezca; y ahora todo lo que tiene es un seguimiento de pila es una excepción de puntero nulo cuando intenta usar esa clase: ¿cómo resolvería eso? Podría tomar horas, donde al menos solo registrar el seguimiento de la pila sin procesar del archivo no encontrado [ruta del archivo] puede permitirle resolver momentáneamente.

O peor aún: una falla en una de las bibliotecas que usa después de una actualización que hace que su código se bloquee más adelante. ¿Cómo puedes rastrear esto hasta la biblioteca?

Incluso sin un manejo robusto de errores simplemente haciendo

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

o

LOGGER.error("[method name]/[arguments] should not be there")

puede ahorrarle horas de tiempo.

Pero hay algunos casos en los que es posible que desee ignorar la excepción y devolver un valor nulo como este (o no hacer nada). Especialmente si se integra con algún código heredado mal diseñado y esta excepción se espera como un caso normal.

De hecho, al hacer esto, debería preguntarse si realmente está ignorando la excepción o "manejando adecuadamente" sus necesidades. Si devolver nulo es "manejar adecuadamente" su excepción en ese caso dado, hágalo. Y agregue un comentario por qué esto es lo correcto.

Las mejores prácticas son cosas a seguir en la mayoría de los casos, tal vez 80%, tal vez 99%, pero siempre encontrará un caso límite en el que no se aplican. En ese caso, deje un comentario de por qué no está siguiendo la práctica para los demás (o incluso para usted) que leerán su código meses después.

Walfrat
fuente
77
+1 para explicar por qué crees que necesitas hacer esto en un comentario. Sin una explicación, la deglución de excepciones siempre me haría sonar la alarma.
jpmc26
Mi problema con eso es que el comentario está atrapado dentro de ese código. Como consumidor de la función, es posible que nunca llegue a ver ese comentario.
corsiKa
@ jpmc26 Si se maneja la excepción (por ejemplo, devolviendo un valor especial), eso no es tragar excepciones en absoluto.
Deduplicador
2
@corsiKa un consumidor no necesita conocer los detalles de implementación si la función está haciendo su trabajo correctamente. tal vez son algo de esto en las bibliotecas de apache, o spring y no lo sabes.
Walfrat
@Deduplicator sí, pero usar una captura como parte de su algoritmo no se supone que sea una buena práctica también :)
Walfrat
14

Hay casos en los que este patrón es útil, pero generalmente se usan cuando la excepción generada nunca debería haber sido (es decir, cuando se usan excepciones para el comportamiento normal).

Por ejemplo, imagine que tiene una clase que abre un archivo, lo almacena en el objeto que se devuelve. Si el archivo no existe, puede considerar que no se trata de un caso de error, devuelve nulo y deja que el usuario cree un nuevo archivo. El método de abrir archivo puede lanzar una excepción aquí para indicar que no hay ningún archivo presente, por lo que la captura silenciosa puede ser una situación válida.

Sin embargo, no es frecuente que desee hacer esto, y si el código está lleno de ese patrón, querrá tratarlo ahora. Por lo menos, esperaría que una captura tan silenciosa escriba una línea de registro diciendo que esto es lo que sucedió (tiene seguimiento, correcto) y un comentario para explicar este comportamiento.

gbjbaanb
fuente
2
"utilizado cuando la excepción generada nunca debería haber sido" no existe tal cosa. El PUNTO de excepción es señalar que algo salió terriblemente mal.
Eufórico
3
@Euphoric Pero a veces el escritor de una biblioteca no conoce las intenciones del usuario final de esa biblioteca. El "horrible error" de una persona puede ser la condición fácilmente recuperable de otra persona.
Simon B
2
@Euphoric, depende de lo que su lenguaje considere "horriblemente incorrecto": a la gente de Python le gustan las excepciones para cosas que están muy cerca del control de flujo en (por ejemplo,) C ++. Devolver nulo podría ser defendible en algunas situaciones, por ejemplo, leer desde un caché donde los elementos podrían ser desalojados repentina e impredeciblemente.
Matt Krause
10
@Euphoric, "El archivo no encontrado no es una excepción normal. Primero debe verificar si el archivo existe si existe la posibilidad de que no exista". ¡No! ¡No! ¡No! ¡No! Ese es un pensamiento erróneo clásico en torno a las operaciones atómicas. El archivo puede ser eliminado entre usted comprobando si existe y accediendo a él. La única forma correcta de manejar tales situaciones es acceder a ellas y manejar las excepciones si ocurren, por ejemplo, regresando nullsi eso es lo mejor que el idioma elegido puede ofrecer.
David Arno
3
@Euphoric: Entonces, ¿escribir el código para manejar no poder acceder al archivo al menos dos veces? Eso viola DRY, y también el código no ejercitado es código roto.
Deduplicador
7

Esto es 100% dependiente del contexto. Si la persona que llama de dicha función tiene el requisito de mostrar o registrar errores en algún lugar, entonces obviamente no tiene sentido. Si la persona que llama ignoró todos los errores o mensajes de excepción tampoco, no tendría mucho sentido devolver la excepción o su mensaje. Cuando el requisito es hacer que el código solo termine sin mostrar ningún motivo, entonces una llamada

   if(QueryServer(args)==null)
      TerminateProgram();

probablemente sería suficiente Debe considerar si esto hará que sea difícil encontrar la causa del error por parte del usuario; si ese es el caso, esta es la forma incorrecta de manejar el error. Sin embargo, si el código de llamada se ve así

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

entonces debería tener una discusión con el desarrollador durante la revisión del código. Si alguien implementa tal forma de manejo sin errores solo porque es demasiado vago para averiguar sobre los requisitos correctos, entonces este código no debe entrar en producción, y se debe enseñar al autor del código las posibles consecuencias de tal pereza .

Doc Brown
fuente
5

En los años que pasé programando y desarrollando sistemas, solo hay dos situaciones en las que encontré útil el patrón en cuestión (en ambos casos la supresión contenía también el registro de la excepción lanzada, no considero que la captura y el nullretorno sean una buena práctica )

Las dos situaciones son las siguientes:

1. Cuando la excepción no se consideró un estado excepcional

Esto es cuando realiza una operación en algunos datos, que pueden arrojarse, sabe que pueden arrojarse, pero aún así desea que su aplicación siga ejecutándose, porque no necesita los datos procesados. Si los recibe, es bueno, si no los recibe, también es bueno.

Se pueden tener en cuenta algunos atributos opcionales de una clase.

2. Cuando proporciona una nueva implementación (¿mejor, más rápida?) De una biblioteca utilizando una interfaz ya utilizada en una aplicación

Imagina que tienes una aplicación que usa algún tipo de biblioteca antigua, que no arrojó excepciones pero regresó nullpor error. Por lo tanto, creó un adaptador para esta biblioteca, prácticamente copió la API original de la biblioteca y está utilizando esta nueva interfaz (aún no lanzada) en su aplicación y manejando los nullcheques usted mismo.

Viene una nueva versión de la biblioteca, o tal vez una biblioteca completamente diferente que ofrece la misma funcionalidad, que, en lugar de devolver nulls, arroja excepciones y desea usarla.

No desea filtrar las excepciones a su aplicación principal, por lo que debe suprimirlas y registrarlas en el adaptador que cree para ajustar esta nueva dependencia.


El primer caso no es un problema, es el comportamiento deseado del código. Sin embargo, en la segunda situación, si en todas partes el nullvalor de retorno del adaptador de biblioteca realmente significa un error, refactorizar la API para lanzar una excepción y atraparlo en lugar de verificarlo nullpuede ser (y el código generalmente es una buena idea).

Yo personalmente uso la supresión de excepciones solo para el primer caso. Solo lo he usado para el segundo caso, cuando no teníamos el presupuesto para hacer que el resto de la aplicación funcionara con las excepciones en lugar de nulls.

Andy
fuente
-1

Si bien parece lógico decir que los programas solo deberían detectar excepciones que saben cómo manejar, y no pueden saber cómo manejar excepciones que no fueron anticipadas por el programador, tal afirmación ignora el hecho de que muchas operaciones pueden fallar en un número casi ilimitado de formas que no tienen efectos secundarios, y que en muchos casos el manejo adecuado para la gran mayoría de tales fallas será idéntico; los detalles exactos de la falla serán irrelevantes y, en consecuencia, no debería importar si el programador los anticipó.

Si, por ejemplo, el propósito de una función es leer un archivo de documento en un objeto adecuado y crear una nueva ventana de documento para mostrar ese objeto o informar al usuario que el archivo no se puede leer, un intento de cargar un el archivo de documento no válido no debe bloquear la aplicación; en su lugar, debe mostrar un mensaje que indique el problema pero dejar que el resto de la aplicación continúe ejecutándose normalmente a menos que por alguna razón el intento de cargar el documento haya dañado el estado de otra cosa en el sistema .

Básicamente, el manejo adecuado de una excepción a menudo dependerá menos del tipo de excepción que de la ubicación donde se lanzó; Si un recurso está protegido por un bloqueo de lectura y escritura, y se lanza una excepción dentro de un método que ha adquirido el bloqueo para la lectura, el comportamiento adecuado generalmente debería ser liberar el bloqueo ya que el método no puede haber hecho nada al recurso . Si se produce una excepción mientras se adquiere el bloqueo para escritura, el bloqueo a menudo debe invalidarse, ya que el recurso protegido puede estar en un estado no válido; Si la primitiva de bloqueo no tiene un estado "invalidado", se debe agregar una bandera para rastrear dicha invalidación. Liberar el bloqueo sin invalidarlo es malo porque otro código puede ver el objeto protegido en un estado no válido. Sin embargo, dejar la cerradura colgando no es ta solución adecuada tampoco. La solución correcta es invalidar el bloqueo para que cualquier intento de adquisición pendiente o futuro falle inmediatamente.

Si resulta que un recurso invalidado se abandona antes de que se intente usarlo, no hay razón para que detenga la aplicación. Si un recurso invalidado es crítico para la operación continua de una aplicación, la aplicación deberá ser desactivada, pero invalidar el recurso probablemente hará que eso suceda. El código que recibió la excepción original a menudo no tendrá forma de saber qué situación se aplica, pero si invalida el recurso puede asegurarse de que la acción correcta terminará siendo tomada en cualquier caso.

Super gato
fuente
2
¡Esto no responde la pregunta porque manejar una excepción sin conocer los detalles de la excepción! = Consumir silenciosamente una excepción (genérica).
Taemyr
@Taemyr: Si el propósito de una función es "hacer X si es posible, o indicar que no fue así", y X no fue posible, a menudo no será práctico enumerar todos los tipos de excepciones que ni la función ni a la persona que llama le importará más allá de "No fue posible hacer X". El manejo de excepciones de Pokémon es a menudo la única forma práctica de hacer que las cosas funcionen en tales casos, y no necesita ser tan malvado como algunas personas si el código es disciplinado sobre invalidar cosas que se corrompen por excepciones inesperadas.
supercat
Exactamente. Cada método debe tener un propósito, si puede cumplir su propósito independientemente de si algún método que llama arroja una excepción o no, entonces tragar la excepción, sea lo que sea, y hacer su trabajo, es lo correcto. El manejo de errores se trata fundamentalmente de completar la tarea después de un error. Eso es lo que importa, no qué error ocurrió.
jmoreno
@ jmoreno: Lo que dificulta las cosas es que en muchas situaciones habrá una gran cantidad de excepciones, muchas de las cuales un programador no anticiparía particularmente, lo que no indicaría un problema, y ​​algunas excepciones, algunas de las cuales un programador no No espere particularmente, lo que sí indica un problema, y ​​no hay una forma sistemática definida para distinguirlos. La única forma que tengo de tratarlo con sensatez es hacer que las excepciones invaliden las cosas que estaban corrompidas, de modo que si importan, se notan y si no importan se ignoran.
supercat
-2

Tragar este tipo de error no es particularmente útil para nadie. Sí, la llamada original no puede preocuparse por la excepción, sino alguien más fuerza .

Entonces que hacen? Agregarán código para manejar la excepción para ver qué sabor de excepción están recuperando. Excelente. Pero si se deja el controlador de excepciones, la persona que llama original ya no se anula y algo se rompe en otro lugar.

Incluso si sabe que algún código ascendente podría generar un error y, por lo tanto, volverse nulo, sería muy inerte de su parte no intentar al menos evitar la excepción en el código de llamada IMHO.

Robbie Dee
fuente
"seriamente inerte" - ¿quiso decir "seriamente inepto"?
Nombre de pantalla esotérico
@EsotericScreenName Elija uno ... ;-)
Robbie Dee
-3

He visto ejemplos en los que las bibliotecas de terceros tenían métodos potencialmente útiles, excepto que arrojan excepciones en algunos casos que deberían funcionar para mí. ¿Que puedo hacer?

  • Implementar exactamente lo que necesito yo mismo. Puede ser difícil en una fecha límite.
  • Modificar el código de terceros. Pero luego tengo que buscar conflictos de fusión con cada actualización.
  • Escriba un método de contenedor para convertir la excepción en un puntero nulo ordinario, un booleano falso o lo que sea.

Por ejemplo, el método de la biblioteca

public foo findFoo() {...} 

devuelve el primer foo, pero si no hay ninguno, arroja una excepción. Pero lo que necesito es un método para devolver el primer foo o un nulo. Entonces escribo este:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

No aseado Pero a veces la solución pragmática.

om
fuente
1
¿Qué quieres decir con "lanzar excepciones donde deberían funcionar para ti"?
corsiKa
@corsiKa, el ejemplo que me viene a la mente son los métodos de búsqueda a través de una lista o estructura de datos similar. ¿ Fallan cuando no encuentran nada o devuelven un valor nulo? Otro ejemplo son los métodos waitUntil que fallan en el tiempo de espera en lugar de devolver un falso booleano.
om