Comment traiter les exceptions non gérées? (Mettre fin à l'application ou la maintenir en vie)

30

Quelle est la meilleure pratique lorsqu'une exception non gérée se produit dans une application de bureau?

Je pensais à montrer un message à l'utilisateur, afin qu'il puisse contacter le support. Je recommanderais à l'utilisateur de redémarrer l'application, mais pas de la forcer. Semblable à ce qui est discuté ici: ux.stackexchange.com - Quelle est la meilleure façon de gérer les erreurs d'application inattendues?

Le projet est une application .NET WPF, donc la proposition décrite pourrait ressembler à ceci (Notez qu'il s'agit d'un exemple simplifié. Il serait probablement judicieux de masquer les détails de l'exception jusqu'à ce que l'utilisateur clique sur "Afficher les détails" et fournisse des fonctionnalités à signaler facilement l'erreur):

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...
    }
}

Dans l'implémentation (commandes de ViewModels ou gestionnaire d'événements d'événements externes), je ne capturais alors que l'exception exogène spécifique et je laissais toutes les autres exceptions (exceptionnelles et exceptionnelles) remonter jusqu'au "Gestionnaire de dernier recours" décrit ci-dessus. Pour une définition des exceptions à tête osée et exogènes, consultez: Eric Lippert - Vexing exceptions

Est-il judicieux de laisser l'utilisateur décider si l'application doit être terminée? Lorsque l'application est terminée, vous n'avez certainement aucun état incohérent ... D'un autre côté, l'utilisateur peut perdre des données non enregistrées ou n'est plus en mesure d'arrêter un processus externe démarré jusqu'à ce que l'application soit redémarrée.

Ou est-ce la décision si vous devez mettre fin à l'application sur des exceptions non gérées en fonction du type d'application que vous écrivez? Est-ce juste un compromis entre «robustesse» et «exactitude» comme décrit dans Code Complete, Second Edition

Pour vous donner un aperçu du type d'application dont nous parlons: L'application est principalement utilisée pour contrôler les instruments de laboratoire de chimie et afficher les résultats mesurés à l'utilisateur. Pour ce faire, les applications WPF communiquent avec certains services (services locaux et distants). L'application WPF ne communique pas directement avec les instruments.

Jonas Benz
la source
27
Si vous ne vous attendiez pas à une exception, comment pouvez-vous être sûr que l'application peut continuer à marcher en toute sécurité?
Déduplicateur
2
@Deduplicator: Bien sûr, vous ne pouvez pas en être sûr. Comme déjà écrit en commentaire de la réponse de Matthew : "Oui, bien sûr, l'application pourrait être dans un état invalide. Peut-être que certains ViewModel n'ont été mis à jour qu'en partie. Mais cela peut-il nuire? L'utilisateur peut recharger les données et si quelque chose d'invalide sera envoyer au service, le service ne l'acceptera pas de toute façon. N'est-il pas préférable pour l'utilisateur qu'il soit en mesure de sauvegarder avant de redémarrer l'application? "
Jonas Benz
2
@Voo Donc, vous vous assurez que l'application peut continuer en toute sécurité en attendant toujours une exception? Semble que vous niez la prémisse de recevoir une exception inattendue.
Déduplicateur
2
Dans tous les cas, veuillez copier le message d'erreur. Sinon, dites dans quel fichier journal il a été écrit.
ComFreek
2
La manipulation n'implique pas nécessairement une action explicite. Si vous pouvez être sûr que l'application peut continuer en toute sécurité, vous avez géré l'exception.
chepner

Réponses:

47

Vous devez vous attendre à ce que votre programme se termine pour plus de raisons qu'une simple exception non gérée de toute façon, comme une panne de courant ou un processus d'arrière-plan différent qui bloque tout le système. Par conséquent, je recommanderais de terminer et de redémarrer l'application, mais avec certaines mesures pour atténuer les conséquences d'un tel redémarrage et minimiser la perte de données possible .

Commencez par analyser les points suivants:

  • Quelle quantité de données peut-on réellement perdre en cas d'arrêt du programme?

  • Quelle est la gravité d'une telle perte pour l'utilisateur? Les données perdues peuvent-elles être reconstruites en moins de 5 minutes, ou parlons-nous de perdre une journée de travail?

  • Combien d'efforts est-ce pour mettre en œuvre une stratégie de «sauvegarde intermédiaire»? N'excluez pas cela parce que "l'utilisateur devrait entrer un motif de changement" lors d'une opération de sauvegarde régulière, comme vous l'avez écrit dans un commentaire. Mieux vaut penser à quelque chose comme un fichier temporaire ou un état, qui peut être rechargé automatiquement après un crash de programme. De nombreux types de logiciels de productivité le font (par exemple, MS Office et LibreOffice ont tous deux une fonction "sauvegarde automatique" et une récupération après incident).

  • Dans le cas où les données étaient erronées ou corrompues, l'utilisateur peut-il le voir facilement (peut-être après un redémarrage du programme)? Si oui, vous pouvez offrir une option pour permettre à l'utilisateur d'enregistrer les données (avec un peu de chance qu'elles soient corrompues), puis forcer un redémarrage, le recharger et laisser l'utilisateur vérifier si les données semblent correctes. Assurez-vous de ne pas écraser la dernière version qui a été enregistrée régulièrement (au lieu d'écrire dans un emplacement / fichier temporaire) pour éviter de corrompre l'ancienne version.

Si une telle stratégie de «sauvegarde intermédiaire» est une option sensée, cela dépend finalement de l'application et de son architecture, ainsi que de la nature et de la structure des données impliquées. Mais si l'utilisateur perd moins de 10 minutes de travail et qu'un tel crash se produit une fois par semaine ou même plus rarement, je n'y investirais probablement pas trop.

Doc Brown
la source
10
en.wikipedia.org/wiki/Crash-only_software , et c'est ainsi que les applications Android fonctionnent par nécessité.
Mooing Duck
3
Une excellente réponse - et un bel exemple sur l'examen des choses dans un contexte plus large (dans ce cas, "comment pouvons-nous éviter la perte de données en cas de crash?") Conduit à une meilleure solution.
sleske
1
J'ai fait une petite modification pour noter que vous ne devriez pas écraser les anciennes données - j'espère que cela ne vous dérange pas.
sleske
1
@MooingDuck De nombreuses applications Android (comme les jeux) perdent leur état lors d'un crash.
user253751
1
@immibis: Oui, Android a en effet un grand nombre d'applications de très mauvaise qualité.
Mooing Duck
30

Cela dépend dans une certaine mesure de l'application que vous développez, mais en général, je dirais que si votre application rencontre une exception non gérée, vous devez la terminer.

Pourquoi?

Parce que vous ne pouvez plus faire confiance à l'état de l'application.

Certainement, fournissez un message utile à l'utilisateur, mais vous devez finalement fermer l'application.

Compte tenu de votre contexte, je voudrais certainement que la demande se termine. Vous ne voulez pas qu'un logiciel exécuté dans un laboratoire produise une sortie corrompue et comme vous ne pensiez pas gérer l'exception, vous ne savez pas pourquoi il a été lancé et ce qui se passe.

Matthieu
la source
J'ai essayé d'ajouter quelques informations contextuelles sur l'application dans la dernière partie.
Jonas Benz
10
@JonasBenz N'est-il pas préférable pour l'utilisateur de pouvoir enregistrer avant de redémarrer l'application? Oui, mais comment savoir si les données que l'utilisateur enregistrerait sont valides et non corrompues? À ce stade, vous avez une exception inattendue et vous ne savez vraiment pas pourquoi. Votre chemin le plus sûr, quoique ennuyeux pour l'utilisateur, est de mettre fin à l'application. Si vous êtes préoccupé par le travail d'épargne des utilisateurs, vous utiliseriez une stratégie d'économie constante. Encore une fois, tout dépend de l'application que vous écrivez.
Matthew
4
Oui, je peux argumenter de la même manière ici: je ne suis pas d'accord avec la présence d'un bouton Continuer. Le problème est simplement que si vous, le développeur de l'application, ne savez pas si vous pouvez continuer en toute sécurité, comment l'utilisateur peut-il le savoir? Si vous obtenez une exception non gérée, cela signifie que vous avez une erreur à laquelle vous ne vous attendiez pas et vous ne pouvez pas dire avec certitude ce qui se passe à ce stade. L'utilisateur voudra continuer parce qu'il ne voudra pas perdre son travail, je le comprends, mais voulez-vous le laisser continuer même si votre application pourrait produire de mauvais résultats à cause de cette erreur?
Matthew
3
@Matthew "si vous, le développeur de l'application ne savez pas si vous pouvez continuer en toute sécurité, comment l'utilisateur peut le savoir" , le développeur ne savait pas quand il a écrit le code. Lorsque l'utilisateur rencontre un bug spécifique comme celui-ci, il peut être connu. Et l'utilisateur peut découvrir à partir des forums d'utilisateurs, des canaux de support et ainsi de suite, ou simplement en testant et en voyant ce qui arrive à leurs données ... Je suis d'accord, c'est toujours un peu trop obscur et dangereux en tant que fonctionnalité utilisateur, soulignant simplement que le temps le permet possible pour l'utilisateur de savoir si "continuer" est sensé ou non.
hyde
1
@JonasBenz, sous Windows 3.1, la boîte de dialogue qui apparaissait lorsqu'un programme effectuait un accès illégal à la mémoire avait un bouton "ignorer" qui permettait au programme de continuer à s'exécuter. Vous remarquerez que chaque version ultérieure de Windows n'a pas ce bouton.
Mark
12

Étant donné que cela est destiné à un laboratoire de chimie et que votre application ne contrôle pas les instruments directement mais plutôt via d'autres services:

Forcer l'arrêt après avoir affiché le message. Après une exception non gérée, votre application est dans un état inconnu. Cela pourrait envoyer des commandes erronées. Il peut même invoquer des démons nasaux . Une commande erronée pourrait potentiellement gaspiller des réactifs coûteux ou mettre en danger l'équipement ou les personnes .

Mais vous pouvez faire autre chose: récupérer gracieusement après le redémarrage . Je suppose que votre application ne supprime pas ces services d'arrière-plan lorsqu'elle se bloque. Dans ce cas, vous pouvez facilement récupérer l'état d'eux. Ou, si vous avez plus d'état, pensez à l'enregistrer. Dans un stockage qui a des dispositions pour l'atomicité et l'intégrité des données (SQLite peut-être?).

Modifier:

Comme indiqué dans les commentaires, le processus que vous contrôlez peut nécessiter des modifications assez rapidement pour que l'utilisateur n'ait pas le temps de réagir. Dans ce cas, vous devriez envisager de redémarrer silencieusement l'application en plus de la récupération de l'état gracieux.

Jan Dorniak
la source
La résiliation dans un état qui nécessite des commandes de suivi MAINTENANT pourrait être tout aussi dangereuse en laboratoire de chimie.
Oleg V. Volkov
@ OlegV.Volkov alors peut-être vous redémarrer lors de la résiliation? Sur un ordinateur décent, le démarrage d'une interface graphique devrait prendre de l'ordre de centaines de millisecondes. Si le processus nécessite des durées plus dures, le contrôle ne serait pas implémenté sur un système d'exploitation non temps réel. Bien que ce soit l'OP qui devrait faire l'évaluation finale des risques.
Jan Dorniak
@ OlegV.Volkov c'est un bon point cependant, j'ai donc ajouté mon opinion à ce sujet dans la réponse.
Jan Dorniak
8

Essayer de répondre généralement à cette question au niveau supérieur du programme n'est pas un jeu intelligent.

Si quelque chose s'est propagé tout au long du processus, et à aucun moment de l'architecture de l'application, personne n'a considéré ce cas, vous n'avez aucune généralisation à faire sur les actions qui sont ou ne peuvent pas être effectuées en toute sécurité.

Donc, non, ce n'est certainement pas une conception généralement acceptable pour permettre à l'utilisateur de choisir si l'application tente de récupérer ou non, car l'application et les développeurs n'ont manifestement pas fait la diligence nécessaire pour savoir si c'est possible ou même sage .

Cependant, si l'application a des parties à haute valeur de sa logique ou de son comportement qui ont été conçues avec ce type de récupération après défaillance à l'esprit, et qu'il est possible de les exploiter dans ce cas, alors par tous les moyens, faites-le - Dans ce cas , il peut être acceptable d'inviter l'utilisateur à voir s'il souhaite tenter une récupération, ou s'il souhaite simplement l'appeler quitter et recommencer.

Ce type de récupération n'est généralement pas nécessaire ni conseillé pour tous (ou même la plupart) des programmes, mais, si vous travaillez sur un programme pour lequel ce degré d'intégrité opérationnelle est requis, cela pourrait être une circonstance dans laquelle présenter ce type de programme demander à un utilisateur serait une chose sensée à faire.

À la lumière de toute logique spéciale de récupération après défaillance - Non, ne faites pas cela. Vous n'avez littéralement aucune idée de ce qui se passera, si vous l'aviez fait, vous auriez pris l'exception plus bas et l'auriez gérée.

Iron Gremlin
la source
Malheureusement, de nombreuses méthodes pour des choses comme «Construire un objet avec des données reçues d'un endroit» ne font aucune distinction entre les exceptions qui indiquent que l'action n'a pas pu être terminée, mais la tentative n'a eu aucun effet secondaire, par rapport à celles qui indiquent que quelque chose de plus grave a mal tourné. Le fait qu'une tentative de chargement d'une ressource ait échoué pour une raison que l'on n'avait pas prévue ne devrait pas forcer une erreur fatale si l'on est généralement préparé à l'incapacité de construire l'objet. Ce qui importe, ce sont les effets secondaires, ce que les cadres d'exception ignorent malheureusement.
supercat
@supercat - Si vous pouvez identifier une erreur, vous pouvez la gérer. Si vous ne pouvez pas l'identifier, vous ne pouvez pas le gérer, sauf si vous écrivez une routine pour exécuter un contrôle d'intégrité sur l'état de l'application afin d'essayer de détecter ce qui aurait pu ne pas fonctionner. Peu importe quelle erreur «aurait pu être», nous avons expressément établi que nous ne le savons pas du fait que nous essayons généralement de gérer les exceptions non capturées.
Iron Gremlin
3

Le problème des "exceptions exceptionnelles", c'est-à-dire des exceptions que vous n'avez pas prévues, est que vous ne savez pas dans quel état se trouve le programme. Par exemple, essayer de sauvegarder les données de l'utilisateur pourrait en fait détruire encore plus de données .

Pour cette raison, vous devez mettre fin à l'application.

Il y a une idée très intéressante appelée Crash-only Software par George Candea et Armando Fox . L'idée est que si vous concevez votre logiciel de manière à ce que la seule façon de le fermer soit de le planter et la seule façon de le démarrer soit de récupérer d'un plantage, alors votre logiciel sera plus résistant et la récupération d'erreur les chemins de code seront testés et exercés de manière beaucoup plus approfondie.

Ils ont eu cette idée après avoir remarqué que certains systèmes démarraient plus rapidement après un crash qu'après un arrêt ordonné.

Un bon exemple, bien que n'étant plus pertinent, est certaines anciennes versions de Firefox qui non seulement démarrent plus rapidement lors de la récupération après un crash, mais ont également une meilleure expérience de démarrage de cette façon ! Dans ces versions, si vous fermez Firefox normalement, il fermerait tous les onglets ouverts et démarrerait avec un seul onglet vide. Alors que lors de la récupération d'un crash, il restaurerait les onglets ouverts au moment du crash. (Et c'était la seule façon de fermer Firefox sans perdre votre contexte de navigation actuel.) Alors, qu'ont fait les gens? Ils n'ont tout simplement jamais fermé Firefox et l'ont toujours pkill -KILL firefoxédité.

Il y a un bon article sur le logiciel crash-only de Valerie Aurora sur Linux Weekly News . Les commentaires méritent également d'être lus. Par exemple, quelqu'un dans les commentaires souligne à juste titre que ces idées ne sont pas nouvelles et sont en fait plus ou moins équivalentes aux principes de conception des applications basées sur Erlang / OTP. Et, bien sûr, en examinant cela aujourd'hui, 10 ans après celui de Valérie et 15 ans après l'article original, nous pourrions remarquer que le battage médiatique actuel des micro-services réinvente encore ces mêmes idées. La conception moderne d'un datacenter à l'échelle du cloud est également un exemple de granularité plus grossière. (Tout ordinateur peut se bloquer à tout moment sans affecter le système.)

Cependant, il ne suffit pas de laisser votre logiciel planter. Il doit être conçu pour cela. Idéalement, votre logiciel devrait être divisé en petits composants indépendants qui peuvent chacun se bloquer de manière indépendante. En outre, le «mécanisme de plantage» doit être en dehors du composant en cours de plantage.

Jörg W Mittag
la source
1

La bonne façon de gérer la plupart des exceptions devrait être d'invalider tout objet qui pourrait être dans un état corrompu en conséquence et de poursuivre l'exécution si les objets invalides ne l'empêchent pas. Par exemple, le paradigme sûr pour la mise à jour d'une ressource serait:

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

Si une exception inattendue se produit lors de la mise à jour de la ressource protégée, la ressource doit être présumée dans un état corrompu et le verrou invalidé, que l'exception soit d'un type qui serait autrement bénin.

Malheureusement, les gardes de ressources implémentés via IDisposable/ usingseront libérés à chaque sortie du bloc gardé, sans aucun moyen de savoir si le bloc est sorti normalement ou anormalement. Ainsi, même s'il devrait y avoir des critères bien définis pour savoir quand continuer après une exception, il n'y a aucun moyen de savoir quand ils s'appliquent.

supercat
la source
+1 simplement pour avoir exprimé cette perspective relativement peu évidente et encore non commune sur la bonne façon de procéder. Je ne sais pas encore si je suis d'accord, car c'est une nouvelle règle / heuristique pour moi, donc je dois y réfléchir pendant un certain temps, mais cela semble plausible.
mtraceur
0

Vous pouvez utiliser l'approche suivie par chaque application iOS et MacOS: Une exception non capturée supprime immédiatement l'application. De plus, de nombreuses erreurs, comme un tableau hors limites ou simplement un débordement arithmétique dans les applications plus récentes, font de même. Pas d'avertissement.

D'après mon expérience, de nombreux utilisateurs n'y prennent pas garde, mais appuyez à nouveau sur l'icône de l'application.

De toute évidence, vous devez vous assurer qu'un tel crash ne conduit pas à une perte de données significative et ne conduit certainement pas à des erreurs coûteuses. Mais une alerte «Votre application va planter maintenant. Appelez l'assistance si cela vous dérange »n'aide personne.

gnasher729
la source