Très, très judicieux ; et très, très important.
Le fait de trouver ou pas quelque chose via le find fait - intrinsèquement - partie de l'algorithme. Ce dernier peut échouer, c'est dans son ADN. Par contre, get suppose que la donnée qu'on souhaite récupérer est présente - échouer ne fait pas partie de son ADN. C'est la différence entre le find() de la librairie standard C++, et le at() du vecteur (operator[] est un cas particulier, dans le sens où un échec fait crasher le système ; c'est donc un choix de design).
Non ; un code d'erreur en retour est lié au fait que l'algorithme, tout en restant dans son domaine d'exécution, échoue. Une exception est liée au fait que l'algorithme n'est pas dans son domaine d'exécution, donc ne peut pas ne pas échouer.
et
Pas vraiment. En fait, la remarque de oodini :
est tout à fait justifiée : la bonne gestion des codes de retour n'est pas liée au programmeur, elle est liée à l'analyse statique du code source (tant mieux si c'est le compilateur qui la fait ; mais ce n'est pas obligatoire, dans le sens où la compilation d'u projet est une suite d'opération, dont l'analyse statique peut faire partie - moyennant la modification du process de build).Cela n'est-t-il pas évitable avec un compilateur qui te signale qu'une valeur de retour n'est pas récupérée ?
Presque OK. Disons que ces programmeurs là n'ont pas de requirement du type "le code doit compiler sans warning avec -Wall", ou qu'ils sont trop jeunes pour se rendre compte qu'un warning signale un problème qu'on va devoir corriger, de toute façon (ce qui implique une perte de temps future, ce qui diminue la portée de l'argument "pas assez de temps").
Ceci-dit, je rejoins l'analyse globale de la suite du post, mais je l'estime trop theorico-pragmatique (cad trop orientée "je pense que dans la pratique, ça se passe comme ça" ; parce que sinon, ça ressemble fort à un oxymore). L'analyse se heurte en fait à ce qu'a annoncé jblecanard:
Ca, je confirme ; je l'ai déjà vu faire. Etant donné que personne (et je dis bien personne) ne fait de revue de code derrière, un tel contournement - même complètement incorrect - a 99,99% de chances de passer.... ils sont capable de simplement attraper une exception juste pour qu'on ne la voie plus ...
Le fait, d'après mon expérience (et tout un chacun est tout à fait autorisé à la remettre en cause), les exceptions n'offrent pas plus de sécurité que les codes de retour - et pas moins, dans le sens où un méchant (pas forcément mauvais, mais juste méchant) programmeur peut tout à fait ignorer et l'un et l'autre - soit pas omission de code, soit par l'écriture de code spécifique. Et dans la faune des programmeurs, on se doit de noter que ceux qui sont prompts à ignorer (voire invalider) les codes retour sont aussi ceux qui vont catcher les exceptions pour les rendre silencieuses. Malheureusement, c'est d'eux qu'on cherche à se protéger, pas du débutant (qui fait l'erreur par ignorance une fois) ni du vieux routard (qui sait exactement pourquoi il ignore une erreur ou une exception). Ce qui est tout à fait résumé par Bousk :
La discussion est sans conteste intéressante, puisqu'elle permet de faire avancer le debat ; par contre, elle oublie de dire que l'article de Aaron Lahman (gracieusement traduit par LittleWhite ; merci, au fait, parce que c'était important qu'il soit rendu accessible au plus grand nombre) se plante complètement sur ce qui fait la base de son article - ce qu'il appelle le C++ moderne.
Le C++ moderne n'utilise pas de shared_ptr<> juste pour ne pas avoir de pointeurs qui trainent ici et là. Effectement, comme il le dit, un pointeur nu est suspect ; mais d'un autre coté, shared_ptr<> (ou unique_ptr<>) sont quasiment transparent aux exception. La seule différence entre ces objets et les pointeurs nus est qu'ils permettent - à la sortie du scope - de détruire l'objet s'ils n'a pas été retourné par la fonction (c'est ce qui arrive en cas d'exception).
C'est du C++ moderne bas de gamme, du niveau d'une personne qui n'a pas encore compris RAII et ce que ce concept apporte au langage. J'ai cru pendant un instant que l'auteur allait prendre de la hauteur (mon dieu ! un jeu de mots !)
Mon gars, ce n'est pas suffisant. Si tu sais ça, alors tu te dois de remarquer que les smarts pointers ne servent pas à implémenter RAII, mais à ramener du code non RAII dans un chemin plus raisonnable - notamment dans le cas où on ne peux pas faire autre chose que manipuler des pointeurs nus.Je change les règles. Je ne me soucie plus du nombre de branchements, que ce soit lors d'échecs ou de réussites. Par contre, je me préoccupe énormément du fait qu'à chaque instant, il n'y a qu'un seul propriétaire pour chaque ressource et qu'en cas d'échec, ce propriétaire gérera la ressource.
C'est un changement fondamental. L'approche moderne utilisant le RAII n'a pas toujours un code explicite pour les conditions d'échecs. Les échecs courants sont toujours gérés non localement. La ressource locale est immédiatement prise en charge par des objets automatiques, grâce au RAII, c'est-à-dire qui seront détruits à la sortie du bloc. N'importe quelle ressource brute (comme l'allocation d'un nouvel objet) doit toujours être déléguée à un objet RAII.
RAII, dans l'exemple proposé, passe nécessairement par des objets complets - on va les appeler notify_icon et icon. Et, comme par magie, le code proposé se simplifie encore :
Pas besoin de faire un reset() sur des objets construits par défaut - après tout, c'est un jeu avec les objets shared_ptr<>, d'un intérêt très, très limité.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 notify_icon CreateNotifyIcon() { notify_icon icon; icon.set_text("Blah blah blah"); icon.set_icon(icon("cool.ico", GetType())); icon.set_visible(true); return icon; }
Le code peut encore être amélioré (1), dans le sens où un notify_icon doit obligatoirement avoir un icone (donc : paramètre du constructeur) et que ce n'est pas nécessairement à la fonction CreateNotifyIcon() de spécifier qu'il est visible ou non (pourquoi est-ce que ça ferait partie de la création d'un icone ?).
En fait, le code pourrait être :
Sans sacrifier à la lisibilité ni à quoi que ce soit d'ailleurs. Et du coup, il devient clair - d'une clarté limpide, j'espère - que dans ce cas, les exceptions sont bien plus adaptés qu'un code d'erreur en retour.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 notify_icon CreateNotifyIcon() { return notify_icon(icon(...), "Blah blah blah"); }
Et du coup, on peut maintenant se poser la question "quand utiliser les exceptions, et quand utiliser les code d'erreur en retour ?".
Personnellement, j'utilise la règle suivante :
* si je ne dois pas échouer, alors je génère une exception en cas d'échec.
* si je peut échouer (cad si c'est dans mon ADN d'échouer (1)), alors je renvoie un code d'erreur en retour.
On en revient donc à ce qu'a dit Bousk - et d'autres avant lui :
La boucle est bouclée - et c'est rare que je boucle les boucles, proffitez en
--
(1) c'est ce que me dit la présence de méthodes nommées set_xxx(). Dès qu'on les voit, on se dit : "bon, quelqu'un n'a pas poussé sa réflexion au bout. Dommage il était bien parti."
(2) et je parle en tant que petite partie d'un programme ; pas en tant qu'humain ; en tant qu'humain, mon ADN me dit principalement "amuse toi, mange, dors, et essaie de faire des enfants" (3)
(3) je ne sais pas si c'est juste moi ou le monde entier, mais ce qui est dur quand on fait un enfant deviens mou quand on l'élève, et ce qui est mou devient dur. C'est bizarre.
Partager