Manejo de errores: si un programa falla en los errores o los ignora silenciosamente

20

Estoy escribiendo un pequeño programa simple para transmitir MIDI a través de una red. Sé que el programa encontrará problemas de transmisión y / u otras situaciones de excepción que no podré predecir.

Para el manejo de excepciones, veo dos enfoques. ¿Debo escribir el programa para que:

  • falla con una explosión cuando algo sale mal o
  • ¿Debería ignorar el error y continuar, a expensas de la integridad de los datos?

¿Qué enfoque esperaría razonablemente un usuario?
¿Hay una mejor manera de manejar las excepciones?

Además, ¿mi decisión sobre el manejo de excepciones se verá afectada por si estoy tratando o no con una conexión de red (es decir, algo en lo que razonablemente puedo esperar que surjan problemas)?

Arlen Beiler
fuente
1
@ GlenH7 Paga hacia adelante ... :)
mosquito
¿Hay alguna forma de +1 ediciones? ;) Oh, espera, te sigo. Claro, lo haré :)
Arlen Beiler
2
"¿Qué enfoque esperaría razonablemente un usuario?". ¿Intentaste preguntarle a uno de tus usuarios? Las pruebas de usabilidad en el pasillo pueden ser notablemente efectivas. Ver blog.openhallway.com/?p=146
Bryan Oakley
No tengo usuarios :)
Arlen Beiler

Respuestas:

34

Nunca debe ignorar un error que encuentre su programa. Como mínimo, debe registrarlo en un archivo u otro mecanismo para la notificación. ¡ Puede haber situaciones ocasionales en las que desee ignorar un error pero documentarlo! No escriba un catchbloque vacío sin ningún comentario que explique por qué está vacío.

Si el programa debe fallar o no depende mucho del contexto. Si puede manejar el error con gracia, hágalo. Si se trata de un error inesperado, su programa se bloqueará. Eso es más o menos lo básico del manejo de excepciones.

marco-fiset
fuente
18
No es suficiente para garantizar un -1, pero "nunca" es una palabra fuerte, y hay situaciones en las que ignorar los errores es el curso de acción correcto. Por ejemplo, software para decodificar transmisiones de HDTV. Cuando encuentre algunos errores, que con frecuencia lo hará, todo lo que puede hacer es ignorarlo y seguir decodificando lo que viene después.
cuál es el
1
@whatsisname: verdadero, pero debe documentar claramente por qué lo está ignorando. Respuesta actualizada para considerar tu comentario.
marco-fiset
1
@ marco-fiset no estoy de acuerdo. Bloquear todo el programa es solo la solución si es una situación en la que reiniciarlo solucionaría el problema. Este no es siempre el caso, por lo que bloquearse a menudo no tiene sentido y el incumplimiento es un estúpido. ¿Por qué querrías suspender toda tu operación debido a un error localizado? Eso no tiene sentido. La mayoría de las aplicaciones lo hacen y, por alguna razón, los programadores están completamente de acuerdo con eso.
MaiaVictor
@Dokkat: el punto es no continuar con valores incorrectos, lo que dará una solución incorrecta (basura, basura). El bloqueo soluciona el problema: obliga al usuario a proporcionar diferentes entradas o molestar al desarrollador para que arregle su programa;)
André Paramés
1
@whatsisname Creo que es posible que desee pensar en su definición de "error" en ese caso. Los errores en los datos recibidos no son lo mismo que los errores en la estructura y ejecución de un programa de software.
joshin4colours
18

Nunca debe ignorar silenciosamente los errores, porque su programa se basa en una serie de acciones que dependen implícitamente de todo lo que se ha ido antes de que funcionen correctamente. Si algo sale mal en el paso 3, e intenta continuar con el paso 4, el paso 4 comenzará en base a suposiciones no válidas, lo que hace que sea más probable que termine generando un error también. (Y si ignora eso también, entonces el paso 5 arroja un error, y las cosas comienzan a desvanecerse desde allí).

El problema es que, a medida que se acumulan los errores, eventualmente se encontrará con un error tan grande que no podrá ignorarlo, porque consistirá en que se le dará algo al usuario, y ese algo estará completamente mal. Luego, los usuarios se quejan de que su programa no funciona correctamente y tiene que solucionarlo. Y si la parte "dar algo al usuario" se encuentra en el paso 28, y no tiene idea de que el error original que está causando todo este desorden estaba en el paso 3 porque ignoró el error en el paso 3, tendrá un ¡Qué gran problema para depurar el problema!

Por otro lado, si ese error en el paso 3 hace que todo explote en la cara del usuario y genera un error que dice SOMETHING WENT BADLY WRONG IN STEP 3!(o su equivalente técnico, un seguimiento de la pila), entonces el resultado es el mismo: el usuario se queja de usted el programa no funciona bien, pero esta vez sabes exactamente dónde empezar a buscar cuando lo arreglas .

EDITAR: en respuesta a los comentarios, si algo sale mal que anticipaste y sabes cómo manejar, eso es diferente. Por ejemplo, en el caso de recibir un mensaje con formato incorrecto, eso no es un error del programa; eso es "el usuario proporcionó una entrada incorrecta que falló la validación". La respuesta adecuada es decirle al usuario que te está dando una entrada no válida, que es lo que parece que estás haciendo. No es necesario bloquearse y generar un seguimiento de pila en un caso como ese.

Mason Wheeler
fuente
¿Qué tal volver a la última posición buena conocida, o en el caso de un bucle receptor, simplemente dejar caer ese mensaje e ir al siguiente?
Arlen Beiler
@Arlen: Incluso eso puede ser una mala idea. Los errores se producen porque sucedió algo que no anticipó cuando lo codificó . Esto significa que todas sus suposiciones han desaparecido. Esa "última buena posición conocida" podría ya no ser buena si estuviera a mitad de la modificación de alguna estructura de datos y ahora está en un estado inconsistente, por ejemplo.
Mason Wheeler
¿Qué pasa si lo estoy esperando, como mensajes corruptos que impiden la deserialización? En algunos casos, serialicé la excepción y la envié de vuelta a través del cable. Allí, vea mi edición de la pregunta.
Arlen Beiler
@Arlen: Si lo anticipó y está seguro de saber cuáles son los efectos del error y de que están contenidos correctamente, entonces eso es diferente. Me parece que estás manejando el error y respondiendo adecuadamente, sin ignorarlo. Estaba hablando de ignorar errores inesperados , que es lo que parecía que estabas preguntando.
Mason Wheeler
16

Hay otras opciones entre "explotar" e "ignorar".

Si el error es predecible y evitable, cambie su diseño o refactorice su código para evitarlo.

Si el error es predecible pero no evitable, pero sabe qué hacer cuando ocurre, entonces detecte el error y maneje la situación. Pero tenga cuidado de evitar el uso de excepciones como control de flujo. Y es posible que desee registrar una advertencia en este punto, y tal vez notificar al usuario si hay alguna acción que puedan tomar para evitar esta situación en el futuro.

Si el error es predecible, inevitable, y cuando sucede, no hay nada que pueda hacer que garantice la integridad de los datos, entonces debe registrar el error y volver a un estado seguro (que, como han dicho otros, puede significar una falla).

Si el error no es algo que haya anticipado, entonces realmente no puede estar seguro de que pueda volver a un estado seguro, por lo que puede ser mejor simplemente iniciar sesión y bloquearse.

Como regla general, no atrape ninguna excepción sobre la que no pueda hacer nada, a menos que solo planee iniciar sesión y volver a lanzarla. Y en los raros casos en los que es inevitable un try-catch-ignore, al menos agregue un comentario en su bloque catch para explicar por qué.

Consulte el excelente artículo sobre manejo de excepciones de Eric Lippert para obtener más sugerencias sobre categorización y manejo de excepciones.

John M Gant
fuente
1
La mejor respuesta hasta ahora, en mi opinión.
jueves jueves
6

Estas son mis opiniones sobre la pregunta:

Un buen principio de partida es fallar rápidamente. Específicamente, nunca debe escribir código de manejo de errores para cualquier falla para la cual no conozca la causa exacta.

Después de aplicar este principio, puede agregar código de recuperación para condiciones de error específicas que encuentre. También puede introducir varios "estados seguros" para regresar. Anular un programa es principalmente seguro, pero a veces es posible que desee volver a otro buen estado conocido. Un ejemplo es cómo un sistema operativo moderno maneja un programa ofensivo. Solo cierra el programa, no todo el sistema operativo.

Al fallar rápida y lentamente cubriendo más y más condiciones de error específicas, nunca compromete la integridad de los datos y avanza constantemente hacia un programa más estable.

La ingestión de errores, es decir, tratar de planificar errores para los que no conoce la causa exacta y, por lo tanto, no tiene una estrategia de recuperación específica, solo conduce a una cantidad cada vez mayor de código de omisión y elusión de errores en su programa. Como no se puede confiar en que los datos anteriores se procesaron correctamente, comenzará a ver comprobaciones dispersas de datos incorrectos o faltantes. Su complejidad ciclomática se irá de las manos y terminará con una gran bola de barro.

Si usted es consciente o no de los casos de falla es de menor importancia. Pero si, por ejemplo, está tratando con una conexión de red para la cual conoce una cierta cantidad de estados de error, posponga agregar el manejo de errores hasta que también agregue el código de recuperación. Esto está en línea con los principios descritos anteriormente.

Alexander Torstling
fuente
6

Nunca debe ignorar silenciosamente los errores. Y especialmente no a expensas de la integridad de los datos .

El programa está intentando hacer algo. Si falla, debe enfrentar el hecho y hacer algo al respecto. Lo que ese algo será depende de muchas cosas.

Al final, el usuario solicitó al programa que hiciera algo y el programa debería decirles que no tuvo éxito. Hay muchas formas de hacerlo. Puede detenerse de inmediato, incluso puede retroceder los pasos ya completados o, por otro lado, puede continuar y completar todos los pasos que pueda y luego decirle al usuario que estos pasos tuvieron éxito y que otros fallaron.

La forma en que elija depende de cuán estrechamente estén relacionados los pasos y si es probable que el error vuelva a ocurrir para todos los pasos futuros, lo que a su vez podría depender del error exacto. Si se requiere una integridad de datos sólida, debe retroceder al último estado coherente. Si solo está copiando un montón de archivos, puede omitir algunos y simplemente decirle al usuario al final que esos archivos no se pudieron copiar. No debe omitir archivos en silencio y no decirle nada al usuario.

Edición de anuncios, la única diferencia que hace es que debería considerar volver a intentarlo varias veces antes de darse por vencido y decirle al usuario que no funcionó, ya que es probable que la red tenga errores transitorios que no volverán a ocurrir si lo intenta nuevamente.

Jan Hudec
fuente
¿Realmente quisiste poner un signo de interrogación al final de la primera línea?
un CVn
@ MichaelKjörling: No. Error al copiar y pegar (copió la formulación de la pregunta e incluyó el signo de interrogación por error).
Jan Hudec
4

Hay una clase de casos en los que ignorar los errores es lo correcto: cuando no hay nada que pueda hacerse sobre la situación y cuando los resultados pobres y posiblemente incorrectos son mejores que ningún resultado.

El caso de decodificar la transmisión HDMI para fines de visualización es este caso. Si la transmisión es mala, es mala, gritar sobre ella no lo arreglará mágicamente. Hace lo que puede para mostrarlo y dejar que el espectador decida si es tolerable o no.

Loren Pechtel
fuente
1

No creo que un programa deba ignorar silenciosamente o causar estragos cada vez que se encuentre con un problema.

Lo que hago con el software interno que escribo para mi empresa ...

Depende del error, digamos que si es una función crítica que está ingresando datos en MySQL, debe informar al usuario que falló. El controlador de errores debe intentar recopilar tanta información y proporcionar al usuario una idea de cómo corregir el error por sí mismo para que pueda guardar los datos. También me gusta proporcionar una forma de enviarnos silenciosamente la información que intentaban guardar para que, en el peor de los casos, podamos ingresarla manualmente después de corregir el error.

Si no es una función crítica, algo que puede generar un error y no afectar el resultado final de lo que están tratando de lograr, es posible que no les muestre un mensaje de error, pero haga que envíe un correo electrónico que lo inserte automáticamente en nuestro software de seguimiento de errores o un grupo de distribución de correo electrónico que alerta a todos los programadores de la compañía para que estemos al tanto del error, incluso si el usuario no lo está. Esto nos permite arreglar el back-end mientras que en el front-end nadie sabe lo que está pasando.

Una de las cosas más importantes que trato de evitar es que el programa se bloquee después del error, no poder recuperarse. Siempre trato de darle al usuario la opción de continuar sin cerrar la aplicación.

Creo que si nadie sabe sobre el error, nunca se solucionará. También creo firmemente en el manejo de errores que permite que la aplicación continúe funcionando una vez que se descubre un error.

Si el error está relacionado con la red, ¿por qué no hacer que las funciones realicen una prueba de comunicación de red simple antes de ejecutar la función para evitar el error en primer lugar? Luego, solo para alertar al usuario de que no hay una conexión disponible, verifique su Internet, etc., etc. e intente nuevamente.

Jeff
fuente
1
Personalmente verifico mucho antes de ejecutar funciones críticas para tratar de limitar los errores que no entiendo completamente y que no puedo proporcionar un manejo útil de errores que devuelva información detallada. No funciona el 100% del tiempo, pero no recuerdo la última vez que tuve un error que me hizo perder durante horas.
Jeff
1

Mi propia estrategia es distinguir entre los errores de codificación (errores) y los errores de tiempo de ejecución y, en la medida de lo posible, dificultar la creación de errores de codificación.

Los errores deben corregirse lo antes posible, por lo que un enfoque de diseño por contrato es apropiado. En C ++, me gusta verificar todas mis condiciones previas (entradas) con aserciones en la parte superior de la función para detectar el error lo antes posible y facilitar la conexión de un depurador y corregir el error. Si el desarrollador o el evaluador optan por intentar continuar ejecutando el programa, cualquier pérdida de integridad de datos se convierte en su problema.

Y encuentre formas de prevenir el error en primer lugar. Ser estricto con la corrección constante y elegir los tipos de datos apropiados para los datos que mantendrán son dos formas de dificultar la creación de errores. Fail-Fast también es bueno fuera del código crítico de seguridad que necesita una forma de recuperación.

Para errores de tiempo de ejecución que podrían ocurrir con código libre de errores, como fallas de comunicación en red o en serie o archivos faltantes o corruptos:

  1. Registra el error.
  2. (Opcional) Intente reintentar silenciosamente o recuperarse de la operación.
  3. Si la operación continúa fallando o es irrecuperable, informe el error visiblemente al usuario. Luego, como se indicó anteriormente, el usuario puede decidir qué hacer. Recuerde el Principio de Menos Asombro , porque una pérdida de integridad de datos es sorprendente para el usuario a menos que lo haya advertido con anticipación.
snips-n-caracoles
fuente
0

Fallar es la opción correcta cuando tiene razones para pensar que el estado general del programa es inestable y que algo malo puede suceder si lo deja correr de ahora en adelante. Un poco "ignorándolo" (es decir, como otros han señalado, registrarlo en algún lugar o mostrar un mensaje de error al usuario, luego continuar) está bien cuando sabe que, seguro, la operación actual no se puede realizar, pero el programa puede sigue corriendo.

Fabien
fuente