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

Posté par  (site web personnel) .
Étiquettes : aucune
0
14
déc.
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 ?
  • # Ma réponse :

    Posté par  . Évalué à 2.

    Question : Comment bien gérer ses erreurs ?

    Réponse : ne pas en faire !
    • [^] # Re: Ma réponse :

      Posté par  (site web personnel) . É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  (site web personnel) . É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" ?

        "La première sécurité est la liberté"

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

          Posté par  . É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  (site web personnel) . Évalué à 4.

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

            "La première sécurité est la liberté"

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

              Posté par  . É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  . É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  . É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  . Évalué à 2.

                    En même temps, ça parait logique lorsqu'on retourne des pointeurs :)
                  • [^] # Re: Ma réponse :

                    Posté par  . É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  . É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  . É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  . É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  . É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  . É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  . É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  (site web personnel) . É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  . É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  (site web personnel) . É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 ...

                  Cyberdépendance, cyberharcèlement, pédocriminalité… : Zelbinium, pour que les smartphones soient la solution, pas le problème !

                  • [^] # Re: Exceptions et RTTI.

                    Posté par  . É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  (site web personnel) . Évalué à 2.

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

                  Il y a pas d'erreurs dans Qt. :-)
    • [^] # Re: Ma réponse :

      Posté par  . É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  (site web personnel) . É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  . É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  (site web personnel) . É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  . É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  . É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  . É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).
  • # Vive les exceptions !

    Posté par  (site web personnel) . Évalué à 9.

    De par mon expérience, j'ai noté plusieurs points.

    La gestion d'erreur par « return code_erreur; » (où code_erreur est un nombre négatif, nul ou encore NULL) est inefficace. Le soucis étant qu'il est pénible d'écrire « if (resultat < 0) » ou « if (!result) » après chaque appel de fonction. Testez-vous le résultat de fopen() et de malloc() ? Si oui : très bien. Mais le faites vous pour snprintf(), write(), etc. ? Pour éviter de « polluer le code », on préfère supposer que les erreurs n'arrivent jamais. Le plus pénible est de diffuser l'erreur est fonctions parentes : si a() appelle b() qui appelle c(), et qu'une erreur intervient dans c() : il faut transmettre l'erreur à b() puis à a().

    Du coup, les exceptions sont une vraie révolution pour la gestion d'erreur : toutes les erreurs sont remontées. Au pire, le code gérant l'erreur peut l'ignorer, mais dans ce cas, c'est volontaire. De plus, les erreurs sont typées et on peut créer une hiérarchie des types d'exception. Ceci permet d'écrire une gestion d'erreur générique.

    Enfin, on peut décaler (déplacer) la gestion d'erreur : pour reprendre mon exemple précédent, l'erreur dans c() n'a pas besoin d'être transmis par b() ou a(), c'est automatique. De plus, on peut très bien écrire un gestion d'erreur très générique dans la fonction main() puis des gestions plus ciblés autour d'appels plus spécifiques. On ne va pas s'occuper des erreurs MemoryError après chaque appel de fonction, on va le gérer une seule fois dans main() par exemple.
    • [^] # Re: Vive les exceptions !

      Posté par  (site web personnel) . Évalué à 6.

      Ah j'oubliais un truc.

      Il ne faut pas considérer les erreurs comme un cas particulier et les mettre de côté. Il faut considérer une erreur comme un code/cas normal. Je vois pas trop comment expliciter mon idée correctement.
      • [^] # Re: Vive les exceptions !

        Posté par  (site web personnel) . Évalué à 1.

        Après avoir essayé divers approches, je suis retourné au mode "je teste les valeurs de retour" de toute mes fonctions.

        Pour moi, une gestion d'erreur propre, ca commence par de l'information. Avant de savoir ce qu'on va faire d'une erreur, on commence par donner un maximum d'information sur où elle s'est produite et pourquoi. Pour ça, le mieux est d'avoir un bon module de log, qui permet de partitionner les logs en domaines et en niveaux. Si j'ai trois couches d'API, je pouvoir activer ou désactiver le log de chacune des couches indépendamment, passer la couche basse en mode debug si c'est nécessaire. Ca permet de se lacher un terme de log dans les couches basses, sans pour autant inonder complètement les logs. Dans mes programmes à l'heure actuelle, j'ai un niveau de log "deepdebug". Par défaut, mes programmes sont en "debug". En mode "deepdebug", les couches basses sont très très bavardes et je sais tout ce qu'il se passe. Très très pratique.

        Le système de log doit à mon sens :
        - permettre à un client qui n'y connait rien de t'envoyer une trace si tu le lui demandes
        - être suffisamment détaillé et précis pour que tu puisses débugger ton programme sans lancer un debugger.

        Vu la qualité plutôt minable des divers debuggers sous Linux, c'est de toute façon intélligent de miser sur un bon système de log.

        Avec les logs, tu sais maintenant que tu pourras traiter correctement ton erreur a posteriori.

        Maintenant que faire de l'erreur quand elle se produit ? En regardant les programmes que j'ai écrit, je m'aperçois qu'il est exceptionnel qu'une erreur ne soit pas absolument bloquante. Dans mon cas, une erreur signifie que le programme ne peut plus s'exécuter. Donc le mieux est de remonter l'erreur vers les couches hautes pour en informer l'utilisateur et le laisser corriger le problème.

        Les exceptions permettent de s'affranchir du problème localement, en disant : "je traiterai cette erreur dans les couches hautes". Personnellement, je trouve que ce n'est pas une bonne approche parce que ça encourage à ne pas traiter le problème. C'est vite fait d'oublier dans les couches hautes qu'il pouvait y avoir l'exception X dans les couches basses. Ou bien on finit par attraper toutes les exceptions indifféremment mais alors le message de l'exception peut être cryptique.

        Exemple : tu reçois un IOError sur un programme avant même de toucher à un seul fichier dans un programme. Quel est le problème ? Impossible pour l'utilisateur de bien comprendre donc de résoudre.

        Problème réel: le programme utilise un système de log basé sur des fichiers mais il ne peut créer de nouveau fichier (la partition est pleine) donc il ne peut pas initialiser le système de log. Dans le cas de mes programmes, le message d'erreur sera plutôt "Could not initialise log system : file /tmp/toto.log could not be created."

        Je préfère traiter les erreurs localement parce que c'est là où on peut le mieux les décrire. Et je les gère avec des valeurs de retours. Tous mes programmes ou presque me conduisent à gérer mes propres valeurs et message d'erreur, que j'étends au fur à mesure des erreurs que je découvre. Les code d'erreurs doivent avoir un message correspondant. J'ai souvent des fonctions pour mapper les erreurs typiques de la couche en dessous vers des erreurs de ma propre lib d'erreur.

        Ca fait du code lourd à écrire mais ça fait du code robuste. J'ai horreur du code qui plante sans que tu saches ce qui s'est passé. Le pire, c'est le code qui plante loin dans le programme à cause d'une erreur qui s'est produite au début du programme. C'est typiquement le genre de code que tu mets des heures à debugger parce que tu cherches au mauvais endroit. S'il y a erreur, le programme doit s'interrompre et dire ce qui ne va pas.

        Mon code ressemble souvent à ca :


        jresult some_function( int a, int * b )
        {
        int ret;
        if (a < 0) {
        jlog_err( "my module", "a should > 0 but is %d", a );
        return JERR_INVALID;
        }

        if (b == NULL) {
        jlog_err( "my module", "b can not be NULL");
        return JERR_INVALID:
        }

        ret = some_sub_func( a, *b );
        if (ret != JOK) {
        jlog_err( "my module", "error when calling some_s_b_func");
        return ret;
        }

        ....

        return JOK;

        }


        Cela m'arrive aussi d'avoir des macros en C du type :

        #define TRY( some_call ) { ret = some_call ; if (ret != JOK) { jlog_err("my_module", "calling %s returned error %s", #some_call, error_msg( ret ) } }


        qui s'utilise avec :

        TRY( some_sub_func(a, *b) )


        C'est sur que c'est plus pratique avec des langages qui génère des exceptions et affichent toute la pile d'appel.

        Voila. On peut penser que c'est lourd et que ca fait perdre du temps d'écrire du code aussi verbeux mais je pense au contraire que ça fait gagner du temps car :
        - un programme vite fait finit toujours par durer beaucoup plus longtemps que prévu initialement. Mieux vaut l'écrire proprement du premier coup.
        - quand une erreur se produit, je sais tout de suite où et pourquoi. Et développer un programme, c'est bien passer son temps à comprendre pourquoi le ne réagit pas normalement.


        Un dernier point sur ce qu'on doit faire quand un programme plante réellement. A mon sens, on doit toujours essayer de sauver les meubles et de réduire l'impact du plantage pour l'utilisateur. Par exemple, j'ai utilisé longtemps le navigateur Opéra à une période où il plantait deux ou trois fois par jour. Ca m'est arrivé plein de fois de le voir planter avec 25 onglets ouverts. Horreur ! Sauf que quand je le relançais, je retrouvais mes 25 onglets. J'ai perdu 15 secondes à le relancer mais je n'ai à peine été dérangé. Il a fallu très très longtemps à firefox pour envisager de faire de même.

        Il doit y avoir pour chaque programme une réflexion sur ce qui est fondamental pour l'utilisateur de ne pas perdre et un mécanisme sur comment sauver cette information même dans les cas les plus critiques.

        C'est pour ça aussi que je suis plutôt contre les asserts. C'est un truc des fainéant et ça ne permet pas de sauver les meubles correctement.
        • [^] # Re: Vive les exceptions !

          Posté par  (site web personnel) . Évalué à 2.

          Les assert() c'est uniquement pour un codeur. Enfin en théorie...

          "La première sécurité est la liberté"

        • [^] # Re: Vive les exceptions !

          Posté par  (site web personnel) . Évalué à 2.

          D'aileurs pour ton "gros" exemple, ce n'est pas le cas typique où un assert() ferait l'affaire ?

          "La première sécurité est la liberté"

          • [^] # Re: Vive les exceptions !

            Posté par  (site web personnel) . Évalué à 1.

            c'est aussi ce que je pensais en le listant
            En outre, avec des asserts bien codées on pourrait avoir la version beaucoup plus parlante et plus light :

            jresult some_function( int a, int * b )
            {
            int ret;
            assert_positiv(a);
            assert_not_null(b);

            ret = some_sub_func( a, *b );
            assert(ret, JOK);

            ....

            return JOK;

            }


            Les asserts sont justement faite pour ces cas. Les asserts sont typiquement là pour tester les cas qui ne devraient pas exister à l'exécution.
            Dans cet optique, une assert doit nécessairement provoquer un crash de l'appli, être bloquante car signale un bug, une erreur qu'il faut obligatoirement résoudre.
            Rien n'empèche par contre, de loguer dans l'assert, juste avant le "exit".

            Pour ma part, je n'utilise pas beaucoup les exceptions (ça dépend aussi des framework) mais je pense qu'un bon traitement des erreurs est multiple :
            - des assert pour les entrées / sorties de fonctions. Tout ce qui doit être valide mais sans aucune action avec l'utilisateur (typiquement un pointeur ne devant pas être null). Et ça doit crasher, les asserts doivent crasher pendant le dev et ne seront de toute manière pas compilées (ou seulement le log sera compilé)
            - des tests "classiques" avec affichage pour tout problème que l'utilisateur peut contrôler
            - des exceptions, des try/catch au milieu permettant de faire remonter les problèmes (très pratique lorsque l'erreur survient dans une routine mais que le traitement de celle-ci ne se fera que dans des couches plus haute)
            • [^] # Re: Vive les exceptions !

              Posté par  (site web personnel) . Évalué à 2.

              Et ça doit crasher, les asserts doivent crasher pendant le dev et ne seront de toute manière pas compilées (ou seulement le log sera compilé)

              ça me laisse toujours un peu perplexe de virer les asserts en production, qu'est ce qui se passe dans l'eventualité ou un bug est passé a travers ? Au lieu d'avoir un plantage bien franc tu auras au mieux un bon crash bien net, au pire des données corrompues difficiles à identifier. Alors pourquoi ne pas toujours laisser les assert ? L'argument du gain de performances me semble très contestable dans l'immense majorité des cas.
          • [^] # Re: Vive les exceptions !

            Posté par  (site web personnel) . Évalué à 1.

            Un assert, ca arrete l'application comme une grosse brutasse. Il y a des applications ou c'est le comportement desiré, mais dès qu'il y a un utilisateur en face, c'est pas une bonne idée. Un beau message d'erreur disant le module dans lequel s'est produit l'erreur est quand même bien plus informatif. Il peut y avoir aussi du nettoyage à faire en cas de plantage (cf ce que je dis à la fin de mon message).

            Autre inconvenient de l'assert : compare les messages d'erreurs suivants :

            v1:
            ASSERTION FAILED at file toto.c line 38

            v2:
            ASSERTION FAILED at file toto. c line 38 : a >= 0

            v3:
            CardCommunication Error : some_func() : a should be > 0 but is -3

            Les grands fans des asserts sont en général des fainéants et dans 80% des cas, on se retrouve avec des message type v1. Tracer le problème est un peu lourd.

            Dans quelques cas chanceux, on est en v2.

            Dans mes programmes, on est en v3. Sachant que j'ai par ailleurs tout le log de l'execution, je suis capable de diagnostiquer le problème beaucoup plus rapidement. Et c'est bien car en général, quand on cherche un problème, on est pressé.

            Tu peux regarder ça pour voir ce que ça donne en pratique :
            http://svn.yzis.org/filedetails.php?repname=Yzis&path=%2(...)

            Je suis sur que les fans des asserts auraient écrit un code 3 fois plus petit. Mais je pense que le mien est beaucoup beaucoup mieux avec des messages d'informations et des messages d'erreur qu'avec des asserts.
            • [^] # Re: Vive les exceptions !

              Posté par  (site web personnel) . Évalué à 1.

              mais c'est normal que ça arrête l'application comme une grosse brutasse !
              l'assert (et je répond ici au commentaire du dessus) ne doit pas être compilée en release.
              Une assert ne correspond pas à tous les cas d'erreur mais doit permettre, lors du développement, de tester les pre et post conditions des fonctions.
              Si une assert apparaît, alors on est clairement dans un cas où on aurait jamais du être et pour lequel on a pas de sortie propre possible (ex d'une fonction devant retourner tout le temps un pointeur ... sauf que le pointeur, pour une raison indépendante de cette fonction, est nul)
              Pour les autres cas il y a les autres gestion d'erreurs.
              Il faut, à mon avis, vraiment séparer les types d'erreurs et leurs traitement.

              Maintenant, concernant les logs, rien n'empèche (et c'est ce que je fais en général) d'avoir des fonctions prenant des messages en parametre :
              a = my_func();
              assert_not_null(a, "my_func return a bad pointer plop plop plop");

              Enfin, oui, l'assert ne doit plus exister en release, et ce pour plusieurs raisons :
              d'une part le code d'assert termine normalement par un exit donc ça crash forcément. D'autre part l'assert étant placée pour tester un cas n'apparaissant normalement pas, l'application crachera de toute manière juste après ou un peu plus loin, ou beaucoup plus loin mais on ne saura jamais pourquoi...

              Et oui, niveau perf le gain peut être important. En utilisant les asserts comme pre/post conditions des fonctions (de la majorité) il devient évident que supprimer de quelques à une dizaine de tests par fonction peut avoir des impacts sur la performance globale du programme.

              Mais ce que j'ai l'impression en lisant les commentaires des "anti" assert est qu'ils croient qu'il n'y a qu'une façon de traiter les erreurs alors que la méthode "parfaite" n'est qu'un mix de toutes les autres.

              ps : non, les fan d'assert n'auraient pas forcément écrit un code plus petit, mais peut être plus grand justement, du fait d'en coller partout où on ne metterait pas un test car on penserait ça inutile alors que...
              • [^] # Re: Vive les exceptions !

                Posté par  (site web personnel) . Évalué à 2.

                d'une part le code d'assert termine normalement par un exit donc ça crash forcément. D'autre part l'assert étant placée pour tester un cas n'apparaissant normalement pas, l'application crachera de toute manière juste après ou un peu plus loin, ou beaucoup plus loin mais on ne saura jamais pourquoi...


                Si la fonction assert affiche une belle boite de dialogue (c'est ce qui se passe sous windows par ex) indiquant que "assertion failed dans toto.c ligne 42 sur l'expression prout==0" alors l'utilisateur qui vient de voir son appli exploser en plein vol va t'envoyer un mail ou tout surpris il t'expliquera avoir reçu un message incomprehensible à propos de prout à la ligne 42. Et si t'as de la chance, tu peux identifier le bug sans sortir de logs ou quoi que ce soit. C'est quand même mieux qu'un programme qui crash sans rien dire je trouve
            • [^] # Re: Vive les exceptions !

              Posté par  (site web personnel) . Évalué à 2.

              Je programme énormément avec les assert, mais en le faisant de manière intélligente. Les assertion ne sont pas limitées à ce que fournit <assert.h> c'est à dire pas grand chose.

              Dans mon code, il y a toujours différents types d'assertions. Elle prennent toutes au moins une condition à tester et un méssage d'erreur ce qui donne :

              assert_fail(a < b, "error in library toto, invalid parameter for bipbip");

              Ca c'est le cas de base, en général il y a plusieurs niveau d'assertion, ce qui me permet de définir qu'elle assertions je veux désactiver à la compilation.
              Ensuite, j'utilise beaucoup des assertion avec valeur de retour du type :

              check(ptr != NULL, "cannot allocate memory in library toto", ERR_MEMORY);

              qui va faire le test et en cas d'erreur logger le message et faire un return ERR_MEMORY. (Ce n'est plus vraiment une assertion ici car il y a un effet de bord, mais je le met quand même dans la même catégorie car l'utilisation est globalement la même. La seule différence c'est que en général je passe deux tests différents, le premier est complet mais lent et fait en mode debug, le deuxième est plus rapide et fait en mode perf, dans tous les cas les check ne sont jamais désactivés.)
              Au final, ce que je fais reviens au même que toi sauf que je cache toute la merde dans un fichier include qui contient un max de macros pour gérer plein de cas et logger les messages et/ou les afficher dans la console. Se système à l'avantage de garder un code plus agréable à lire à mon gout, et surtout me permet de facilement désactiver les assert suivant le type de compilation.

              Au sujet du désactivage pour la version finale, je comprend que cela puisse en choquer certains, mais pour moi c'est indispensable dans mon code. J'écrit notament des programmes qui font des calculs mathématiques très lourds et complexes. Pour garder le truc relativement simple à comprendre et à maintenir, je découpe en de nombreuses sous fonction qui sont simple à documenter, que l'on comprend facilement donc débuggage beaucoup plus facile.
              Pour toutes ces fonction je test tous les paramètres en entrées et je fais des vérifications en sorties pour vérifier au maximum que tout ces bien passé.
              Ces vérifications sont extrèmement couteuses et si je laisse tourner mon programme au lieu de tourner pendant deux semaines, c'est dix ans qu'il va falloir que j'attende.
              J'utilise des vérifications très complètes pendant le dévellopement, mais pendant les phases de calcul, je les réduit au minimum, et chez moi ça ce fait juste en faisant un "make perf" au lieu d'un "make" ou "make debug".
    • [^] # Re: Vive les exceptions !

      Posté par  . Évalué à 1.

      On ne va pas s'occuper des erreurs MemoryError après chaque appel de fonction

      Tu dis ça parce que tu utilises un langage avec un ramasse miette ;)
      • [^] # Re: Vive les exceptions !

        Posté par  . Évalué à 3.

        Le fait d'avoir un GC n'y change rien : si les données ne tiennent pas en mémoire, il n'y a plus qu'à abandonner. Donc traiter l'erreur ne sert à rien : autant laisser l'exception remonter la pile d'appels jusqu'à entraîner la terminaison du programme.
        • [^] # Re: Vive les exceptions !

          Posté par  (site web personnel) . Évalué à 2.

          Dans un monde parfait ou le GC fait parfaitement son boulot, oui :)
          Dans le monde réel, demander à la GC de faire le ménage permet parfois/souvent (dependant du motif d'allocation) de réussir son allocation.

          Et puis tu peux vouloir evacuer certaines données qui ne sont pas si utiles que ca pour le calcul (même si pour la précision, tu aurai préféré les avoir).

          Le problème n'est pas si évident.
        • [^] # Re: Vive les exceptions !

          Posté par  . Évalué à 2.

          Il y a beaucoup de cas ou tu peux faire du ménage dans ta mémoire. Par exemple tu peux maintenir un cache d'objet le plus gros possible et quand tu arrives au bout de la mémoire, ou une itération du GC, tu fais le ménage selon une politique donnée. Dans les cas simple tu utilises des weak references et autrement tu le fais a la main.

          De même utiliser les assert pour vérifier les paramètres publiques est une erreur. Premièrement les assertions peuvent être activées ou... désactivées. Deuxièment dans beaucoup de langages il y a un contrat pour les méthode publiques qui est qu'elles doivent vérifier leurs arguments et remonter à l'appelant un exception bien définie si il n'est pas satisfait. Enfin terminer brutalement un thread, c'est ne pas permettre aux fonctions appelantes de faire le ménage pour se retrouver dans un état consistant.
          • [^] # Re: Vive les exceptions !

            Posté par  . Évalué à 3.

            De même utiliser les assert pour vérifier les paramètres publiques est une erreur.

            Les assert() servent typiquement à vérifier le fonctionnement interne d'un programme en insérant des tests de conformité qu'on ne veut pas exécuter dans la version de production (parce que trop coûteux). Effectivement ils doivent se limiter à ça, et surtout pas à gérer la vérification des paramètres fournis par l'utilisateur ou un développeur tiers, ni le bon fonctionnement d'un dispositif d'entrées/sorties.
            • [^] # Re: Vive les exceptions !

              Posté par  . Évalué à 2.

              L'utilisation des assert ca dépend pas mal des gens, c'est comme les conventions de la valeur de retour de fonctions.

              Je considere aussi qu'arreter l'execution d'un programme sur une erreur utilisateur est un poil barbare et pas toujours voulu.

              Les assert simplifie beaucoup la gestion des erreurs, je les utilisent pour tester les conditions internes d'un module (ca inclus les donnée fournit par un développeur tiers) Si l'assert fail c'est que le truc est mal codé.


              Si la fonction (pour des raison d'efficacité/lourdeur) ne renvoie pas de code d'erreur, il est aussi attendue que les paramètres sont vérifié/correct par l'appellant, et la ca ce regle a coup d'assert.

              De plus dans le cycle de développement ca peut aider pas mal au début :
              toute mauvaise action arrete tout tout de suite, c'est barbare mais les fondamentaux marche plus vite (et ca evite de voire : l'erreur vue le mardi vient d'une action invalide de 2 jours avant)

              Tout bon framework devrais permettre d'avoir plusieurs réaction : rien, log, kill du programme, restart de la machine.

              En production le programme est tué avec un log, car de toute manière tout va partir en sucette si on continue

              c'est valable pour du C, parceque sinon les exception c'est un poil plus flexible pour la gestion d'erreur.
  • # Proprement ...

    Posté par  . Évalué à 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..."

    Parce qu'il y a des programmeurs qui font autrement ? Oh les sagouins !
  • # Suffit de demander

    Posté par  (site web personnel) . Évalué à 4.

    Trouvé sur le Daily WTF :

    void failIfNull(Object o) throws RuntimeException {
    if (o == null)
    {
    throw new RuntimeException(o.getClass().getName() + " is null!");
    }
    }
    • [^] # Re: Suffit de demander

      Posté par  . Évalué à 1.


      Python 2.5.1 (r251:54863, Sep 13 2007, 09:06:49)
      [GCC 4.2.1 20070828 (prerelease) (4.2.1-6mdv2008.0)] on linux2
      Type "help", "copyright", "credits" or "license" for more information.
      >>> class InfiniteException(Exception):
      ...       def __init__(self):
      ...           raise InfiniteException()
      ...
      >>> raise InfiniteException()
      Traceback (most recent call last):
      File "", line 1, in
      File "", line 3, in __init__
      [ ... beaucoup de lignes plus loin ... ]
      File "", line 3, in __init__
      RuntimeError: maximum recursion depth exceeded
      >>>
    • [^] # Re: Suffit de demander

      Posté par  (site web personnel) . Évalué à 1.

      Dans le même genre : http://thedailywtf.com/Articles/Its-Like-Calling-Assert.aspx

      pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

  • # Programmation par aspects ?

    Posté par  . Évalué à 2.

    Soit un type de données. Sur ce type de données, on va effectuer toute une série d'opérations. À chaque opération, on doit vérifier que l'instance passée en paramètre respecte certaines règles (contraintes du domaine d'application ou contraintes techniques). Toute cette gestion d'erreur va être mélangée au code fonctionnel (l'algorithme) et éparpillée dans l'API. Si une contrainte change, il faut tout changer, partout...

    Ce sont deux symptômes classiques :
    - confusion ou mélange de problèmes différents dans un même fichier (tangling)
    - éparpillement d'un même problème dans plusieurs fichiers (scattering)

    C'est tout à fait le genre de problème que la programmation par aspects peut régler (disons "mitiger"). D'autres exemples typiques d'utilisation sont le logging ou le contrôle d'accès.

    Disclémeur: je suis une buse en aspect, nul si découvert, toussa...
    • [^] # Re: Programmation par aspects ?

      Posté par  (site web personnel) . Évalué à 2.

      Cela serait plus clair si tu donnais un exemple genre en pseudo code.

      Je comprends bien l'idée d'avoir 2 "plans" pour voir un algo, le cas normal et le cas en erreur. Mais en pratique, je ne vois pas du tout la tronche que cela peut avoir.

      "La première sécurité est la liberté"

      • [^] # Re: Programmation par aspects ?

        Posté par  . Évalué à 1.

        Tu as le programme de base, dans un fichier, et divers « aspects », chacun dans un fichier séparé. Chaque aspect contient le code à insérer, l'endroit où il faut l'insérer et éventuellement des propriétés à vérifier. Lorsque le programme est compilé, il est d'abord « tissé » (woven en anglais), les différents aspects sont insérés automatiquement par un pré-compilateur (un weaver) puis tout ça est compilé.

        Il existe aussi des weaver qui travaillent en niveau byte-code, et même au run-time. Tout ça est encore un peu académique (j'ai jamais vu d'application à large échelle, mais je ne suis pas un expert). Ceci dit, ça ne me semble pas abracadabrant, comme approche... YMMV
  • # glib

    Posté par  (site web personnel) . Évalué à 1.

    vive g_assert, g_return_if_fail, g_return_val_if_fail et GError :-)
    A chaque type d'erreur sa gestion.
  • # le mieux est l'ennemi du bien

    Posté par  . Évalué à 1.

    La gestion des erreurs est quelque chose de très difficile. Les exceptions permettent heureusement d'avoir du code plus propre, mais il reste la difficulté de bien les gérer.

    Toutefois, dans ce domaine la solution parfaite n'existe pas. Afin de rendre le programme robuste, il faut gérer les exceptions, mais intelligemment.

    Moi, je pars du principe de gérer tout d'abord les exceptions de manière grossières, c'est à dire, que tous les comportement exceptionnels mènent à l'arrêt du programme (après log).

    Ensuite, j'essaie progressivement d'améliorer la tolérance du programme afin de gérer de plus en plus de cas. Sachant que si quelque chose n'est pas précisément géré, le programme s'arrêtera a nouveau.

    Et c'est là qu'il est important de savoir ce que l'on peut récupérer ou pas. Le traitement d'exception lui même étant susceptible de lever des exceptions, le code peut devenir trop compliqué.

    De manière générale, je définis une liste d'exception que je ne vais jamais gérer et que je considérerai directement comme menant à l'arrêt du programme. Exemple: problèmes d'io sur le disque ou dans la base de donnée.

    Enfin, la gestion des erreurs ne doit pas être considérée que par rapport à elle-même. Afin d'avoir une gestion simplifiée, il est indispensable de factoriser le programme de manière adéquate et d'arriver à regrouper la gestion des erreurs et ne pas (trop) la mélanger au code fonctionnel.

    A mon avis, l'arrêt est souvent une solution très sage quand on a pas la certitude de pouvoir traîter le problème de manière ciblée. Dans certains cas, on peut envisager un programme externe qui va relancer le programme dès qu'il constate son arrêt (avec un timer également).
    • [^] # Re: le mieux est l'ennemi du bien

      Posté par  . Évalué à 4.

      "De manière générale, je définis une liste d'exception que je ne vais jamais gérer et que je considérerai directement comme menant à l'arrêt du programme. Exemple: problèmes d'io sur le disque ou dans la base de donnée."
      Donc si l'utilisateur a voulu sauvegarder ses données dans un répertoire dans lequel il n'a pas de droits d'écriture, tu décides tout simplement d'arrêter le programme ? Oo

      Ça me semble un peu violent comme solution... C'est un coup à lui faire perdre ses données non sauvegardées, juste pour une erreur de saisie de répertoire.
      • [^] # Re: le mieux est l'ennemi du bien

        Posté par  (site web personnel) . Évalué à 3.

        C'est vrai ... Mais en même temps ça dépend un peu du type de programme qu'il développe.
      • [^] # Re: le mieux est l'ennemi du bien

        Posté par  . Évalué à 2.

        évidemment comme tout le monde sauf toi l'aura j'espère compris, c'était un exemple qui illustrait juste mon propos et je ne parle évidemment pas d'un programme orienté utilisateurs. Je voulais parler de graves problèmes d'io, c'est à dire des écritures qui foirent là ou elles sont censés marcher: disque plein, base de données plus disponible...

        Mais tout dépend du programme, essaie d'extrapoler un peu ce que je dis.

        En résumé: mieux vaut s'arrêter que d'essayer de traîter des choses pour lesquelles on a pas un traitement adéquat sinon le remède peut être pire que le mal.
        • [^] # Re: le mieux est l'ennemi du bien

          Posté par  . Évalué à 1.

          J'extrapole à partir des termes que tu veux bien employer...
          Quand tu dis "je définis une liste d'exception que je ne vais jamais gérer", le travail d'extrapolation est somme toute minime.
          • [^] # Re: le mieux est l'ennemi du bien

            Posté par  . Évalué à 1.

            Ce qui casse les c**** c'est quand on passe 10 minutes à écrire un texte et que quelqu'un sort *1 phrase* mal contextualisée comme étant le centre du propos et en la détournant (involontairement) du sens que voulait lui donner son auteur.

            Alors que mon propose était de parler de l'affinage progressif de la gestion des erreurs en partant depuis des traitements assez brutaux jusqu'à une bonne granularité.

            A cause de toi, mon intervention est nullifiée et ça c'est ennervant.

            Et à propos de ton extrapolation, quelqu'un en dessous a lui compris que je parlais dans cette phrase d'un certain type de programme, donc ce n'était quand même pas totalement impossible de me comprendre correctement.
    • [^] # Re: le mieux est l'ennemi du bien

      Posté par  (site web personnel) . Évalué à 1.

      de l'interet de ce que j'avais fait en perl à savoir un systeme de reprise sur erreur au travers d'un mécanisme de gestion d'exception.

      Cela permettait d'ecrire des choses du genre :

      bla();
      blo();
      bli();

      Et si blo() générait une exception de ce genre, le gestionnaire corrigeait le contexte et comme si de rien était dans blo().

      Ce qui allege le code et sa lisibilité ... en prime, l'on pouvait déclarer des gestionnaires permanents ... ce qui evitait les try{} catch() à gogo si le catch faisait toujours la même chose.

      J'avais meme parlé dans la doc d'un systeme de code transactionnel avec possibilité de rollback de contexte, mais je n'ai jamais publié le code et il fut perdu lors d'un crash disque avec backup defectueux ( comme quoi publier du code, c'est aussi la garanti d'un backup distribué peu onereux ;) ) ... cela se basait sur une réécriture de Safe.
      Cela offrait l'avantage avec le module précedent, de pouvoir rejouer à la volée tout un pavé de code sans avoir à coder de manière descriptive une séquence du genre catch/control/replay_or_fail

      Des mécanismes de ce genre permettent de limiter le risque de corruption de données lors du traitement des erreurs ou lors d'une interruption inopinée de service, et reduisent le risque d'introduction d'erreur dans la gestion d'erreur elle meme.
  • # Tout est dans le shell

    Posté par  . Évalué à 7.

    Moi je les gère comme çà :

    $ programme 2> /dev/null


    Et bien, crois-le ou pas, mais depuis que j'ai adopté cette technique, je n'ai plus aucune erreur ! :-)
  • # Sémantiquement

    Posté par  (site web personnel) . Évalué à 2.

    D'un point de vue sémantique, une erreur qu'est-ce que c'est ?

    Une erreur est un comportement non désiré du programme par rapport à la spécification. Elle peut avoir plusieurs sources :

    - Les données ne sont pas exactement structurée comme prévu
    - Une erreur de logique traine dans le code
    - La conception architectural est mal pensé (bugs les plus graves)
    - ...

    Les erreurs que l'on rencontre dans notre métier, sont essentiellement du à la sémantique opérationelle des langages de programmation que l'on utilise.
    Ils se réduisent tous à une sémantique simple :
    - Transfert d'une donnée en mémoire vers un autre endroit
    - Calcul arithmétique sur une donnée en mémoire
    - Test conditionnel sur l'état d'une donnée en mémoire.

    Avec ça, on fait tout (langage procédurales, objets, fonctionnel, à contraintes, ...

    La logique devient bien vite énorme, et l'équation grandissant, le risque d'avoir un problème non prévu augmente, puisque le nombre d'état augmente tout autant.

    Qu'est-ce qu'une fonction ? Un outil permettant de rendre déclaratif un sous ensemble du programme, de sorte à découper la complexité en petit bout pour pouvoir l'aborder.
    Il arrive souvent que l'on croit que le code est déclaratif, voire commutatif (ie l'ordre d'exécution de 2 fonctions n'est pas important), et se rendre compte qu'en fait, non..

    Tout ça pour dire, que même avec des outils du genre exceptions, contrats, qui ont chacun leur avantages et inconvéniant, on attaquera jamais le noeud du problème.

    Le noeud, c'est de s'élever sémantiquement avec un langage ne se réduisant plus à la sémantique décrite plus haut, mais se rapprochant plus d'un langage de spécification déclaratif.

    Quoi ? J'en parlerai bientôt en ces pages.

    « Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker

    • [^] # Re: Sémantiquement

      Posté par  . Évalué à 1.

      Une erreur est un comportement non désiré du programme par rapport à la spécification.

      RRRRAAAAAAAHHHHH

      Non, les erreurs sont des comportements parfaitement désirés. Ne pas confondre une erreur et un bug. Un comportement non désiré c'est un bug ! Quand on s'attend à un résultat A avec des paramètres valides et que l'on obtient un résultat B ca n'est pas une erreur au sens de la logique du programme, si erreur il y a, elle se trouve dans le code ou (pire) dans le compilateur. Mais il est parfaitement logique, raisonnable et predictible de se prendre un "segmentation fault" dans les gencives quand on déborde de la mémoire allouée par exemple. L'ordinateur fonctionne sur le mode "question stupide, réponse idiote", il faut juste en tenir compte et propager cette logique aux cas que l'ordinateur ne peut pas traiter tout seul ou à ceux qui peuvent poser problèmes plus loin dans le code.
      Par exemple si à la question "quel est votre age" on répond "Michel" il faut que le code génère une erreur parceque ca va avoir des répercussions plus tard, de même à la question "quel est votre nom" une réponse de type "42" bien que ne posant pas techniquement de problèmes doit quand même générer si ce n'est une erreur (après tout les pseudos ridicules sont légion sur le grand ternet), au moins un avertissement.

      Bref les erreurs sont des comportement parfaitement désirées, et gérer les erreurs consiste simplement à rajouter soi-même des exceptions que l'on pourra traiter dans le but d'éviter de se prendre une exception que l'on ne pourra pas traiter (généralement dès que l'OS ou le CPU s'en mêle c'est foutu)
      • [^] # Re: Sémantiquement

        Posté par  . Évalué à 2.

        Bref les erreurs sont des comportement parfaitement désirées, et gérer les erreurs consiste simplement à rajouter soi-même des exceptions

        Et j'ajoute qu'il est bon dans les tests unitaires de vérifier que les exceptions sont bien levées en cas d'erreur.
    • [^] # Re: Sémantiquement

      Posté par  . Évalué à 2.

      Le noeud, c'est de s'élever sémantiquement avec un langage ne se réduisant plus à la sémantique décrite plus haut, mais se rapprochant plus d'un langage de spécification déclaratif.

      Hahaha, oui, le bon vieux coup du langage de spécifications qui résoud tous les problèmes de programmation. Le tout avec des super buzzwords d'universitaire. Mort de rire.
  • # log ?

    Posté par  (site web personnel) . Évalué à 2.

    En lisant, les commentaires, j'ai l'impression que votre seul action est de loguer l'erreur dans un but de diagnostique.

    Il n'y a rien d'autres a faire ?

    "La première sécurité est la liberté"

    • [^] # Re: log ?

      Posté par  . Évalué à 1.

      ben ca parle principalement des mechanismes de gestion d'erreur, quoi faire en fonction de l'erreur , :
      - ca depend de l'erreur
      - ca depend du programme
      - ca depend du systeme

      dans un cas faire un beau assert est le bon comportement (voir reboot de la machine), mais c'est pas trop apprécié pour un programme mail.

      l'erreur peut etre attendus/normal, un traitement spécifique pour tel erreur (si le fichier n'est pas present, utiliser le fichier par defaut), il peut y avoir des traitement génériques (rollback).

      bref comment gérer les erreurs, c'est presque du meme niveau que comment écrire sa fonctions (prototype inclus).
  • # Shell scripting

    Posté par  . Évalué à 1.

    Je profite du topic pour demander comment vous gérez les erreurs en shell ? Quelles techniques ? Existe-t-il une bibliothèque de fonctions utiles pour ce genre de cas ? Actuellement j'utilise des système un peu batards qui exploitent les variables standard (genre $? et compagnie) et les traps systèmes mais je trouve cela très "sale" car j'ai toujours cette impression de "réinventer" la roue.
    • [^] # Re: Shell scripting

      Posté par  (site web personnel) . Évalué à 2.

      Yep, j'écris mes programmes shell avec Python. Fastoche.
      • [^] # Re: Shell scripting

        Posté par  . Évalué à 1.

        Ce n'est pas le genre de réponse que j'attendais ;) Si cela ne tenait qu'à moi, il n'y aurait pas de KSH pour les développements que nous faisons, malheureusement, ce n'est pas moi qui décide et surtout KSH est bien ancré dans les mentalités (plus de 10 ans que tout est comme ça côté Unix) :)
        • [^] # Re: Shell scripting

          Posté par  (site web personnel) . Évalué à 2.

          C'est les memes personnes qui utilisent encore csh ou ksh de base au lieu de bash ou zsh ? :)

          A part pour gerer les fichiers eux meme, dans les repertoires, a coup de "find" ou de "for file in *", le programation shell devrait etre considere comme de la torture et banni comme tel (sauf pour les maso, il y en a toujours).

          On fait bien plus propre/performant/rapidement avec Perl, Python ou Ruby.

          "La première sécurité est la liberté"

          • [^] # Re: Shell scripting

            Posté par  . Évalué à 1.

            Bah si c'est pour faire du Perl 4, je préfère encore conserver KSH ;)

            Python n'est pas près d'arriver sur nos machines, ruby, n'en parlons même pas ;)
    • [^] # Re: Shell scripting

      Posté par  . Évalué à 1.

      Perso, ma gestion des erreurs dans les scripts se résume souvent à terminer dès qu'un truc non supporté se passe. J'ai pris, avec les ebuilds Gentoo, l'habitude de faire des "do_something || die" un peu partout. Ça coute rien, ça alourdi pas le code, donc faut pas se priver. J'ai souvent aussi des espèces d'asserts du genre :
      [[ -n $foo ]] || die "\$foo aurait déjà due être définie à ce stade"

      Pour des scripts courts, mon implem' de die() se résume à une ligne de ce style :
      die() { [[ $# -gt 0 ]] && echo "!!! $*" >&2 ; exit 1 ; }

      Pour des scripts plus tordus, je m'adapte une des bonnes implèm' existantes, comme celle là (affiche une stack trace, et flingue le processus principal quand je die depuis un sous-shell) :
      http://paludis.pioto.org/trac/browser/trunk/paludis/reposito(...)
      Ne pas oublier un petit shopt -s expand_aliases puisque cette implem' utilise des aliases. Et attention, là on est dans du Bash, et déjà assez loin d'un shell Posix quelconque.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.