¿Cómo tratar las excepciones no manejadas? (Termina la aplicación vs. Mantenla viva)

30

¿Cuál es la mejor práctica cuando se producen excepciones no controladas en una aplicación de escritorio?

Estaba pensando en mostrar un mensaje al usuario, para que pueda ponerse en contacto con el soporte. Recomendaría al usuario reiniciar la aplicación, pero no forzarla. Similar a lo que se discute aquí: ux.stackexchange.com - ¿Cuál es la mejor manera de manejar errores inesperados de la aplicación?

El proyecto es una aplicación .NET WPF, por lo que la propuesta descrita podría verse así (tenga en cuenta que este es un ejemplo simplificado. Probablemente tendría sentido ocultar los detalles de la excepción hasta que el usuario haga clic en "Mostrar detalles" y proporcione alguna funcionalidad para informar fácilmente el error):

public partial class App : Application
{
    public App()
    {
        DispatcherUnhandledException += OnDispatcherUnhandledException;
    }

    private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        LogError(e.Exception);
        MessageBoxResult result = MessageBox.Show(
             $"Please help us fix it and contact [email protected]. Exception details: {e.Exception}" +
                        "We recommend to restart the application. " +
                        "Do you want to stop the application now? (Warning: Unsaved data gets lost).", 
            "Unexpected error occured.", MessageBoxButton.YesNo);

        // Setting 'Handled' to 'true' will prevent the application from terminating.
        e.Handled = result == MessageBoxResult.No;
    }

    private void LogError(Exception ex)
    {
        // Log to a log file...
    }
}

En la implementación (Comandos de ViewModels o controlador de eventos externos), solo capturaría la excepción exógena específica y dejaría que todas las demás excepciones (excepciones descabelladas y desconocidas) se propaguen al "controlador de último recurso" descrito anteriormente. Para obtener una definición de excepciones exógenas y descabelladas, eche un vistazo a: Eric Lippert - Excepciones irritantes

¿Tiene sentido dejar que el usuario decida si la aplicación debe terminarse? Cuando finaliza la aplicación, seguramente no tiene un estado incoherente ... Por otro lado, el usuario puede perder datos no guardados o ya no puede detener ningún proceso externo iniciado hasta que se reinicie la aplicación.

¿O es la decisión si debe terminar la aplicación en excepciones no manejadas, dependiendo del tipo de aplicación que está escribiendo? ¿Es solo una compensación entre "robustez" versus "corrección" como se describe en Code Complete, Segunda edición

Para darle un poco de contexto de qué tipo de aplicación estamos hablando: la aplicación se utiliza principalmente para controlar instrumentos de laboratorio químico y mostrar los resultados medidos al usuario. Para hacerlo, las aplicaciones WPF se comunican con algunos servicios (servicios locales y remotos). La aplicación WPF no se comunica directamente con los instrumentos.

Jonas Benz
fuente
27
Si no esperaba una excepción, ¿cómo puede estar seguro de que la aplicación puede seguir avanzando con seguridad?
Deduplicador
2
@Dupuplicator: Por supuesto, no puedes estar seguro. Como ya se escribió como comentario a la respuesta de Matthew : "Sí, por supuesto, la aplicación podría estar en un estado no válido. Quizás algunos ViewModel solo se actualizaron parcialmente. ¿Pero esto puede causar algún daño? El usuario puede volver a cargar los datos y si algo no válido será enviar al servicio, entonces el servicio no lo aceptará de todos modos. ¿No es mejor para el usuario si puede guardar antes de reiniciar la aplicación? "
Jonas Benz
2
@Voo Entonces, ¿te aseguras de que la aplicación pueda continuar de forma segura esperando siempre una excepción? Parece que está negando la premisa de recibir una excepción inesperada.
Deduplicador
2
En cualquier caso, haga que el mensaje de error se pueda copiar. Alternativamente, diga en qué archivo de registro se ha escrito.
ComFreek
2
El manejo no necesariamente implica una acción explícita. Si puede estar seguro de que la aplicación puede continuar de manera segura, ha manejado la excepción.
chepner

Respuestas:

47

De todos modos, debe esperar que su programa finalice por más razones que una simple excepción no controlada, como una falla de energía o un proceso en segundo plano diferente que bloquea todo el sistema. Por lo tanto, recomendaría finalizar y reiniciar la aplicación, pero con algunas medidas para mitigar las consecuencias de dicho reinicio y minimizar la posible pérdida de datos .

Comience analizando los siguientes puntos:

  • ¿Cuántos datos pueden perderse en caso de finalización del programa?

  • ¿Qué tan grave es realmente una pérdida para el usuario? ¿Se pueden reconstruir los datos perdidos en menos de 5 minutos, o estamos hablando de perder un día de trabajo?

  • ¿Cuánto esfuerzo es implementar alguna estrategia de "respaldo intermedio"? No descarte esto porque "el usuario tendría que ingresar un motivo de cambio" en una operación de guardado regular, como escribió en un comentario. Mejor piense en algo como un archivo o estado temporal, que se puede volver a cargar después de que un programa se bloquee automáticamente. Muchos tipos de software de productividad hacen esto (por ejemplo, MS Office y LibreOffice tienen una función de "autoguardado" y recuperación de fallos).

  • En caso de que los datos estén incorrectos o dañados, ¿puede el usuario ver esto fácilmente (tal vez después de reiniciar el programa)? En caso afirmativo, puede ofrecer una opción para permitir que el usuario guarde los datos (con una pequeña posibilidad de que esté dañado), luego forzar un reinicio, volver a cargarlo y dejar que el usuario verifique si los datos se ven bien. Asegúrese de no sobrescribir la última versión que se guardó regularmente (en su lugar, escriba en una ubicación / archivo temporal) para evitar corromper la versión anterior.

Si dicha estrategia de "respaldo intermedio" es una opción sensata, depende en última instancia de la aplicación y su arquitectura, y de la naturaleza y estructura de los datos involucrados. Pero si el usuario pierde menos de 10 minutos de trabajo, y tal falla ocurre una vez a la semana o incluso más raramente, probablemente no invertiría demasiado en esto.

Doc Brown
fuente
10
en.wikipedia.org/wiki/Crash-only_software , y así es como las aplicaciones de Android funcionan por necesidad.
Mooing Duck
3
Excelente respuesta, y un buen ejemplo al considerar las cosas en un contexto más amplio (en este caso, "¿cómo podemos evitar la pérdida de datos en cualquier caso de bloqueo?") Conduce a una mejor solución.
Sleske
1
Hice una pequeña edición para tener en cuenta que no debe sobrescribir los datos antiguos, espero que no le importe.
Sleske
1
@MooingDuck Muchas aplicaciones de Android (como los juegos) pierden su estado en un bloqueo.
user253751
1
@immibis: Sí, Android tiene una gran cantidad de aplicaciones de muy baja calidad.
Mooing Duck
30

Depende en cierta medida de la aplicación que esté desarrollando, pero en general, diría que si su aplicación encuentra una excepción no controlada, debe terminarla.

¿Por qué?

Porque ya no puede confiar en el estado de la aplicación.

Definitivamente, proporcione un mensaje útil al usuario, pero finalmente debe finalizar la aplicación.

Dado su contexto, definitivamente me gustaría que la aplicación finalice. No desea que el software que se ejecuta en un laboratorio produzca resultados corruptos y, dado que no pensó en manejar la excepción, no tiene idea de por qué se lanzó y qué está sucediendo.

Mateo
fuente
Traté de agregar información contextual sobre la aplicación en la última parte.
Jonas Benz
10
@JonasBenz ¿No es mejor para el usuario si puede guardar antes de reiniciar la aplicación? Sí, pero ¿cómo saber si los datos que el usuario estaría guardando son válidos y no están dañados? En este punto, tienes una excepción inesperada y realmente no sabes por qué. Su ruta más segura, aunque molesta para el usuario, es finalizar la aplicación. Si le preocupa el trabajo de ahorro del usuario, emplearía una estrategia de ahorro constante. Nuevamente, todo depende de la aplicación que esté escribiendo.
Mateo
44
Sí, puedo argumentar de la misma manera aquí: no estoy de acuerdo con la presencia de un botón Continuar. El problema es simplemente que si usted, el desarrollador de la aplicación, no sabe si puede continuar de manera segura, ¿cómo puede saberlo el usuario? Si obtiene una excepción no controlada, significa que tiene un error que no esperaba y no puede decir con certeza qué está sucediendo en este momento. El usuario querrá continuar porque no querrá perder su trabajo, lo entiendo, pero ¿desea dejar que continúen incluso si su aplicación podría estar produciendo malos resultados debido a este error?
Mateo
3
@Matthew "si usted, el desarrollador de la aplicación no sabe si puede continuar de manera segura, ¿cómo puede saberlo el usuario?" , El desarrollador no sabía cuándo escribió el código. Cuando el usuario encuentra un error específico como este, puede ser conocido. Y el usuario puede averiguarlo en cualquier foro de usuarios, canales de soporte y demás, o simplemente probando y viendo qué sucede con sus datos ... Estoy de acuerdo en que todavía es un poco demasiado oscuro y peligroso como característica del usuario, solo señalando que el tiempo lo hace es posible que el usuario sepa si "continuar" es sensato o no.
hyde
1
@JonasBenz, en Windows 3.1, el cuadro de diálogo que apareció cuando un programa realizó un acceso ilegal a la memoria tenía un botón "ignorar" que permitía que el programa continuara ejecutándose. Notarás que cada versión posterior de Windows no tiene ese botón.
Mark
12

Teniendo en cuenta que esto está destinado a un laboratorio químico y que su aplicación no controla los instrumentos directamente, sino a través de otros servicios:

Forzar la terminación después de mostrar el mensaje. Después de una excepción no controlada, su aplicación se encuentra en un estado desconocido. Podría enviar comandos erróneos. Incluso puede invocar demonios nasales . Un comando erróneo podría desperdiciar reactivos costosos o poner en peligro el equipo o las personas. .

Pero puede hacer otra cosa: recuperarse con gracia después de reiniciar . Supongo que su aplicación no elimina esos servicios en segundo plano cuando se bloquea. En ese caso, puede recuperar fácilmente el estado de ellos. O, si tiene más estado, considere guardarlo. En un almacenamiento que tiene disposiciones para la atomicidad e integridad de los datos (¿tal vez SQLite?).

Editar:

Como se indicó en los comentarios, el proceso que controlas puede requerir cambios lo suficientemente rápido como para que el usuario no tenga tiempo de reaccionar. En ese caso, debe considerar reiniciar silenciosamente la aplicación además de la recuperación de estado elegante.

Jan Dorniak
fuente
La terminación en un estado que requiere comandos de seguimiento AHORA MISMO podría ser igual de peligroso en el laboratorio químico.
Oleg V. Volkov
@ OlegV.Volkov, ¿tal vez reiniciar al terminar? En una computadora decente, iniciar una GUI debería ser del orden de cientos de milisegundos. Si el proceso requiere tiempos más difíciles, el control no se implementaría en un sistema operativo que no sea en tiempo real. Aunque es OP quien debe hacer la evaluación final de riesgos.
Jan Dorniak
@ OlegV.Volkov es un buen punto, así que agregué mi opinión al respecto en la respuesta.
Jan Dorniak
8

Tratar de responder generalmente esta pregunta en el nivel superior del programa no es una jugada inteligente.

Si algo ha surgido por completo, y en ningún momento de la arquitectura de la aplicación alguien consideró este caso, no tiene generalizaciones que pueda hacer sobre qué acciones son o no seguras de tomar.

Entonces, no, definitivamente no es un diseño generalmente aceptable para permitir que el usuario elija si la aplicación intenta recuperarse o no, porque la aplicación y los desarrolladores demostrativamente no han realizado la debida diligencia necesaria para averiguar si eso es posible o incluso sabio .

Sin embargo, si la aplicación tiene porciones de alto valor de su lógica o comportamiento que se han diseñado teniendo en cuenta este tipo de recuperación de fallas, y es posible aprovecharlas en este caso, entonces, por todos los medios, hágalo. En ese caso , puede ser aceptable pedirle al usuario que vea si desea intentar la recuperación, o si desea simplemente cerrarlo y comenzar de nuevo.

Este tipo de recuperación no es generalmente necesaria o aconsejable para todos (o incluso para la mayoría) de los programas, pero, si está trabajando en un programa para el que se requiere este grado de integridad operativa, esa podría ser una circunstancia en la que presentar este tipo de preguntarle a un usuario sería algo sensato.

En virtud de cualquier lógica especial de recuperación de fallas: No, no haga esto. Literalmente no tienes idea de lo que sucederá; si lo hicieras, habrías captado la excepción más abajo y la manejarías.

Gremlin de hierro
fuente
Desafortunadamente, muchos métodos para cosas como "Construir un objeto con datos recibidos de alguna ubicación" no hacen distinción entre las excepciones que indican que la acción no se pudo completar, pero el intento no tuvo efectos secundarios, frente a los que indican que algo más serio Ha salido mal. El hecho de que un intento de cargar un recurso haya fallado por alguna razón que uno no había previsto no debería forzar un error fatal si uno está generalmente preparado para la incapacidad de construir el objeto. Lo que importa son los efectos secundarios, que desafortunadamente es algo que los marcos de excepción ignoran.
supercat
@supercat: si puede identificar un error, puede manejarlo. Si no puede identificarlo, no puede manejarlo, a menos que escriba una rutina para ejecutar una verificación de integridad en el estado de la aplicación para tratar de detectar qué pudo haber salido mal. No importa cuál haya sido el error 'podría haber sido', hemos establecido expresamente que no lo sabemos por el hecho de que estamos tratando de manejar generalmente las excepciones no detectadas.
Iron Gremlin
3

El problema con las "excepciones excepcionales", es decir, las excepciones que no ha previsto, es que no sabe en qué estado se encuentra el programa. Por ejemplo, tratar de guardar los datos del usuario podría destruir aún más datos .

Por esa razón, debe finalizar la aplicación.

Hay una idea muy interesante llamada Software solo para accidentes de George Candea y Armando Fox . La idea es que si diseña su software de tal manera que la única forma de cerrarlo es bloquearlo y la única forma de iniciarlo es recuperarse de un bloqueo, entonces su software será más resistente y la recuperación de errores las rutas de código se probarán y ejercitarán mucho más a fondo.

Se les ocurrió esta idea después de notar que algunos sistemas comenzaron más rápido después de un bloqueo que después de un apagado ordenado.

Un buen ejemplo, aunque ya no es relevante, son algunas versiones anteriores de Firefox que no solo comienzan más rápido al recuperarse de un bloqueo, sino que también tienen una mejor experiencia de inicio . En esas versiones, si cierra Firefox normalmente, cerraría todas las pestañas abiertas y comenzaría con una sola pestaña vacía. Mientras que al recuperarse de un bloqueo, restauraría las pestañas abiertas en el momento del bloqueo. (Y esa era la única forma de cerrar Firefox sin perder su contexto de navegación actual). Entonces, ¿qué hicieron las personas? Simplemente nunca cerraron Firefox y en su lugar siempre lo pkill -KILL firefoxeditaban.

Hay una buena reseña sobre el software de solo bloqueo de Valerie Aurora en Linux Weekly News . Los comentarios también merecen una lectura. Por ejemplo, alguien en los comentarios señala con razón que esas ideas no son nuevas y, de hecho, son más o menos equivalentes a los principios de diseño de las aplicaciones basadas en Erlang / OTP. Y, por supuesto, al ver esto hoy, otros 10 años después del de Valerie y 15 años después del artículo original, podríamos notar que la exageración actual del micro servicio está reinventando esas mismas ideas una vez más. El diseño moderno del centro de datos a escala de la nube también es un ejemplo de una granularidad más gruesa. (Cualquier computadora puede fallar en cualquier momento sin afectar el sistema).

Sin embargo, no es suficiente dejar que su software se bloquee. Tiene que estar diseñado para ello. Idealmente, su software se dividiría en componentes pequeños e independientes que cada uno puede bloquearse de forma independiente. Además, el "mecanismo de bloqueo" debe estar fuera del componente que se está bloqueando.

Jörg W Mittag
fuente
1

La forma correcta de manejar la mayoría de las excepciones debería ser invalidar cualquier objeto que pueda estar en un estado corrupto como consecuencia, y continuar la ejecución si los objetos invalidados no lo impiden. Por ejemplo, el paradigma seguro para actualizar un recurso sería:

acquire lock
try
  update guarded resource
if exception
  invalidate lock
else
  release lock
end try

Si se produce una excepción inesperada al actualizar el recurso protegido, se debe suponer que el recurso está en un estado corrupto y se debe invalidar el bloqueo, independientemente de si la excepción es de un tipo que de otro modo sería benigno.

Desafortunadamente, los guardias de recursos implementados a través de IDisposable/ usingse liberarán cada vez que el bloque protegido salga, sin ninguna forma de saber si el bloque salió de manera normal o anormal. Por lo tanto, aunque debe haber criterios bien definidos sobre cuándo continuar después de una excepción, no hay forma de saber cuándo se aplican.

Super gato
fuente
+1 simplemente por expresar esta perspectiva relativamente no obvia y aún no común sobre cuál es la forma correcta. En realidad, aún no sé si estoy de acuerdo con esto, porque esta es una novela heurística / regla para mí, por lo que tengo que reflexionar durante un tiempo, pero parece plausiblemente sabio.
mtraceur
0

Puede utilizar el enfoque que siguen todas las aplicaciones de iOS y MacOS: una excepción no detectada desactiva la aplicación de inmediato. Además, muchos errores, como la matriz fuera de los límites o simplemente el desbordamiento aritmético en las aplicaciones más nuevas, hacen lo mismo. Sin advertencia.

En mi experiencia, muchos usuarios no se dan cuenta pero simplemente vuelven a tocar el ícono de la aplicación.

Obviamente, debe asegurarse de que dicho bloqueo no conduzca a una pérdida de datos significativa y definitivamente no conduzca a errores costosos. Pero una alerta "Tu aplicación se bloqueará ahora. Llame al soporte si le molesta ”no está ayudando a nadie.

gnasher729
fuente