web-dev-qa-db-fra.com

Utilisation de try/catch pour empêcher l'application de planter

Je travaille sur une application Android qui utilise try/catch fréquemment pour l'empêcher de tomber en panne, même à des endroits où cela n'est pas nécessaire. Par exemple,

Une vue dans xml layout avec id = toolbar est référencée comme suit:

// see new example below, this one is just confusing
// it seems like I am asking about empty try/catch
try {
    View view = findViewById(R.id.toolbar);
}
catch(Exception e) {
}

Cette approche est utilisée dans toute l'application. La trace de la pile n’est pas imprimée et il est très difficile de trouver ce qui ne va pas. L'application se ferme soudainement sans imprimer aucune trace de pile.

J'ai demandé à mon supérieur de me l'expliquer et il a dit:

Ceci est pour éviter un crash en production.

_ {Je ne suis absolument pas d'accord avec ça}. Pour moi, ce n'est pas le moyen d'empêcher les applications de se bloquer. Cela montre que le développeur ne} _ sait pas ce qu’il fait et doute.

Est-ce l'approche utilisée dans l'industrie pour empêcher les applications d'entreprise de planter?

Si try/catch est vraiment, vraiment notre besoin, est-il possible d'attacher un gestionnaire d'exceptions avec un thread d'interface utilisateur ou d'autres threads et de tout intercepter? Ce sera une meilleure approche si possible.

Oui, try/catch vide est mauvais et même si nous imprimons une trace de pile ou une exception de journal sur le serveur, insérer des blocs de code dans try/catch de manière aléatoire dans l'ensemble de l'application n'a aucun sens, par exemple. quand chaque fonction est incluse dans un try/catch.

METTRE À JOUR

Comme cette question a attiré beaucoup d'attention et que certaines personnes l'ont mal interprétée (peut-être parce que je ne l'ai pas formulée clairement), je vais la reformuler. 

Voici ce que les développeurs font ici

  • Une fonction est écrite et testée, il peut s'agir d'une petite fonction qui initialise simplement des vues ou d'une vue complexe, après l'avoir testée, elle est encapsulée dans le bloc try/catch. Même pour une fonction qui ne lève jamais aucune exception.

  • Cette pratique est utilisée dans toute l'application. Parfois, la trace de pile est imprimée et parfois juste un debug log avec un message d'erreur aléatoire. Ce message d'erreur diffère d'un développeur à l'autre.

  • Avec cette approche, l'application ne plante pas, mais le comportement de l'application devient indéterminé. Même parfois, il est difficile de suivre ce qui a mal tourné.

  • La vraie question que je posais était: _ {Est-ce la pratique courante dans l'industrie visant à empêcher les applications d'entreprise de se bloquer?} Et je ne pose pas de question à propos de (try/catch vide}. Est-ce que c'est comme, les utilisateurs aiment les applications qui ne plantent pas en panne que les applications qui se comportent de manière inattendue? Parce qu’il s’agit vraiment de le bloquer ou de présenter à l’utilisateur un écran vide ou au comportement que l’utilisateur ignore.

  • Je publie quelques extraits du code réel ici

      private void makeRequestForForgetPassword() {
        try {
            HashMap<String, Object> params = new HashMap<>();
    
            String email= CurrentUserData.msisdn;
            params.put("email", "blabla");
            params.put("new_password", password);
    
            NetworkProcess networkProcessForgetStep = new NetworkProcess(
                serviceCallListenerForgotPasswordStep, ForgotPassword.this);
            networkProcessForgetStep.serviceProcessing(params, 
                Constants.API_FORGOT_PASSWORD);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
     private void languagePopUpDialog(View view) {
        try {
            PopupWindow popupwindow_obj = popupDisplay();
            popupwindow_obj.showAsDropDown(view, -50, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    void reloadActivity() {
        try {
            onCreateProcess();
        } catch (Exception e) {
        }
    }
    

Il s’agit de pas dupliquer de la meilleure gestion des exceptions Android pratiques , là OP essaie d’attraper une exception pour un différent but que cette question.

74
mallaudin

Je mettrais cela comme commentaire dans une autre réponse, mais je n’ai pas encore la réputation de le faire.

Vous avez raison de dire que c'est une mauvaise pratique. En fait, ce que vous avez publié montre différents types de mauvaises pratiques en ce qui concerne les exceptions.

  1. Manque de gestion des erreurs
  2. Capture générique
  3. Aucune exception intentionnelle
  4. Couverture Essayez/attrapez

Je vais essayer d'expliquer tous ceux-ci via cet exemple.

try {
   User user = loadUserFromWeb();     
   if(user.getCountry().equals("us")) {  
       enableExtraFields();
   }
   fillFields(user);   
} catch (Exception e) { 
}

Cela peut échouer de plusieurs manières qui devraient être traitées différemment.

  1. Les champs ne seront pas renseignés. Un écran vide apparaît alors à l'utilisateur, puis ... quoi? Rien - manque de gestion des erreurs.
  2. Il n'y a pas de distinction entre les différents types d'erreurs, par exemple. Problèmes Internet ou problèmes avec le serveur lui-même (panne, demande interrompue, transmission corrompue, ...) - Captures génériques.
  3. Vous ne pouvez pas utiliser d'exceptions pour vos propres besoins car le système actuel interfère avec cela. - Aucune exception intentionnelle
  4. Des erreurs non essentielles et inattendues (par exemple, null.equals (...)) peuvent empêcher le code essentiel de s'exécuter. - Couverture try/catch

Solutions

(1) Tout d’abord, échouer en silence n’est pas une bonne chose. En cas d'échec, l'application ne fonctionnera pas. Au lieu de cela, vous devez essayer de résoudre le problème ou afficher un avertissement, par exemple "Impossible de charger les données utilisateur, peut-être que vous n'êtes pas connecté à Internet?". Si l'application ne fait pas ce qu'elle est censée faire, c'est beaucoup plus frustrant pour un utilisateur que de se fermer tout seul.

(4) Si l'utilisateur est incomplet, par ex. le pays n'est pas connu et renvoie null. La méthode equals créera une NullPointerException. Si ce NPE est simplement lancé et attrapé comme ci-dessus, la méthode fillFields (utilisateur) ne sera pas appelée, même si elle peut toujours être exécutée sans problème. Vous pouvez éviter cela en incluant des contrôles nuls, en modifiant l'ordre d'exécution ou en ajustant la portée try/catch. (Vous pouvez également sauvegarder en utilisant le code suivant: "us" .equals (user.getCountry ()), mais je devais donner un exemple). Bien entendu, toute autre exception empêchera également fillFields () d'être exécutée, mais s'il n'y a pas d'utilisateur, vous ne voudrez probablement pas qu'elle soit exécutée de toute façon.

(1, 2, 3) Le chargement à partir du Web génère souvent une série d'exceptions, d'une exception IOException à une exception HttpMessageNotReadable, voire même à un retour. Il se peut que l'utilisateur ne soit pas connecté à Internet, qu'un serveur dorsal ait été modifié ou qu'il soit en panne, mais vous ne le savez pas car vous interceptez (Exception). Vous devez au contraire intercepter des exceptions spécifiques. Vous pouvez même en prendre plusieurs comme ça

try{
   User user = loadUserFromWeb(); //throws NoInternetException, ServerNotAvailableException or returns null if user does not exist
   if(user == null) { 
       throw new UserDoesNotExistException(); //there might be better options to solve this, but it highlights how exceptions can be used.
   }
   fillFields(user);
   if("us".equals(user.getCountry()) {
       enableExtraFields();
   }
} catch(NoInternetException e){
    displayWarning("Your internet conneciton is down :(");
} catch(ServerNotAvailableException e){
    displayWarning("Seems like our server is having trouble, try again later.");
} catch(UserDoesNotExistException e){
    startCreateUserActivity();
}

J'éspère que ça l'explique.

À tout le moins, comme solution rapide, ce que vous pourriez faire est d’envoyer un événement à votre serveur avec l’exception. Par exemple via firebase ou crashlytics. De cette façon, vous pouvez au moins voir des choses comme (hé, l’activité principale ne se charge pas pour 80% de nos utilisateurs en raison d’un problème comme (4).

9
Syzygy

C'est définitivement une mauvaise pratique de programmation. 

Dans le scénario actuel, s'il existe des centaines de trycatch comme celle-ci, vous ne saurez même pas où l'exception se produit sans déboguer l'application, ce qui est un cauchemar si votre application est en environnement de production.

Mais vous pouvez inclure un enregistreur afin de savoir quand une exception est levée (et pourquoi). Cela ne changera pas votre flux de travail normal.

...
try {
    View view = findViewById(R.id.toolbar);
}catch(Exception e){
    logger.log(Level.SEVERE, "an exception was thrown", e);
}
...
8
Raman Sahasi

C'est une mauvaise pratique. D'autres réponses l'ont dit mais je pense qu'il est important de prendre du recul et de comprendre pourquoi nous avons des exceptions au départ.

Chaque fonction a une post-condition - un ensemble de choses qui doivent toutes être vraies après son exécution. Par exemple, une fonction qui lit un fichier a la condition postérieure que les données du fichier soient lues sur le disque et retournées. Une exception est alors levée lorsqu'une fonction n'a pas été en mesure de satisfaire l'une de ses post-conditions.

En ignorant une exception d'une fonction (ou même en l'ignorant simplement en enregistrant l'exception), vous dites que vous acceptez que cette fonction ne réalise pas tout le travail qu'elle a convenu de faire. Cela semble peu probable - si une fonction ne s'exécute pas correctement, rien ne garantit que ce qui suit sera exécuté du tout. Et si le reste de votre code fonctionne correctement, qu’une fonction particulière soit achevée ou non, on peut se demander pourquoi vous avez cette fonction en premier lieu.

[Il existe maintenant certains cas où catches vide est correct. Par exemple, la journalisation est quelque chose que vous pourriez justifier de placer dans une capture vide. Votre application fonctionnera probablement correctement même si une partie de la journalisation ne peut pas être écrite. Mais ce sont des cas spéciaux que vous devez travailler très dur à trouver dans une application normale.]

Le fait est que c'est une mauvaise pratique car cela ne maintient pas votre application en cours d'exécution (la prétendue justification de ce style). Peut-être que techniquement, l'OS ne l'a pas tué. Mais il est peu probable que l'application fonctionne toujours correctement après avoir simplement ignoré une exception. Et dans le pire des cas, il pourrait en fait être dommageable (par exemple, corrompre des fichiers d’utilisateur, etc.).

7
JoshG79

C'est mauvais pour plusieurs raisons:

  1. Que faites-vous pour que findViewById lève une exception? Corrige ça (et dis-moi, parce que je n'ai jamais vu ça) au lieu d'attraper.
  2. N'attrapez pas Exception si vous pouviez intercepter un type d'exception spécifique. 
  3. Il existe cette conviction que les bonnes applications ne plantent pas. Ce n'est pas vrai. Une bonne application se bloque s'il le faut.

Si une application se met en mauvais état, il est bien préférable qu'elle se bloque que de continuer à fonctionner dans son état inutilisable. Quand on voit un NPE, il ne faut pas simplement s'en tenir à un contrôle nul et s'en aller. La meilleure approche consiste à déterminer la raison pour laquelle quelque chose est null et à l’empêcher d’être nul, ou à vérifier (si null est un état valide et attendu), vérifier la valeur null. Mais vous devez comprendre pourquoi le problème se pose en premier lieu.

3
A J

Je développe des applications Android depuis 4 ou 5 ans et je n’ai jamais utilisé de prise d’essai pour l’initialisation de la vue.

Si c'est une barre d'outils comme ça

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

par exemple: - Pour obtenir un TextView à partir d'une vue (fragment/boîte de dialogue/toute vue personnalisée)

TextView textview = (TextView) view.findViewById(R.id.viewId);

TextView textview = (TextView) view.findViewById (R.id.viewId);

au lieu de cela

View view = findViewById(R.id.toolbar);

Un objet de vue a une portée minimale par rapport à son type de vue réel.

Remarque: - Peut-être que cela s'est écrasé parce que la vue a été chargée Mais ajouter try catch est une mauvaise pratique. 

2
shijin

Comme indiqué précédemment, les exceptions générales ne doivent pas être capturées, ou du moins uniquement dans des emplacements peu centraux (généralement situés dans le code d'infrastructure/infrastructure, pas dans le code d'application). Si vous attrapez des exceptions générales et vous enregistrez, l'application doit être fermée par la suite ou au moins, l'utilisateur doit être informé que l'application est potentiellement dans un état instable et que les données risquent d'être corrompues (si l'utilisateur choisit de continuer l'exécution). Parce que c'est ce qui pourrait arriver si vous capturez toutes sortes d'exceptions (mémoire insuffisante pour en nommer une) et en laissant l'application dans un état non défini.

À mon humble avis, il est pire d’avaler des exceptions et de compromettre l’intégrité des données, la perte de données ou tout simplement de laisser l’application dans un état non défini que de laisser l’application planter et l’utilisateur sait que quelque chose s’est mal passé et peut essayer à nouveau. Cela entraînera également de meilleurs problèmes signalés (plus à la racine du problème), probablement moins de symptômes différents que si vos utilisateurs commencent à signaler tout type de problèmes liés à un état d'application non défini.

Une fois que la gestion/la journalisation/la génération d’exceptions centralisée et la fermeture contrôlée sont en place, commencez à réécrire la gestion des exceptions pour capturer les exceptions locales aussi spécifiques que possible. Essayez de faire en sorte que le bloc try {} soit le plus court possible.

1
DavWEB

Une autre perspective, en tant que personne qui écrit un logiciel d’entreprise au quotidien, si une application a une erreur irrécupérable, je veux elle tombe en panne. Crashing est souhaitable. S'il se bloque, il est connecté. S'il se bloque plus d'une fois sur une courte période, je reçois un courrier électronique l'informant que l'application se bloque et je peux vérifier que notre application et tous les services Web que nous consommons fonctionnent toujours.

Alors la question:

Est 

try{
  someMethod();
}catch(Exception e){}

bonnes pratiques? Non! Voici quelques points:

  1. La chose la plus importante: c'est une mauvaise expérience client. Comment suis-je censé savoir quand quelque chose de mauvais se passe? Mes clients essaient d'utiliser mon application et rien ne fonctionne. Ils ne peuvent pas consulter leur compte bancaire, payer leurs factures, quoi que fasse mon application. Mon application est complètement inutile, mais bon, au moins elle ne s'est pas écrasée! (Une partie de moi pense que ce développeur "senior" obtient des points brownie pour un faible nombre d'accidents, alors ils jouent avec le système.)

  2. Quand je fais du développement et que j'écris du mauvais code, si je ne fais que capturer et avaler toutes les exceptions de la couche supérieure, je n'ai pas de journalisation. Je n'ai rien dans ma console et mon application échoue silencieusement. D'après ce que je peux dire, tout semble bien fonctionner. Donc, je commets le code ... Il s'avère que mon objet DAO était null tout le temps, et que les paiements du client n'étaient jamais réellement mis à jour dans la base de données. Oups! Mais mon application n'a pas planté, c'est donc un avantage.

  3. En défendant les intérêts du diable, disons que je pouvais attraper et avaler toutes les exceptions. Il est extrêmement facile d'écrire un gestionnaire d'exceptions personnalisé sous Android. Si vous voulez vraiment (avoir} _ attraper toutes les exceptions, vous pouvez le faire en un seul endroit et ne pas utiliser le code try/catch partout dans votre code.

Certains des développeurs avec lesquels j'ai travaillé dans le passé ont pensé que se bloquer était une mauvaise chose.

Je dois leur assurer que nous voulons notre application plante. Non, une application instable n'est pas acceptable, mais un blocage signifie que nous avons commis une erreur et que nous devons y remédier. Plus vite il se bloque, plus tôt nous le trouvons, plus il est facile de le réparer. La seule autre option à laquelle je peux penser est de permettre à l'utilisateur de continuer dans une session interrompue, ce qui équivaut à assommer ma base d'utilisateurs. 

1

Permettez-moi d'ajouter mon point de vue, en tant que personne travaillant dans le secteur du développement mobile d'entreprise pendant plus d'une décennie. Premièrement, quelques conseils généraux sur les exceptions, dont la plupart sont inclus dans les réponses ci-dessus:

  • Les exceptions doivent être utilisées pour des situations exceptionnelles, inattendues ou incontrôlées, et non de manière régulière dans le code.
  • Un programmeur doit connaître les parties de code susceptibles de générer des exceptions et les intercepter, en laissant le reste du code aussi propre que possible.
  • Les exceptions ne doivent pas rester silencieuses, en règle générale.

Maintenant, lorsque vous ne développez pas une application pour vous-même, mais pour une entreprise ou une entreprise, il est courant de faire face à des exigences supplémentaires à ce sujet:

  • "Les crashs d'applications donnent une mauvaise image de la société, ils ne sont donc pas acceptables". Ensuite, un développement minutieux doit être effectué, et attraper même des exceptions improbables peut être une option. Si tel est le cas, cela doit être fait de manière sélective et maintenu dans des limites raisonnables. Et notez que le développement ne concerne pas uniquement les lignes de code, par exemple, un processus de test intensif est essentiel dans ces cas. Cependant, un comportement inattendu dans une application d'entreprise est pire que de planter. Ainsi, chaque fois que vous détectez une exception dans votre application, vous devez savoir quoi faire, quoi montrer et comment l’application se comportera par la suite. Si vous ne pouvez pas contrôler cela, il vaut mieux laisser l'application se bloquer.
  • "Les journaux et les traces de pile peuvent envoyer des informations sensibles sur la console. Cela pourrait être utilisé par un attaquant. Par conséquent, pour des raisons de sécurité, ils ne peuvent pas être utilisés dans un environnement de production". Cette exigence est en conflit avec la règle générale selon laquelle un développeur ne doit pas écrire d'exceptions silencieuses. Vous devez donc en trouver le moyen. Par exemple, votre application peut contrôler l'environnement. Elle utilise donc des journaux et des traces de pile dans des environnements non productifs, tout en utilisant des outils basés sur le cloud tels que bugsense, crashlitics ou similaires pour les environnements de production.

La réponse courte est donc que le code que vous avez trouvé n'est pas un bon exemple de pratique, car il est difficile et coûteux de le maintenir sans améliorer la qualité de l'application.

1
Miguel

Oui, try/catch est utilisé pour éviter le blocage de l'application, mais vous n'avez certainement pas besoin de try/catch pour extraire une vue à partir de XML, comme indiqué dans votre question.

try/catch est généralement utilisé lors de toute requête http, lors de l'analyse syntaxique de String en URL, lors de la création de connexions d'URL, etc., et veille également à imprimer le suivi de la pile. Ne pas l'imprimer n'a pas beaucoup de sens en l'entourant d'essayer/attraper.

1

C'est une mauvaise pratique d'utiliser catch(Exception e){} parce que vous ignorez essentiellement l'erreur. Ce que vous voulez probablement faire est quelque chose de plus semblable à:

try {
    //run code that could crash here
} catch (Exception e) {
    System.out.println(e.getMessage());
}
0
chbchb55

Nous utilisons beaucoup votre même logique. Utilisez try-catch pour empêcher les applications de production de tomber en panne.

Les exceptions doivent êtreJAMAISignorées. C'est une mauvaise pratique de codage. Les personnes qui gèrent le code auront beaucoup de difficulté à localiser la partie du code qui a déclenché l'exception si elles ne sont pas connectées.

Nous utilisons Crashlytics pour consigner les exceptions. Le code ne plantera pas (mais certaines fonctionnalités seront perturbées). Mais vous obtenez le journal des exceptions dans le tableau de bord de Fabric/Crashlytics. Vous pouvez consulter ces journaux et corriger les exceptions.

try {
    codeThatCouldRaiseError();
} catch (Exception e) {
    e.printStackTrace();
    Crashlytics.logException(e);
}
0
K Neeraj Lal

Bien que je sois d’accord avec les autres réponses, j’ai rencontré à maintes reprises une situation où ce motif est marginalement tolérable. Supposons que quelqu'un écrive un peu de code pour une classe comme suit:

private int foo=0;

    . . .

public int getFoo() throws SomeException { return foo; }

Dans ce cas, la méthode 'getFoo ()' ne peut pas échouer - il y aura toujours une valeur légitime du champ privé 'foo' à renvoyer. Pourtant, quelqu'un - probablement pour des raisons pédantes - a décidé que cette méthode devait être déclarée comme pouvant potentiellement générer une exception. Si vous essayez ensuite d'appeler cette méthode dans un contexte - par exemple un gestionnaire d'événements - qui ne permet pas de lever une exception, vous êtes essentiellement obligé d'utiliser cette construction (même dans ce cas, je conviens qu'il faut au moins connectez l'exception juste au cas où). Chaque fois que je dois faire cela, j'ajoute au moins un gros commentaire 'CECI NE PEUT PAS OCCURER' à côté de la clause 'catch'.

0
PMar