Faire un don ! | | style | statistiques | contactez-nous | plan | lettre d'information

Journal : Qu'est-ce que bien gérer les erreurs dans ses programmes ?

Posté par Nicolas Boulay () le 14 décembre 2007
La question métaphysique du jour : comment bien gérer les erreurs ?

Il y a déjà plusieurs types d'erreurs: celles qui relèvent de la mauvaise utilisation de code, elle pourrait se traiter avec des assert(), il y a celle qui remonte un mauvais fonctionnement à l'étage du dessus, et celle entre les deux, qui peuvent servir parfois à effectuer des "scans" de fonctionnalités (genre on charge tous les noyau réseau un par un pour trouver le bon driver de sa carte).

Les problèmes surviennent lorsque du code qui devrait retourner un assert() et donc crasher en cas d'erreur font un simple "return error"; et qui a le malheur de faire une exécution partielle du code. En général, 2 ou 3 appels de fonctions différentes de la lib en question plus tard, tout va se viander aléatoirement.

Donc, il faut déjà prévoir quoi faire en cas d'erreurs au plus bas niveau et ne remonter qu'en cas d'impossibilité de gérer le problème à ce niveau et encore, en le faisant proprement (par exemple, un retour de malloc à 0 ?).

Proprement, cela veut dire peut-être de séparer fonction de test de validité des paramètres, et exécution proprement dite de la fonction, cela permet d'éviter les exécutions partielles (imaginez une fonction de lib qui retourne une erreur en s'étant exécuter à moitie dans un cas qui n'entraine pas une erreur fatal de l'ensemble).

Souvent dans les exemples d'utilisation, la gestion des erreurs est mise de coté pour éviter d'alourdir un exemple. Cela démontre déjà que la gestion d'erreur à tendance à brouiller l'algorithme de base. Je trouve que cela renforce le principe de base de bien séparer exécution et traitement d'erreur.

Il y a maintenant les gestions d'exception pour faire cela. Je n'ai jamais vraiment coder avec, mais je n'ai jamais non plus vu un véritable enthousiasme pour ce système.

J'aurais tendance à éviter toute gestion d'erreurs qui entraîne un crash. Aucun utilisateur n'aime voir un crash, surtout dans l'embarqué. Cela me rappelle une certaine central inertielle qui partait en autotest sur une exception Ada. J'aurais tendance à interdire formellement tout code qui interrompt la fonctionnalité.

Connaissez vous des règles génériques pour déterminer la conduite à tenir en cas de retour d'erreur ?

Qu'est-ce que vous conseillez donc pour faire propre ?

> Lire le journal (80 commentaires, moyenne: 2,4).  

Vous avez demandé le commentaire #890593.

Ma réponse :

Posté par Matthieu MARC () le 14/12/2007 à 14:54. (lien). Évalué à 2.

Question : Comment bien gérer ses erreurs ?

Réponse : ne pas en faire !

  • [^]Re: Ma réponse :

    Posté par bamboo (page perso, ) le 14/12/2007 à 15:03. (lien). Évalué à 5.

    >J'aurais tendance à interdire formellement tout code qui interrompt la fonctionnalité.

    Le principe de l'erreur (et encore plus en embarqué) c'est justement que ça interrompt en plein milieu de rien. (genre le bête plantage physique d'un équipement)

    Pour moi, les exceptions c'est le top. En c#, des exceptions typées qui vont bien. Alors oui ça alourdit énormément. Ceci étant, c'est un problème récurrent : si tu veux gérer tous les cas d'erreurs possibles et imaginables, ton code sera hyper lourd. Après le tout est de trouver le juste milieu.

    • [^]Re: Ma réponse :

      Posté par Nicolas Boulay () le 14/12/2007 à 15:42. (lien). Évalué à 3.

      En C#, tu arrives à bien séparer la gestion d'erreur du reste ?

      Parce que si ton code devient illisible, cela peut être pire.

      Qu'est-ce que tu appelles des "exceptions typées" ?

      • [^]Re: Ma réponse :

        Posté par Romain Bignon (Jabber id, page perso, ) le 14/12/2007 à 16:19. (lien). Évalué à 2.

        Qu'est-ce que tu appelles des "exceptions typées" ?


        En gros ton exception est une classe. Quand tu lances une exception, c'est l'instance d'une classe que tu envoies.

        Ça va remonter tous les niveaux de la pile d'appel jusqu'à ce qu'on la capture, et le programme s'arrête si jamais aucune fonction ne la capture.

        Donc ce qu'il devait vouloir dire, c'est que tu es libre de capturer les types d'exceptions que tu veux à un niveau donné du code, et à avoir un traitement différent à chaque fois.

        Le principe des exceptions en soit n'est pas mauvais, c'est juste que ça reste très chiant à mettre en oeuvre (dans le sens où dans un projet relativement gros ça prends du temps, et qu'en général tu as autre chose en tête).

        • [^]Re: Ma réponse :

          Posté par Nicolas Boulay () le 14/12/2007 à 16:44. (lien). Évalué à 4.

          Il existe d'autre modèle que les exceptions ou le retour de message d'erreur ?

          • [^]Re: Ma réponse :

            Posté par Sylvain Sauvage () le 14/12/2007 à 19:16. (lien). Évalué à 6.

            Oui, le pire de tous : une variable globale (au hasard, appelons-la errno)…


            (Ouais, bon, j’exagère, en général, errno sert seulement à savoir quelle erreur s’est produite, la survenance de l’erreur elle-même est indiquée par un code retour spécial (souvent 0, en tout cas un résultat hors domaine), mais la technique de la variable globale à vérifier existe quand même…)

            • [^]Re: Ma réponse :

              Posté par left () le 14/12/2007 à 20:31. (lien). Évalué à 2.

              C'est le contraire: en général c'est -1 la valeur de retour pour une erreur, 0 signifiant que tout s'est bien passé. C'est d'ailleurs une hypothèse de très longue date des shells (genre `set -e' en sh: 'Exit immediately if a simple command [...] exits with a non-zero status.').

              • [^]Re: Ma réponse :

                Posté par totof2000 () le 14/12/2007 à 22:55. (lien). Évalué à 3.

                C'est vrai pour le shell mais pas en programmation C.

                Il arrive de voir des fonctions de bibliothèques prendre comme convention 0=erreur, !=0=succes.

                Ca permet des trucs du genre

                if (fonction() ) {
                }

                • [^]Re: Ma réponse :

                  Posté par Batchyx () le 15/12/2007 à 09:36. (lien). Évalué à 2.

                  En même temps, ça parait logique lorsqu'on retourne des pointeurs :)

                  [^]Re: Ma réponse :

                  Posté par left () le 15/12/2007 à 12:54. (lien). Évalué à 2.

                  Je parlais de petites lib pas très connues comme la libc: c'est généralement une valeur -1 qui est retournée en cas d'erreur. et 0 en cas de succès (quand ce la a du sens, pas quand il faut retourner un descripteur de fichier). Genre chown, ioctl (dans la majorité des cas), shutdown, sysctl, syscall, etc. Tout n'est question de convention et d'homogénéité, et on peut faire l'inverse si on veut.

                  • [^]Re: Ma réponse :

                    Posté par Sylvain Sauvage () le 15/12/2007 à 13:26. (lien). Évalué à 2.

                    Vi, vi, m’a gouru pour la libc (qui est le principal utilisateur de errno). Si je l’oublie si facilement, c’est parce que je fais comme tout le monde : je ne vérifie jamais la valeur de retour :oP

                    [^]Re: Ma réponse :

                    Posté par Antoine () le 15/12/2007 à 20:55. (lien). Évalué à 2.

                    Tout n'est question de convention et d'homogénéité, et on peut faire l'inverse si on veut.

                    Oui, le problème c'est qu'il n'y a vraiment pas homogénéité dans les conventions d'une lib à l'autre.
                    D'où l'intérêt des exceptions qui permettent que tout soit clair sur la signification du retour.

              [^]Re: Ma réponse :

              Posté par Obsidian () le 14/12/2007 à 23:51. (lien). Évalué à 6.

              Non, y a toujours moyen de faire pire :

              16:39 on m'a demander de debugger un prog C qui marche pas, pour faire simple j'ai remplacé tous les malloc( x) par des mallox( x * 100 ) ... et bien maintenant ça marche ... aller Zou ! au suivant !


              http://quadaemon.free.fr/tribune.fortune

            [^]Re: Ma réponse :

            Posté par left () le 14/12/2007 à 20:19. (lien). Évalué à 5.

            Il existe d'autre modèle que les exceptions ou le retour de message d'erreur ?

            Oui, en Common Lisp notamment ou la gestion des erreurs va beaucoup plus loin et permet notamment la gestion des erreurs sans destruction de la pile d'appels. Il y a un article interessant à ce sujet sur wikipedia: http://fr.wikipedia.org/wiki/Syst%C3%A8me_de_gestion_d'excep(...)

            [^]Re: Ma réponse :

            Posté par Antoine () le 14/12/2007 à 21:23. (lien). Évalué à 8.

            Oui, PHP affiche un warning débile à l'écran (en espérant que quelqu'un le voie) et continue son exécution comme si de rien n'était.

            • [^]Re: Ma réponse :

              Posté par Juke (Jabber id, page perso, ) le 15/12/2007 à 08:46. (lien). Évalué à 4.

              ça depend comment tu l'a configuré.

          [^]Re: Ma réponse :

          Posté par Pierre Tramonson () le 14/12/2007 à 16:49. (lien). Évalué à 4.

          Le principe des exceptions en soit n'est pas mauvais, c'est juste que ça reste très chiant à mettre en oeuvre (dans le sens où dans un projet relativement gros ça prends du temps, et qu'en général tu as autre chose en tête).

          C'est quelque chose qu'il faut absolument prévoir dès le début.
          Personnellement je conseille de créer au moins 2 types d'exceptions :
          * exceptions de type fonctionnel : en cas d'impossibilité de conclure sur une règle de gestion, ou valeur non permise et qui risque de provoquer des erreurs graves plus loin.
          * exceptions de type technique : pour signaler les problèmes i/o, connexion, timeout...

          Avoir ces 2 classes permet de logger différement (2 fichiers, ou 1 fichier et 1 base de données) les 2 types de problèmes, gràce à la souplesse de log4j.

          Ensuite suivant la complexité du projet il est utile de détailler plus finement la hierarchie des exceptions. Par exemple on peut très bien avoir envie de signaler dans une table de BDD des exceptions fonctionnelles corerspondant à une règle bien précise, et jeter tout le reste dans un fichier de log.
          Typage des exceptions + LOG4J = ROXOR \o/

          • [^]Re: Ma réponse :

            Posté par TImaniac (page perso, ) le 14/12/2007 à 21:42. (lien). Évalué à 2.

            Je rajouterai que les exceptions techniques doivent proposer une "alternative" sous la forme d'un test. Exemple dans le framework .NET :
            int Parse(string str) renvoi potentiellement une exception (à utiliser par défaut si on ne veut pas gérer le cas où la chaîne ne contient pas d'entier).
            bool TryParse(string ref, out int value) qui retourne un booléen à la place d'une exception. Cela évite la construction "lourde" try/catch et surtout évite de lever une exception, ce qui peut être coûteux à l'exécution (faut chopper tout le contexte dans la pile des appels qui ont conduit à l'exception).

            Bref l'exception doit rester exceptionnel.

            • [^]Re: Ma réponse :

              Posté par thedidouille () le 15/12/2007 à 11:24. (lien). Évalué à 2.

              Attention à propos des exceptions, autant en Java et en C# ça pose pas de problèmes, autant en C++, ça empêche d'utiliser "-Bstatic" et ça impose le RTTI. Je sais pas comment fait Qt pour gérer les erreurs, mais ils y arrivent sans les exceptions, alors il doit y avoir une solution à ce problème satisfaisante.

              • [^]Exceptions et RTTI.

                Posté par Claude SIMON (page perso, ) le 16/12/2007 à 17:24. (lien). Évalué à 2.

                De quel compilateur s'agit-il ?

                Avec Visual C++ (8.0), j'utilise les exceptions tout en ayant désactivé le RTTI. Quand j'utilise g++, je ne précise pas d'options ayant trait au RTTI, donc j'ignore si c'est activé ou non, ni même si cela existe sous g++.

                Si on ne dispose pas des exceptions, on peut arriver à les simuler à l'aide de la bibliothèque C 'setjmp.h'. C'est peut-être ce qui est utilisé pour Qt ...

                --
                Projet Epeios (http://zeusw.org/epeios/) :
                Bibliothèques C++ généralistes et dédiées.
                • [^]Re: Exceptions et RTTI.

                  Posté par thedidouille () le 17/12/2007 à 12:43. (lien). Évalué à 2.

                  tu as raison, pour g++, on peut à priori utiliser "-fno-rtti" avec des exceptions ... Le compilateur dont je parlais était Forte 6u1 et SS10, en mode "compat4".

                [^]Re: Ma réponse :

                Posté par Gof (Jabber id, page perso, ) le 19/12/2007 à 15:54. (lien). Évalué à 2.

                > Je sais pas comment fait Qt pour gérer les erreurs

                Il y a pas d'erreurs dans Qt. :-)

                --
                :-D !!!NOUVEAU!!!

    [^]Re: Ma réponse :

    Posté par Erwan (page perso, ) le 14/12/2007 à 17:40. (lien). Évalué à 3.

    Ah, les erreurs, c'est pas forcément les tiennes :)

    Exemple 1: les erreurs de serveurs extérieurs.
    Tu fais un appel XML-RPC à un serveur qui te renvoie une page HTML pour te dire "en cours de maintenance". (ça m'est arrivé plusieurs fois avec Wordpress.com). Boum, le parser XML !

    Exemple 2: les erreurs de tes collègues
    Ton collègue Pierre vient te voir: "Y'a un bug dans ton module dans ton module foo". Après une heure de recherche, tu te rends compte que Pierre a appelé ton module avec un nombre négatif, alors que ça n'a de sens qu'avec des nombres positifs. Si tu avais utilisé un assert(), le programme aurait crashé simplement avec un message clair que tu as écrit à l'attention des collègues qui utilisent mal ton code.

    Alors à part ça, pour le premier exemple : à Flock on a défini une classe flockIError adapté aux erreurs qui viennent du serveur. Ça nous permet de mettre un code d'erreur serveur, un message d'erreur serveur, un code d'erreur Flock (unifié entre les services), et un message d'erreur Flock. Le message d'erreur Flock est localisé, comme ça on peut le montrer à l'utilisateur ("mauvais mot de passe", "serveur indisponible", "permissions insuffisante"...).
    http://lxr.flock.com/source/flock/mozilla/flock/base/common/(...)

    Parallèlement à ça, on a un logger qui a différents niveaux : erreur, warning, info, debug.

    Pour le deuxième exemple, l'astuce c'est de mettre tout tes assert() dans du code de préprocesseur. Comme ça quand tu développes le moindre bug provoque un crash (c'est mieux pour débugguer) mais pour les releases tu changes les options de compilation pour désactiver les assert() et les erreurs ne provoquent plus de crash.

    Un peu dans cette idée, pour Flock les exceptions Javascript (Flock est principalement développé en Javascript) qui ne sont pas capturées provoquent une boite de dialogue alert(). C'est très agaçant, et ça donne envie de réparer l'exception très vite :).

    • [^]Re: Ma réponse :

      Posté par zul (Jabber id, page perso, ) le 14/12/2007 à 19:18. (lien). Évalué à 3.

      C'est là où on aimerait retrouver toutes les facilités d'eiffel pour ce genre de chose. (on se demande même pourquoi si peu de langages permettent d'implementer facilement des contrats : on arrive a peu pres à écrire des préconditions, mais pour les post-conditions, et les invariants de boucle c'est un peu tendu).

      Le principe d'exception permet à mon avis de résoudre assez facilement le problème de signaler les erreurs. La difficulté je trouve c'est souvent quoi faire de l'exception ? Si on la catche tout de suite c'est presque aussi chiant que la gestion d'erreur classique. Si on le fait trop loin de ce qui l'a déclenché, c'est difficile de rattrapper le coup (que faire dans ce cas là à partir dire 'y'a une erreur').

      Typiquement, une erreur d'allocation, on pourrait attendre deux secondes, faire le ménage dans le GC et retenter l'allocation mais ca sous-entend des choses assez lourdes derrière pour reprendre le fil de l'execution au bon endroit.

      • [^]Re: Ma réponse :

        Posté par left () le 14/12/2007 à 20:54. (lien). Évalué à 2.

        La programmation par contrat c'est bon, mangez en.
        Ceci dit, dans des langages objet, leur utilisation en cas d'héritage + polymorphisme devient obscure. La syntaxe de Eiffel en témoigne (require else et consort). Pour autant, je ne vois pas en quoi c'est plus tendu pour les post-conditions et les invariants de boucle. Autant j'ai un problème avec les invariants de classe (on fait comment quand une instance de la classe a un invariant cassé puisqu'on ne peux plus invoquer de méthodes de cette instance sans rompre le contrat de l'invariant), autant je n'en ai pas plus avec les post-conditions (contrat de sortie) et les invariants de boucle (contrat local, a priori pour le debug?) qu'avec les pré-conditions (contrat de sortie).

        • [^]Re: Ma réponse :

          Posté par Mildred (Jabber id, page perso, ) le 15/12/2007 à 12:00. (lien). Évalué à 2.

          La programmation pr contrats, c'est très bien mais insuffisant pour différents types d'erreurs:
          - les erreurs inprédictibles. Typiquement d'entrée/sortie (la clef USB a été enlevée sauvagement, le réseau est coupé ...) et d'allocation mémoire
          - les erreurs qui peuvent se déclencher selon des paramètres variables au cours du temps. Dans un système concurrent, comment s'assurer qu'un container ne soit pas plein ? Solution: une section critique

          Pour les erreurs prédictibles, c'est l'idéal, sinon, il faut trouver une autre solution. Et la solution n'est pas forcément un crash du programme. Par exemple si le réseau est déconnecté, je veux un joli message d'erreur ... et je veux que l'application continue a s'exécuter (si elle a quelque chose à faire)

          • [^]Re: Ma réponse :

            Posté par left () le 15/12/2007 à 12:58. (lien). Évalué à 3.

            En effet, alors je précise: la programmation par contrat c'est bien pour détecter les erreurs des programmeurs. Ça n'aide en rien à la résolution des erreurs externes genre de disque plein, de pb réseaux, etc.

            • [^]Re: Ma réponse :

              Posté par sanao () le 15/12/2007 à 15:09. (lien). Évalué à 3.

              Il y a aussi des erreurs ingérables que l'on arrive à gérer : http://www.beunited.org/bebook/The%20Kernel%20Kit/System.htm(...)

              Alors l'excuse de la coupure d'alimentation cela me fait doucement rire!

              • [+] [^]Re: Ma réponse :

                Posté par left () le 15/12/2007 à 20:24. (lien). Évalué à -1.

                T'as oublié le smiley.

              [^]Re: Ma réponse :

              Posté par Antoine () le 15/12/2007 à 20:56. (lien). Évalué à 3.

              En effet, alors je précise: la programmation par contrat c'est bien pour détecter les erreurs des programmeurs.

              Les erreurs simples d'utilisation d'une API peut-être. Les erreurs subtiles de sémantique, ça m'étonnerait, ou alors ça revient à réimplémenter une deuxième fois le programme sous forme de contrats (double-checking intégral).