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 ?
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 #890974.



le mieux est l'ennemi du bien
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
"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
C'est vrai ... Mais en même temps ça dépend un peu du type de programme qu'il développe.
La Roue du Temps
[^]Re: le mieux est l'ennemi du bien
é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
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
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
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.
[^]Re: le mieux est l'ennemi du bien
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 tout ce code a été perdu à cause d'un crash disque et d'un backup défectueux... C'est assez cocasse somme toute :)
[^]Re: le mieux est l'ennemi du bien
tout n'est pas perdu meme si il ne reste pas grand chose :
http://search.cpan.org/~mouns/PException-2.4/PException.pm