Gestion des erreurs - C++ - Programmation
Marsh Posté le 21-10-2004 à 10:50:30
exceptions, exceptions et exceptions.
(je dé"velopperais ce soir)
Marsh Posté le 21-10-2004 à 11:12:51
kadreg a écrit : exceptions, exceptions et exceptions. |
Tout a fait d'accord
Marsh Posté le 21-10-2004 à 11:22:11
Bon, comme je connais Kad, et qu'il ne développera jamais, je vais le faire un peu en attendant ce soir.
Les principales raisons pour lesquelles les vieux outils type OpenGl et DirectX n'utilisent pas les exceptions, c'est que:
1. leur code doit être compatible C (qui n'a pas d'exceptions)
2. l'utilisation de setjmp et compagnie est pète-noisettes et peux facilement t'envoyer dans les choux si tu ne fais pas attention
3. petites raisons propres à l'implémentation: certains vieux compilos géraient mal les exceptions, (Game SDK date de 95 quand même...), tous les programmeurs ne savaient pas faire ça proprement, etc.
Quand au fait que l'erreur ne se récupère pas forcément au dessus, c'est très pratique au contraire. Par exemple, là j'ai ça:
DécodeJpeg
DécodeFrame
DécodeScan
DécodeMCU
LisHuffman
LisBits S-R
LisChar = Boom parce que fin de buffer
Bah, mon code est on ne peut plus simple, et ce n'est qu'au top-niveau que je récupère les problèmes liés à la fin prématurée de buffer (comme me disent souvent les femmes : chez moi, tout est au top-niveau ).
D'autre part, les compilos savent très bien générer le code qui leur indique si les choses se sont bien passées dans les fonctions au dessus, où s'il se retrouve dans un bout de code parce qu'une exception s'est passée...
Ceci étant dit, une fonction qui échoue parce que quelque chose n'était pas juste ne devrait pas forcément lancer une exception. Si un fichier ne peut s'ouvrir, c'est limite "prévisible" comme cas. Ce n'est donc pas un cas hors-norme. Donc, pas d'exceptions... Si tu ne peux pas faire un blit parce que la surface n'est pas disponible, pareil: ça risque de ne pas être une exception...
Marsh Posté le 21-10-2004 à 11:24:15
Nyast a écrit : Contexte: je suis en train de travailler sur un moteur 3D, mais la gestion des erreurs commence à devenir assez bordélique. Il est temps d'y remédier mais je suis encore indécis sur la façon de faire.. |
Non.
Nyast a écrit : |
c'est justement le but !
Marsh Posté le 21-10-2004 à 11:24:41
Lam's a écrit : Bon, comme je connais Kad, et qu'il ne développera jamais, je vais le faire un peu en attendant ce soir. |
je développerais ce soir, je ferais un apté
Marsh Posté le 21-10-2004 à 11:26:12
Lam's a écrit : kézako apté ? |
un paté, mais avec une phote de frappe.
Marsh Posté le 21-10-2004 à 11:26:24
Je serais plutôt pour les exceptions, moi aussi..
...mais l'intégration avec les autres languages est PRIMORDIAL pour moi.
Car mon moteur 3D sera sous forme de DLL, et pourra être utilisé en C++, mais aussi C, Java, Visual Basic, Delphi, etc..
Marsh Posté le 21-10-2004 à 12:00:38
Tu comptes donc avoir un mapping C utilisé par tous les langages si je ne me trompte pas.
Et bien au niveau de tes fonctions de mapping, tu attrapes toutes tes exceptions, et tu les convertis en mode de retour d'erreur ou de GetLastError().
Après, préférer le mode OpenGl ou Direct-X, c'est plus une question de goût (et de complexité de l'API que tu fournis) qu'autre chose.
Marsh Posté le 23-10-2004 à 14:34:08
J'utilise un retour d'erreur (erreur eventuellement passée en dernier param de la fonction). Les exceptions ralentissent le code de manière non négligeable (quand les perfs sont critiques), et cela grossi également l'exe.
Au passage, DirectX fonctionne aussi à coup de GetLastError, mais c'est pour avoir la description de l'erreur. Je ne vois pas franchement de différence entre la gestion d'erreurs de DirectX et d'OpenGL.
Marsh Posté le 23-10-2004 à 15:08:39
Panini a écrit : J'utilise un retour d'erreur (erreur eventuellement passée en dernier param de la fonction). Les exceptions ralentissent le code de manière non négligeable (quand les perfs sont critiques), et cela grossi également l'exe. |
C'est au contraire les codes de retour d'erreur qui font gonfler le code de façon exponentielle (sauf si on décide de ne pas tous les tester, évidemment).
Dans l'exemple de Lam's
DécodeJpeg
DécodeFrame
DécodeScan
DécodeMCU
LisHuffman
LisBits S-R
LisChar = Boom parce que fin de buffer
c'est évident. Chaque niveau doit vérifier les codes du niveau inférieur. Pour que la fonction DecodeJpeg renvoie un message en cas d'erreur, il faut faire remonter cette erreur via tous les niveaux intermédiaires, ce qui est à la fois lourd, coûteux en temps d'exécution (pour obtenir une granularité fine, chaque appel de fonction doit être doté d'un switch(...) case), et sujet à des oublis. L'avantage des codes de retour d'erreur et la raison de sa popularité dans l'industrie est qu'il permet d'appliquer une règle simple sans se poser de question : on les teste tous (ce qui signifie en pratique qu'il faut réaliser un cas de test par code d'erreur. Du coup, le travail de test et de documentation s'accroît lui aussi de façon exponentielle, cette méthode a donc un coût non négligeable que l'on ne mesure pas forcément).
Les exceptions, elles, permettent d'éviter tous les niveaux intermédiaires et de traiter une erreur au niveau approprié. la difficulté est d'être rigoureux et d'avoir les idées claires sur ce qui est le niveau approprié, mais c'est le cas pour toutes les gestions d'erreur.
A la rigueur, la version OpenGL me parait bcp plus simple.
Pour ce qui de la supposée lenteur, lire :
http://cpptips.hyperformix.com/cpptips/except_cost3
Marsh Posté le 23-10-2004 à 16:29:23
Le fait que les exceptions ralentissent le code, c'est un vieux mythe, comme le fait que les std::string sont plus lentes que les char * (c'est vrai, mais uniquement sur du code de merde), et que les classes ralentissent le code (ça aussi c'est vrai, sur des vieilles architectures, si tu n'utilises pas l'optimiseur de ton compilo, et et si tu mets tout ton code en virtuel sans queue ni tête).
Je vais pas te faire un cours de compil, mais en gros, tous les appels qui sont au sein d'un try-catch se voient dotés d'une addresse de retour supplémentaire:
Code :
|
Comme ça, si la fonction appellante a envie de lever une exception, elle fait un POP, et elle retourne: ça l'envoie automatiquement au bloc catch. Sinon, c'est la fonction appellante qui enlèvera l'addresse du catch de la pile. Bref, ça coute clairement moins cher que de faire une comparaison. C'est d'ailleurs la raison pour laquelle le switch-case utilise le principe inverse.
Maintenant, il est vrai que le traitement de l'exception en lui-même est couteux (comparer les classes, etc.) C'est la raison pour laquelle il doit être reservé aux...cas exceptionnels. Mais c'est très très loin d'être pénalisant.
Même cette bouse de gcc arrive à générer du code inliné violemment sur Solaris (genre 5 fonctions template imbriquées les unes dans les autres en deviennnent 2 grosses) avec du code d'exception à peu près propre.
(Je dis bouse, parce que il est loin d'être aussi performant que Forte, et il a des bugs vraiment pénibles. Maintenant, le rapport qualité/prix est imbattable, c'est clair).
Marsh Posté le 23-10-2004 à 16:45:10
[citation=880837,0,13]
(Je dis bouse, parce que il est loin d'être aussi performant que Forte, et il a des bugs vraiment pénibles. Maintenant, le rapport qualité/prix est imbattable, c'est clair).
[/citation]
[HS]gcc est loin d'être une bouse, bien au contraire. S'il est moins performant que Forte, il ne te lâche pas régulièrement un "Signal 11" ( = plantage pur et simple du compilateur) sur une bête erreur de syntaxe comme Forte/Sun CC.[/HS]
Marsh Posté le 23-10-2004 à 16:49:33
el muchacho a écrit : [HS]gcc est loin d'être une bouse, bien au contraire. S'il est moins performant que Forte, il ne te lâche pas régulièrement un "Signal 11" ( = plantage pur et simple du compilateur) sur une bête erreur de syntaxe comme Forte/Sun CC.[/HS] |
[HS]Ce qui ne l'empêche pas d'avoir une trad française à mourrir de rire (ceci au lieu de this dans les messages d'erreur), des ABI super mouvante qui fout la merde avedc les lib C++ de tiers, et d'être d'une lenteur effroyable...[/HS]
Marsh Posté le 23-10-2004 à 16:58:04
el muchacho a écrit : [HS]gcc est loin d'être une bouse, bien au contraire. S'il est moins performant que Forte, il ne te lâche pas régulièrement un "Signal 11" ( = plantage pur et simple du compilateur) sur une bête erreur de syntaxe comme Forte/Sun CC.[/HS] |
Toi, t'as utilisé le template repository de Forte. T'es malade ? Tu veux te dégouter à jamais de la programmation ?
Nan, sincèrement, j'utilise g++ à la maison (j'ai la flemme d'installer .net ou bcb), et il est bien, mais on est très très loin des compilos pros sur lesquels les boites investissent beaucoup. Sous HP ou AIX (les machines de notre enfance à Kad et moi),je dis pas. Mais sous Windows et Solaris, c'est le dernier compilo auquel je penserais...
Par contre, j'ai jamais eu de plantage sec de Forte qui ne soit pas corrigeable (patch, update de lib, etc.). Alors que mes unit-tests, il crashaient systèmatiquement au-dessus de -O2 avec g++.
Marsh Posté le 23-10-2004 à 17:25:11
kadreg a écrit : je développerais ce soir, je ferais un apté |
On l'attend toujours
Marsh Posté le 23-10-2004 à 17:39:07
Excusez moi.
Oui, alors personnellement, je suis grand fan des exceptions. Et pour moi, la première raison de les utiliser tiens plus des notions de génie logiciel que des histoires de perfs ou de capacité d'interconnexions.
Le premier intêrets des exception est de garder au maximum un code orienté métier, et de déléguer la gestion des erreurs dans le catch (qui est généralement lié par le gestionnaire de transactions). Contrairement à une grande séries de if/then/else il n'y a pas de mélange traitement normal/traimtement d'erreur, d'ou un code plus clair et plus facilement maintenable. Je viens par exemple de modifier un code métier à base de if/then/else en exceptions, et j'ai 70% de code qui a viré, à fonctionnalité identiques, et avec en cadeau un bien meilleur retour d'informations à l'utilisateur (pour info, c'est la création d'un nouveau modèle UML à partir d'un modèle pré-configuré).
De plus, la mise en place d'une hiérarchie d'exception adaptée permet de jouer sur la granularité des niveaux d'interprétations d'erreur et d'avoir un retour plus efficace du problème au niveau de l'appelant. Charge à lui de mettre en forme une réponse adaptées à l'utilisateur suivant son contexte.
Enfin, les exceptions étants de objets qui peuvent être complexes, ils peuvent embarquer beaucoup d'informations sur le contexte du problème, permettant d'avoir facilement un retour très riche si un problème à lieu en production.
Marsh Posté le 23-10-2004 à 18:47:55
Je suis très intéressé par cette gestion, j'ai une vague idée de la façon dont ça fonctionne, mais si qqun a un petit example d'une gestion d'exception hiérarchisée ça m'intéresserai, et suis sur que je ne suis pas le seul
Marsh Posté le 23-10-2004 à 19:51:43
Ca dépend de ce que l'on recherche. Pour répondre à muchacho, effectivement, je ne teste pas tout, essentiellement ce qui peut conduire à un crash de l'application ou à une corruption de données.
Pour la lenteur des exceptions, ça dépend des perfs qu'on attend. C'est pourquoi je ne les activerais que quand celles ci ne sont pas critiques, ce qui est le cas dans le devt d'un outil, mais pas dans l'écriture d'une boucle de rendu.
A ce stade, si ça doit crasher, j'aime autant que ça le fasse pour debuguer l'erreur.
Cela dit, je vais me pencher plus en détail la dessus car les quelques précisions que vous avez apporté sont intéressantes.
Marsh Posté le 24-10-2004 à 00:34:07
Une question intéressante a été levée : que se passe-t-il si par exemple j'utilise dans un programme compilé avec g++ une dll compilée avec VC++ qui lève une exception ?
Sinon perso je mixe les 2. Les exceptions, c'est pour les cas exceptionnels. Le cas classique c'est des fonctions qui lisent un fichier. Je n'utilise pas d'exception en cas d'erreur de lecture, comme la SL d'ailleurs.
Les exceptions ont un défaut selon moi : il faut placer un bloc try...catch et ça prend plus de lignes qu'un simple if. Déjà que pas grand monde teste les codes de retour, alors un try...catch a chaque appel de fonction...
Qui ici entoure tout ses appels à push_back d'un bloc try...catch ?
Mais ceci est compensé par l'avantage des exceptions : le fait de remonter tout le long des fonctions, ce qui permet de laisser filer les erreurs les plus graves et les plus exceptionnelles.
J'utilise les exceptions pour les erreurs internes (assertion maison qui fouarre, etc...) car ont peut y mettre plein d'infos. Les erreurs simples genre entrer une chaine au lieu d'un nombre, nom de fichier invalide, etc... je me contente de codes d'erreurs.
En fait, j'utilise un code d'erreur quand l'erreur doit être traitée par l'appelant, et une exception quand elle doit être traitée à un niveau plus élevé (l'appelant ne sait pas quoi en faire).
Marsh Posté le 24-10-2004 à 09:28:27
HelloWorld a écrit : Une question intéressante a été levée : que se passe-t-il si par exemple j'utilise dans un programme compilé avec g++ une dll compilée avec VC++ qui lève une exception ? |
A priori, je dirais boum. La solution a été donnée par Lam's:
Citation : Tu comptes donc avoir un mapping C utilisé par tous les langages si je ne me trompte pas. |
Marsh Posté le 24-10-2004 à 10:36:54
HelloWorld a écrit : Une question intéressante a été levée : que se passe-t-il si par exemple j'utilise dans un programme compilé avec g++ une dll compilée avec VC++ qui lève une exception ? |
Boum.
A moins d'avoir une ABI compatible (c'est censé être le cas sous Linux), les compilateurs ne vont pas décorer leurs noms de la même façon, ils ne vont pas utiliser les mêmes registres pour passer les paramètres, etc. Il y en a même des fourbes (Forte et g++ par exemple), qui s'amusent à mettre les mêmes symboles dans le namespace global. Par exemple dout, ce qui fait que si tu linkes 2 objets compilés avec des compilos différents et qui écrivent tous les deux sur std::cout, ça crashera, même si l'un n'appelle pas l'autre...
Citation : Les exceptions ont un défaut selon moi : il faut placer un bloc try...catch et ça prend plus de lignes qu'un simple if. Déjà que pas grand monde teste les codes de retour, alors un try...catch a chaque appel de fonction... |
Dans mon cas, je trouve que c'est souvent inutile. Il est clair que le code doit être exception-safe (tous les mutexes vont dans des scoped-objects, tous les pointeurs alloués vont dans des auto_ptr, etc.). Mais souvent, un cas exceptionnel, ça demande d'être remonté bien haut et bien vite plutôt que d'être traité à la petite semaine...
M'enfin, je suppose que ça dépend fortement des applications.
Marsh Posté le 24-10-2004 à 12:18:21
Nyast a écrit : 4. Autres: préciser dans votre post ? |
Utiliser des handlers afin que le gus puisse lever des exceptions s'il aime ça, afficher "code de l'erreur prout prout de sa race maudite", s'envoyer un mail parce qu'il aime que sa bécane lui raconte sa vie, ...
Citation : Votre avis ? |
Il n'y a pas de solution miracle et parfaite pour la gestion des erreurs. Depuis le temps, ça se saurait. De mon point de vue, c'est très contextuel. Parfois, c'est lorsque l'erreur survient qu'on est le plus à même de la traiter correctement et proprement. D'autre fois, on ne sait pas quoi en faire et mieux vaut laisser un autre bout de code se démerder avec. La manière dont on fait remonter les infos sur l'erreur et quelles infos vont remonter dépendent de ça. Sans compter le fait qu'il y a aussi des choix métaphysiques derrière selon que l'on a affaire à une erreur fonctionelle, logique, technique, ...
Bref, affirmer de manière péremptoire "les erreurs, ça se gère comme ça et pas autrement", c'est du vent.
Marsh Posté le 21-10-2004 à 10:34:22
Contexte: je suis en train de travailler sur un moteur 3D, mais la gestion des erreurs commence à devenir assez bordélique. Il est temps d'y remédier mais je suis encore indécis sur la façon de faire..
1. Exceptions C++
PRO: - interface très propre
CON: - intégration avec d'autres languages problématique?
- un peu plus lent?
- l'interception de l'erreur ne se fait pas forcément dans la fonction du dessus
2. DirectX: toutes les fonctions retournent un code d'erreur
PRO: - interface cohérente
CON: - si la fonction retourne quelque chose, cela doit devenir un argument, pour laisser la place au code d'erreur à retourner.
- interface très moche, donc
3. OpenGL: les fonctions ne retournent pas de code d'erreur, c'est à l'utilisateur de tester si une erreur est apparue après avoir exécuté la fonction, à l'aide d'une fonction globale de type "getLastError".
PRO: - interface très propre
- l'utilisateur peut choisir d'ignorer les erreurs
CON: - l'utilisateur peut choisir d'ignorer les erreurs
4. Autres: préciser dans votre post ?
Votre avis ?