OVH Cloud OVH Cloud

De l'usage des exceptions

106 réponses
Avatar
Guillaume Gourdin
Bonjour à tous,

selon ma (modeste) expérience, les exceptions en C++sont très largement sous
utilisées, et on en reste (me semble t-il) toujours à la bonne vieille
méthode du retour d'un code d'erreur. Je suis un peu surpris de cet état de
fait, les exceptions étant supposées apporter une gestion plus performantes
des erreurs. J'ai dons quelques questions: est-il vrai que les exceptions
sont sous utilisées? Quels sont vos retours d'expérience? Ou bien y a t'il
une raison plus profonde à la sous-utilisation des exceptions?

Merci!

- Guillaume -

10 réponses

Avatar
Fabien LE LEZ
On Mon, 06 Jul 2009 13:40:39 +0200, Wykaaa :

D'ailleurs tous les langages "modernes" ont une syntaxe pour
le traitement des exceptions.



Tout comme la plupart des langages (tous ?) ont une syntaxe pour les
codes d'erreurs.
Avatar
Fabien LE LEZ
On Mon, 06 Jul 2009 13:37:37 +0200, Wykaaa :

Et il savoir apprécier le cas où un code d'erreur de retour est
préférable à une exception.





Désolé mais ton message est incompréhensible.



En d'autres termes : de la mesure en toute chose. Il faut savoir
utiliser la technique adéquate suivant le contexte.
Avatar
Wykaaa
Fabien LE LEZ a écrit :
On Sat, 04 Jul 2009 16:22:43 +0200, Wykaaa :

tentative de prendre le cinquième élément d'un tableau qui n'en
comporte que trois, etc.


L'exemple du tableau est plutôt un bug de programmation.



T'es sûr ?

Imagine le cas suivant : j'ai chargé un fichier ligne par ligne dans
un vector<string>. Les spécifications de ce fichier indiquent
clairement qu'il doit contenir 5 lignes. Si le fichier est mal formé,
je ne peux qu'arrêter le programme.

Je pourrais écrire :

if (lignes.size() < 5)
throw quelque_chose;
f (lignes[4]);

Sauf que... la fonction at() fait déjà ça pour moi ! Pourquoi
réinventer la roue ?



Je maintiens ce que j'ai dit. Un débordement de tableau est un bug de
programmation.
maintenant, la façon de traiter les différents types d'exception relève
de la "safe programming" (ce qui consiste, du moins en objet, à ne
jamais délivrer à l'utilisateur d'une classe un objet mal formé).
Avatar
Fabien LE LEZ
On Sun, 5 Jul 2009 03:07:04 -0700 (PDT), James Kanze
:

Elles
apportent en revanche leurs propres problèmes (cherche
« exception safety » sur la toile)



Mais en pratiques, les exceptions apportent ces "problèmes", que le
programmeur les utilise ou pas -- puisque des fonctions de la
bibliothèque standard peuvent en lancer.
Avatar
Fabien LE LEZ
On Sun, 5 Jul 2009 03:07:04 -0700 (PDT), James Kanze
:

Elles
apportent en revanche leurs propres problèmes (cherche
« exception safety » sur la toile)



Mais en pratique, les exceptions apportent ces "problèmes", que le
programmeur les utilise ou pas -- puisque des fonctions de la
bibliothèque standard peuvent en lancer.
Avatar
Wykaaa
James Kanze a écrit :
On Jul 4, 4:22 pm, Wykaaa wrote:
Fabien LE LEZ a écrit :



On Sat, 4 Jul 2009 12:34:35 +0200, "Guillaume Gourdin" :





les exceptions en C++sont très largement sous utilisées, et
on en reste (me semble t-il) toujours à la bonne vieille
méthode du retour d'un code d'erreur.







Il y a plusieurs raisons à ça :





D'une part, les exceptions n'existent pas en C, et leur
support en C++ est "récent". Du coup, les gens qui
programment toujours soit en C, soit en C++ "ancien", on du
mal à s'y faire. De plus, beaucoup de bibliothèques, écrites
en C, ne gèrent pas les exceptions.


Qu'entends-tu par "récent" ?
Ca existait déjà dans le C++ au moins en 1990...



Ah bon. Dans quel compilateur ? Certainement g++. Ni CFront.
Ni Zortech.



Dans mon cours C++ de 90, les exceptions étaient déjà traitées en détail
et les compilateurs des constructeurs (Sun, HP, IBM au moins) les
traitaient. Le compilateur Borland les traitait également.
Remarque : Zortech, c'est avant 90 non ? plutôt 88.

D'autre part, il n'y a pas vraiment de consensus sur
l'étendue d'utilisation des exceptions. Certains pensent
qu'il faut les réserver à des cas vraiment exceptionnels,
qui ne devraient pas se produire lors du déroulement normal
du programme. Exemple : pas assez de mémoire pour créer un
objet, tentative de prendre le cinquième élément d'un
tableau qui n'en comporte que trois, etc.





L'exemple du tableau est plutôt un bug de programmation. Ce
n'est pas une situation exceptionnelle comme le "pas assez de
mémoire".



Exacte. Dans la plupart des applications, il vaut mieux
carrément avorter -- la situation ne peut pas se produire, elle
s'est produite, on ne peut donc plus raisonner sur le code, et
tout ce qu'on fait risque de rendre la situation pire. (Il y a
des exceptions, évidemment. Des jeux, par exemple, mais aussi
probablement certaines services Web non critiques.)



Il faut aussi se préoccuper des systèmes "fault tolerant".

D'ailleurs, par défaut, les iostream ne lancent pas
d'exception en cas d'erreur.





C'est un tort !



Tu rigoles, non ? Tu ne vas pas dire qu'une erreur de format
dans une entrée d'utilisateur est un cas « exceptionnel ». Ou
arriver à la fin du fichier.

En ce qui concerne badbit (erreur dur, erreur d'écriture en
sortie, erreur de lecture physique en entrée), on pourrait en
discuter. Ce genre d'erreur doit être exceptionnelle, et je
crois que l'utilisation d'une exception pourrait se justifier
dans ce cas-là. Mais certainement pas dans les autres cas.



Je pensais plutôt aux erreurs que tu cites. Bien sûr que cela serait
ridicule pour une erreur de donnée d'entrée mais le sujet est "les
exceptions", pas les erreurs, ça c'est un autre sujet.

Note aussi que si la fonction appelante comporte directement
le code de traitement d'erreur en cas d'échec de la fonction
appelée, utiliser un code de retour est plus facile. Les
exceptions deviennent utiles s'il faut remonter de plusieurs
appels de fonctions pour trouver le code de traitement
d'erreur.





Je ne suis pas d'accord avec ton dernier paragraphe.



Il a cependant clairement raison. Quelque chose comme :

if ( someAction() != success ) {
// traiter l'erreur...
}

est bien plus simple à comprendre et à maintenir que quelque
chose du genre :

try {
someAction() ;
// qui sait quoi de plus...
} catch ( errorCode ) {
// traiter erreur concernant someAction
}



Ben non. Il est préférable d'utiliser la seconde forme. Et si on veut
traiter l'exception au plus proche de sa survenue, on ne mettra pas "qui
sait quoi de plus..." dans le "try" !

Même si c'est directement la fonction appelante qui traite
l'exception, il est préférable d'utiliser le mécanisme
d'exception plutôt qu'un code retour.
Pourquoi ?
Parce que le traitement de l'exception est isolé dans le code
(catch) et non disséminé directement derrière l'appel, ce qui
mélange le cas normal et le cas "exceptionnel".



Sauf qu'il a bien dit qu'on parle des cas où l'« erreur » n'est
pas exceptionnelle, et que justement, on veut le traiter tout de
suite ; la gestion de l'erreur est une partie essentielle de
l'algorithme (si l'algorithme est correct), et il faut
l'analyser comme telle. Le problème avec l'exception ici, c'est
justement qu'il exige qu'on sépare physiquement (textuellement,
puisqu'on parle du code source) la gestion de l'erreur de
l'endroit où elle se produit.



Une erreur qui n'est pas exceptionnelle n'est pas une "exception". Ne
pas confondre "erreur" et "exception" (les différentes normes
distinguent ces deux notions).

C'est une question de méthodologie, de lisibilité et de
maintenabilité.



Exactement. S'il faut gerer l'erreur tout de suite,
l'utilisation d'une exception est à proscrire. (Il ne faut pas
oublier qu'en fin de compte, une exception n'est autre chose
qu'un goto maquillé.)



Oui mais c'est le compilateur qui le gère, ce qui fait toute la
différence...
Remarque : un if-else, génère aussi, souvent, un goto en assembleur
ainsi qu'un while, si tu vas par là. Ce n'est pas un argument.

De plus, on peut facilement changer le catch au profit de la
capture d'une exception plus générale que l'exception
spécifique qu'on a "attrapé" dans la première version d'un
logiciel.



Ce qui apporte quoi ?



Des facilités de maintenance quand le contexte du traitement de
l'exception change.
Avatar
Wykaaa
Michael Doubez a écrit :
On 5 juil, 12:07, James Kanze wrote:
On Jul 4, 12:34 pm, "Guillaume Gourdin" wrote:

selon ma (modeste) expérience, les exceptions en C++sont très
largement sous utilisées,


Ça dépend. D'après mon expérience, elles servent souvent trop.


[snip]
Quels sont vos retours d'expérience? Ou bien y a t'il une
raison plus profonde à la sous-utilisation des exceptions?


Mon expérience, c'est, précisement, qu'elles sont sur-utilisées.
Surtout parmi les programmeurs plus jeunes.



AMA il y a aussi une mode de l'expressivité d'un programme (style DSL)
et comme la gestion d'erreur brise la linéarité de l'expression, les
exceptions sont alors vues comme une solution pour /préserver/ la
lecture du programme en segmentant le programme en "logique
d'application"/"gestion d'erreur".

--
Michael



Ce qui me semble un bon usage.
Avatar
Wykaaa
Michael Doubez a écrit :
On 5 juil, 11:41, "Patrick 'Zener' Brunet"
wrote:
Bonjour.

"Guillaume Gourdin" a écrit dans le message
de news 4a4f3042$0$30992$

selon ma (modeste) expérience, les exceptions en C++sont très
largement sous utilisées, et on en reste (me semble t-il) toujours
à la bonne vieille méthode du retour d'un code d'erreur. Je suis un
peu surpris de cet état de fait, les exceptions étant supposées
apporter une gestion plus performantes des erreurs. J'ai dons
quelques questions: est-il vrai que les exceptions sont sous
utilisées? Quels sont vos retours d'expérience? Ou bien y a t'il
une raison plus profonde à la sous-utilisation des exceptions?


En complément aux arguments de Fabien Le Lez, en lisant du code C++
utilisant des exceptions, on voit souvent des choses aussi lourdes que
risquées, qui peuvent donc "discréditer" les exceptions aux yeux de
certains.

Lancer une exception implique de la construire, donc il vaut mieux que cette
partie-là soit garantie.



C'est pour ça qu'il y a la garantie nothrow. Mais dans le cas général,
à moins d'un état grave de manque de mémoire, la construction des
exceptions n'est pas un problème.

Il n'est pas rare de voir des exceptions créées par un new, et pour faire
bonne mesure, on insère tout un tas de diagnostics dedans sous forme de
strings...



C'est du code à la Java.

Tout ça pour traiter un incident tels qu'un échec d'allocation de mémoire ?
Il doit bien y avoir des gens que ça ennuie...



Je prefère une exception à un NULL à vérifier chaque fois. De toutes
façons, je ne sais pas quoi faire si j'ai plus de mémoire, il y a peux
de chance qu'essayer de nouveau marche. Le mieux est encore de revenir
à un état antérieur qui se prétend capable de gérer la situation.


Ensuite, comment passe-t-on cette exception (que "throwe"-t-on) ?
Par adresse, référence ou par copie ?



Pour autant que je sache, on ne peux pas lancer par référence.
Un bonne règle est de lancer par valeur et de catcher par référence
pour éviter le slicing.

La copie est plus naturelle, mais elle implique effectivement de copier tout
ça (donc de mouliner intérieurement du new et du delete).



Je ne comprends pas ce que tu veux dire ici.

Reste à voir ce qu'on en fait à la fin (le catch).

Si le système d'exceptions est bien fait, d'une part on a une hiérarchie
bien claire permettant de cascader les catch spécifiques et de se passer du
catch-all (sauf pour faire un cleanup intermédiaire suivi d'un rethrow).



Si je catche quelque chose, c'est que j'ai prévu de le traiter et que
personne ne pense pouvoir le faire dans une sosu-couche donc je sais
bien ce que je doit catcher.
Un cas spécial est la racine d'un thread où je fais un catch sur
plusieurs hiérarchies pour reporter l'erreur à l'application.

D'expérience, souvent on voit un catch-all avec dedans un printf("oh zut, ça
a foiré") suivi de return(ERROR).



Il y a beaucoup de cas où il est difficile de trouver une façon
gracieuse de gérer l'erreur. S'assurer que l'utilisateur n'a pas perdu
de données et lui donner une idée du diagnostique est un minimum mais
souvent c'est déjà pas mal.

Généralement on est bien embêté parce qu'on se tape pour l'occasion une
librairie plus ou moins exotique et on n'a pas trop le temps de creuser son
fonctionnement intime (c'est même ce qu'on veut éviter - on a la pression
bien sûr):
- Pour le détail des causes d'échec possibles, normalement, on regarde les
retours de la fonction appelée, et on fait un switch avec un default...
- Pour une exception, ça peut venir de plus profond et donc c'est assez
obtus. On ne veut pas en oublier une parce que ça conduit à un plantage et
c'est pas vendeur. Et ça peut arriver par la suite avec une nouvelle version
de ladite librairie. D'où le choix du catch-all équivalent du défault...



Il y a plein de façon d'avoir une bibliothèque mal conçue en dehors
des exceptions.

La gestion des exceptions au bord d'une bibliothèque est sujet à
débat. Mon avis est de n'en laisser passer aucunes sauf les exceptions
standard (allocation ...) et sauf si la logique le justifie (throw
dans un constructeur ou assimilé).

Et donc le sort de l'exception elle-même, on s'en désintéresse (ce qui en
soi "justifie" le passage par copie, car alors on peut espérer que le
destructeur prenne en charge tout ce qui doit l'être).
Moi j'aime bien les exceptions passées par adresse avec une méthode
Release() dedans, et je n'ai jamais de memory leak avec ça, mais je suis un
cas...

Ce qui est amusant, c'est que l'usage des exceptions devrait permettre de
démonter proprement une tentative d'opération, pour laisser au process
maître une chance de corriger la situation (libérer des ressources par
exemple) et réessayer.



Pour moi c'est plutôt de retourner à un état connu dans un cas
exceptionnel.

Si il s'agit juste de ressayer, alors on est dans une stratégie locale
et une valeur de retour aurait dû être utilisée.

Moi j'aime pas trop les logiciels qui disent "au
secours, j'y arrive pas", de plus je fais dans le process automatisé, alors
souvent il n'y a personne pour lire le message.



Un petit mail à l'administrateur, ça fait toujours plaisir :)

En général, on va seulement sortir un message "désolé ça a foiré" puis
avorter. Très souvent, la cause précise de l'erreur n'est pas remontée
jusque-là.

Pour faire ça, il faudrait que la nomenclature des erreurs de chaque
sous-librairie soit définie (et figée) et intégrée dans la nomenclature de
la librairie qui l'utilise, etc.
jusqu'au process maître. Il faudrait aussi
pouvoir retourner un contexte assez détaillé dans la même démarche
encyclopédique. Il faudrait ensuite que process maître ait une stratégie
correctrice pour chaque cas recensé.



Je pense que dans ce cas, il y a confusion entre les logs et le
mécanisme d'exception.

En général on ne se donne pas cette peine, ou on n'a pas de doc utilisable
pour les insides, et donc la mission de diagnostic détaillé de l'exception
devient presque inutile (tout au plus on ramène le complément du message
d'erreur à afficher pour faire plus humain "erreur système lors de l'accès
à: ujk35585.dat").
Ne reste que la lourdeur apparente du mécanisme d'exception par rapport à un
bon vieux return(code), spécialement si on dispose d'un enum de valeurs
génériques (ou l'équivalent en #define) dans un header global (beuâârk).



Tester chaque retour d'erreur tout en sachant que la probabilité
d'erreur dans une sous-couche dont on est même pas conscient à un
effet déprimant (pour moi). Surtout quand la seule chose que je peux
faire est de prévenir la couche au dessus que ça a pas marché.

L'exception a au moins l'avantage d'automatiser cela.


--
Michael



Votre discussion à tous deux est intéressante mais vous m'excuserez de
jouer les trouble-fêtes car si le logiciel en question est du guidage de
missile ou du pilotage de module lunaire ou martien, le
printf("oh zut, ça a foiré") suivi de return(ERROR)
me paraît un peu léger :-)
Avatar
Michael Doubez
On 6 juil, 14:21, Wykaaa wrote:
[snip]
Votre discussion à tous deux est intéressante mais vous m'excuserez d e
jouer les trouble-fêtes car si le logiciel en question est du guidage d e
missile ou du pilotage de module lunaire ou martien, le
printf("oh zut, ça a foiré") suivi de return(ERROR)
me paraît un peu léger :-)



Sauf si la gestion de l'exception permet de faire un reboot soft du
système (plus rapide qu'un fault/reboot hard qui rechargerait le soft
à partir de la ROM + POST ) :)

En pratique, j'ai pas vu d'exemple de logiciel critique utilisant les
exceptions mais je sais que MISRA C++ a des recommandations à ce sujet
(en particulier que la racine du programme doit attraper toutes les
exceptions).

A noter que meme JSF C++ a bannis les exceptions. Le probabilité d'en
trouver dans le code d'une fusée est mince.

--
Michael
Avatar
Michael Doubez
On 6 juil, 14:16, Wykaaa wrote:
Michael Doubez a écrit :



> On 5 juil, 12:07, James Kanze wrote:
>> On Jul 4, 12:34 pm, "Guillaume Gourdin" wrote:

>>> selon ma (modeste) expérience, les exceptions en C++sont très
>>> largement sous utilisées,
>> Ça dépend. D'après mon expérience, elles servent souvent trop.
> [snip]
>>> Quels sont vos retours d'expérience? Ou bien y a t'il une
>>> raison plus profonde à la sous-utilisation des exceptions?
>> Mon expérience, c'est, précisement, qu'elles sont sur-utilisées.
>> Surtout parmi les programmeurs plus jeunes.

> AMA il y a aussi une mode de l'expressivité d'un programme (style DSL )
> et comme la gestion d'erreur brise la linéarité de l'expression, le s
> exceptions sont alors vues comme une solution pour /préserver/ la
> lecture du programme en segmentant le programme en "logique
> d'application"/"gestion d'erreur".

Ce qui me semble un bon usage.



Comme toute bonne chose, tant qu'on en abuse pas. De plus générer une
exception avec suffisament d'information pour être utilisable revient
au même, question flux de lecture du programme.

--
Michael