Exceptions comme affirmations ou comme erreurs?

10

Je suis un programmeur C professionnel et un programmeur Obj-C amateur (OS X). Récemment, j'ai été tenté de développer en C ++, en raison de sa syntaxe très riche.

Jusqu'à présent, je n'ai pas beaucoup traité des exceptions avec le codage. Objective-C en a, mais la politique d'Apple est assez stricte:

Important Vous devez réserver l'utilisation d'exceptions pour la programmation ou des erreurs d'exécution inattendues telles que l'accès à la collection hors limites, les tentatives de mutation d'objets immuables, l'envoi d'un message non valide et la perte de la connexion au serveur Windows.

C ++ semble préférer utiliser les exceptions plus souvent. Par exemple, l'exemple de wikipedia sur RAII lève une exception si un fichier ne peut pas être ouvert. Objective-C return nilavec une erreur envoyée par un paramètre de sortie. Notamment, il semble que std :: ofstream puisse être défini dans les deux sens.

Ici, sur les programmeurs, j'ai trouvé plusieurs réponses soit proclamant utiliser des exceptions au lieu de codes d'erreur, soit ne pas utiliser d'exceptions du tout . Les premiers semblent plus répandus.

Je n'ai trouvé personne faire une étude objective pour C ++. Il me semble que comme les pointeurs sont rares, je devrais utiliser des indicateurs d'erreur internes si je choisis d'éviter les exceptions. Sera-ce trop difficile à gérer, ou cela fonctionnera-t-il peut-être encore mieux que les exceptions? Une comparaison des deux cas serait la meilleure réponse.

Edit: Bien que pas complètement pertinent, je devrais probablement clarifier ce qui nilest. Techniquement, c'est la même chose que NULL, mais le fait est que c'est ok d'envoyer un message à nil. Vous pouvez donc faire quelque chose comme

NSError *err = nil;
id obj = [NSFileHandle fileHandleForReadingFromURL:myurl error:&err];

[obj retain];

même si le premier appel est revenu nil. Et comme vous ne le faites jamais *objdans Obj-C, il n'y a aucun risque de déréférence de pointeur NULL.

Per Johansson
la source
À mon humble avis, il serait préférable que vous montriez un morceau de code Objective C comment vous travaillez avec des erreurs là-bas. Il semble que les gens parlent de quelque chose de différent de ce que vous demandez.
Gangnus
D'une certaine manière, je suppose que je cherche une justification pour l'utilisation des exceptions. Étant donné que j'ai une idée de la façon dont ils sont mis en œuvre, je sais à quel point ils peuvent être coûteux. Mais je suppose que si je peux obtenir un argument suffisamment valable pour leur utilisation, je vais suivre cette voie.
Per Johansson
Il semble, pour C ++, vous avez plutôt besoin d'une justification pour ne pas les utiliser . Selon les réactions ici.
Gangnus
2
Peut-être, mais jusqu'à présent, personne n'a expliqué pourquoi ils sont meilleurs (sauf par rapport aux codes d'erreur). Je n'aime pas le concept d'utiliser des exceptions pour des choses qui ne sont pas exceptionnelles, mais c'est plus instinctif que basé sur des faits.
Per Johansson
"Je n'aime pas ... utiliser des exceptions pour des choses qui ne sont pas exceptionnelles" - d'accord.
Gangnus

Réponses:

1

C ++ semble préférer utiliser les exceptions plus souvent.

Je suggérerais en fait moins qu'Objective-C à certains égards parce que la bibliothèque standard C ++ ne déclencherait généralement pas d'erreurs de programmation comme l'accès hors limites d'une séquence à accès aléatoire dans sa forme de conception de cas la plus courante (dans operator[], c'est- à -dire) ou essayer de déréférencer un itérateur invalide. Le langage ne permet pas d'accéder à un tableau en dehors des limites, ni de déréférencer un pointeur nul, ou quoi que ce soit de ce genre.

Prendre les erreurs de programmation en grande partie hors de l'équation de gestion des exceptions enlève en fait une très grande catégorie d'erreurs auxquelles d'autres langues répondent souvent throwing. Le C ++ a tendance à assert(qui n'est pas compilé dans les versions de publication / production, seulement les versions de débogage) ou simplement à se briser (souvent en panne) dans de tels cas, probablement en partie parce que le langage ne veut pas imposer le coût de ces vérifications d'exécution comme cela serait nécessaire pour détecter de telles erreurs de programmation à moins que le programmeur ne veuille spécifiquement payer les coûts en écrivant du code qui effectue lui-même de telles vérifications.

Sutter encourage même à éviter les exceptions dans de tels cas dans les normes de codage C ++:

Le principal inconvénient de l'utilisation d'une exception pour signaler une erreur de programmation est que vous ne voulez pas vraiment que le déroulement de la pile se produise lorsque vous souhaitez que le débogueur se lance sur la ligne exacte où la violation a été détectée, avec l'état de la ligne intact. En résumé: vous savez qu'il peut y avoir des erreurs (voir les points 69 à 75). Pour tout le reste qui ne devrait pas, et c'est la faute du programmeur si c'est le cas, il y en a assert.

Cette règle n'est pas nécessairement figée. Dans certains cas plus critiques, il peut être préférable d'utiliser, par exemple, des wrappers et une norme de codage qui enregistre uniformément les erreurs de programmation et throwen présence d'erreurs de programmation, comme essayer de déférer quelque chose d'invalide ou d'y accéder hors des limites, car il pourrait être trop coûteux de ne pas récupérer dans ces cas si le logiciel a une chance. Mais dans l'ensemble, l'utilisation la plus courante du langage a tendance à ne pas se heurter à des erreurs de programmation.

Exceptions externes

Là où je vois des exceptions encouragées le plus souvent en C ++ (selon le comité standard, par exemple), c'est pour les "exceptions externes", comme dans un résultat inattendu dans une source externe en dehors du programme. Un exemple ne parvient pas à allouer de la mémoire. Un autre ne parvient pas à ouvrir un fichier critique requis pour l'exécution du logiciel. Un autre ne parvient pas à se connecter à un serveur requis. Un autre est un utilisateur bloquant un bouton d'abandon pour annuler une opération dont le chemin d'exécution de cas commun s'attend à réussir en l'absence de cette interruption externe. Toutes ces choses échappent au contrôle du logiciel immédiat et des programmeurs qui l'ont écrit. Ce sont des résultats inattendus de sources externes qui empêchent l'opération (qui devrait vraiment être considérée comme une transaction indivisible dans mon livre *) de réussir.

Transactions

J'encourage souvent à considérer un trybloc comme une "transaction" car les transactions devraient réussir dans leur ensemble ou échouer dans leur ensemble. Si nous essayons de faire quelque chose et que cela échoue à mi-chemin, alors tous les effets secondaires / mutations apportés à l'état du programme doivent généralement être annulés pour remettre le système dans un état valide comme si la transaction n'avait jamais été exécutée, tout comme un SGBDR qui ne parvient pas à traiter une requête à mi-parcours ne doit pas compromettre l'intégrité de la base de données. Si vous mutez l'état du programme directement dans ladite transaction, vous devez le "réactiver" s'il rencontre une erreur (et ici, les gardes de portée peuvent être utiles avec RAII).

L'alternative beaucoup plus simple est de ne pas muter l'état du programme d'origine; vous pouvez muter une copie de celui-ci puis, s'il réussit, échanger la copie avec l'original (en vous assurant que l'échange ne peut pas être lancé). S'il échoue, jetez la copie. Cela s'applique également même si vous n'utilisez pas d'exceptions pour la gestion des erreurs en général. Un état d'esprit "transactionnel" est la clé d'une récupération correcte si des mutations d'état du programme se sont produites avant de rencontrer une erreur. Il réussit dans son ensemble ou échoue dans son ensemble. Il ne parvient pas à moitié à faire ses mutations.

C'est bizarrement l'un des sujets les moins fréquemment discutés lorsque je vois des programmeurs demander comment faire correctement la gestion des erreurs ou des exceptions, mais il est le plus difficile à obtenir dans n'importe quel logiciel qui veut muter directement l'état du programme dans de nombreux ses opérations. La pureté et l'immuabilité peuvent aider ici à atteindre une sécurité d'exception tout autant qu'elles aident à la sécurité des fils, car une mutation / un effet secondaire externe qui ne se produit pas n'a pas besoin d'être annulé.

Performance

Un autre facteur déterminant dans l'utilisation ou non des exceptions est la performance, et je ne veux pas dire d'une manière obsessionnelle, penny-pinching, contre-productive. De nombreux compilateurs C ++ implémentent ce que l'on appelle le "traitement d'exception à coût nul".

Il n'offre aucun temps d'exécution pour une exécution sans erreur, ce qui dépasse même celui de la gestion des erreurs de valeur de retour C. En guise de compromis, la propagation d'une exception a une surcharge importante.

Selon ce que j'ai lu à ce sujet, cela rend vos chemins d'exécution de cas courants ne nécessitent pas de surcharge (pas même la surcharge qui accompagne normalement la gestion et la propagation du code d'erreur de style C), en échange de fausser fortement les coûts vers les chemins exceptionnels ( ce qui signifie throwingest maintenant plus cher que jamais).

"Cher" est un peu difficile à quantifier mais, pour commencer, vous ne voulez probablement pas lancer un million de fois dans une boucle étroite. Ce type de conception suppose que les exceptions ne se produisent pas toujours à gauche et à droite.

Non-erreurs

Et ce point de performance m'amène à des non-erreurs, ce qui est étonnamment flou si l'on regarde toutes sortes d'autres langages. Mais je dirais, étant donné la conception EH à coût nul mentionnée ci-dessus, que vous ne voulez certainement pas throwen réponse à une clé qui ne se trouve pas dans un ensemble. Parce que non seulement c'est sans doute une non-erreur (la personne recherchant la clé peut avoir construit l'ensemble et s'attendre à rechercher des clés qui n'existent pas toujours), mais cela coûterait énormément cher dans ce contexte.

Par exemple, une fonction d'intersection d'ensemble peut vouloir parcourir deux ensembles et rechercher les clés qu'ils ont en commun. Si threwvous ne parvenez pas à trouver une clé , vous seriez en boucle et pourriez rencontrer des exceptions dans la moitié ou plus des itérations:

Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
     Set<int> intersection;
     for (int key: a)
     {
          try
          {
              b.find(key);
              intersection.insert(other_key);
          }
          catch (const KeyNotFoundException&)
          {
              // Do nothing.
          }
     }
     return intersection;
}

Cet exemple ci-dessus est absolument ridicule et exagéré, mais j'ai vu, dans le code de production, certaines personnes venant d'autres langages utiliser des exceptions en C ++ un peu comme ça, et je pense que c'est une déclaration raisonnablement pratique que ce n'est pas une utilisation appropriée des exceptions que ce soit en C ++. Un autre indice ci-dessus est que vous remarquerez que le catchbloc n'a absolument rien à faire et qu'il est juste écrit pour ignorer de force de telles exceptions, et c'est généralement un indice (bien qu'il ne soit pas un garant) que les exceptions ne sont probablement pas utilisées de manière très appropriée en C ++.

Pour ces types de cas, un certain type de valeur de retour indiquant l'échec (que ce soit de retourner falseà un itérateur invalide nullptrou à tout ce qui a du sens dans le contexte) est généralement beaucoup plus approprié, et aussi souvent plus pratique et productif car un type sans erreur de case n'appelle généralement pas de processus de déroulement de pile pour atteindre le catchsite analogique .

Des questions

Je devrais utiliser des indicateurs d'erreur internes si je choisis d'éviter les exceptions. Sera-ce trop difficile à gérer, ou cela fonctionnera-t-il peut-être encore mieux que les exceptions? Une comparaison des deux cas serait la meilleure réponse.

Éviter purement et simplement les exceptions en C ++ me semble extrêmement contre-productif, à moins que vous ne travailliez dans un système embarqué ou un type particulier de cas qui interdit leur utilisation (auquel cas vous devrez également faire tout votre possible pour éviter tout bibliothèque et des fonctionnalités linguistiques qui, autrement throw, seraient strictement utilisées nothrow new).

Si vous devez absolument éviter les exceptions pour une raison quelconque (ex: travailler à travers les limites de l'API C d'un module dont vous exportez l'API C), beaucoup pourraient être en désaccord avec moi, mais je suggérerais en fait d'utiliser un gestionnaire / état d'erreur global comme OpenGL avec glGetError(). Vous pouvez lui faire utiliser le stockage local de threads pour avoir un statut d'erreur unique par thread.

Ma raison en est que je n'ai pas l'habitude de voir des équipes dans des environnements de production vérifier soigneusement toutes les erreurs possibles, malheureusement, lorsque les codes d'erreur sont renvoyés. S'ils étaient approfondis, certaines API C peuvent rencontrer une erreur avec à peu près chaque appel d'API C, et une vérification approfondie nécessiterait quelque chose comme:

if ((err = ApiCall(...)) != success)
{
     // Handle error
}

... avec presque chaque ligne de code invoquant l'API nécessitant de telles vérifications. Pourtant, je n'ai pas eu la chance de travailler avec des équipes aussi approfondies. Ils ignorent souvent de telles erreurs la moitié, parfois même la plupart du temps. C'est le plus grand attrait pour moi des exceptions. Si nous encapsulons cette API et la rendons uniforme throwlors de la rencontre d'une erreur, l'exception ne peut pas être ignorée , et à mon avis, et par expérience, c'est là que réside la supériorité des exceptions.

Mais si les exceptions ne peuvent pas être utilisées, le statut d'erreur global par thread a au moins l'avantage (énorme par rapport au renvoi de codes d'erreur) qu'il pourrait avoir une chance de détecter une ancienne erreur un peu plus tard que lorsqu'il s'est produit dans une base de code bâclée au lieu de la manquer complètement et de nous laisser complètement inconscients de ce qui s'est passé. L'erreur peut s'être produite quelques lignes auparavant, ou lors d'un appel de fonction précédent, mais à condition que le logiciel ne soit pas encore tombé en panne, nous pourrons peut-être commencer à revenir en arrière et déterminer où et pourquoi cela s'est produit.

Il me semble que comme les pointeurs sont rares, je devrais utiliser des indicateurs d'erreur internes si je choisis d'éviter les exceptions.

Je ne dirais pas nécessairement que les pointeurs sont rares. Il existe même des méthodes désormais en C ++ 11 et ultérieur pour accéder aux pointeurs de données sous-jacents des conteneurs, et un nouveau nullptrmot-clé. Il est généralement considéré comme imprudent d'utiliser des pointeurs bruts pour posséder / gérer la mémoire si vous pouvez utiliser quelque chose comme à la unique_ptrplace étant donné à quel point il est essentiel d'être conforme à RAII en présence d'exceptions. Mais les pointeurs bruts qui ne possèdent pas / ne gèrent pas la mémoire ne sont pas nécessairement considérés comme si mauvais (même par des gens comme Sutter et Stroustrup) et parfois très pratiques comme moyen de pointer des choses (avec des indices qui pointent vers des choses).

Ils ne sont sans doute pas moins sûrs que les itérateurs de conteneurs standard (au moins dans la version, en l'absence d'itérateurs vérifiés) qui ne détecteront pas si vous essayez de les déréférencer après leur invalidation. Le C ++ est encore sans vergogne un peu un langage dangereux, je dirais, à moins que votre utilisation spécifique de celui-ci veuille tout envelopper et cacher même les pointeurs bruts non propriétaires. Il est presque essentiel, à quelques exceptions près, que les ressources soient conformes à RAII (qui ne coûte généralement aucun frais d'exécution), mais à part cela, il n'essaie pas nécessairement d'être le langage le plus sûr à utiliser pour éviter les coûts qu'un développeur ne souhaite pas explicitement. échanger contre autre chose. L'utilisation recommandée n'essaie pas de vous protéger contre des choses comme les pointeurs pendants et les itérateurs invalides, pour ainsi dire (sinon nous serions encouragés à utilisershared_ptrpartout, ce à quoi Stroustrup s’oppose avec véhémence). Il essaie de vous protéger contre l'échec de libérer / libérer / détruire / déverrouiller / nettoyer correctement une ressource quand quelque chose throws.

Dragon Energy
la source
14

Voici la chose: en raison de l'historique et de la flexibilité uniques de C ++, vous pouvez trouver quelqu'un proclamant pratiquement n'importe quelle opinion sur une fonctionnalité que vous souhaitez. Cependant, en général, plus ce que vous faites ressemble à C, pire est l'idée.

L'une des raisons pour lesquelles C ++ est beaucoup plus souple en ce qui concerne les exceptions est que vous ne pouvez pas exactement return nilquand vous en avez envie. Il n'y a rien de tel que nildans la grande majorité des cas et des types.

Mais voici le simple fait. Les exceptions font leur travail automatiquement. Lorsque vous lève une exception, RAII prend le relais et tout est géré par le compilateur. Lorsque vous utilisez des codes d'erreur, vous devez vous rappeler de les vérifier. Cela rend intrinsèquement plus sûr les exceptions que les codes d'erreur - ils se vérifient, pour ainsi dire. De plus, ils sont plus expressifs. Lancez une exception et vous pouvez obtenir une chaîne vous indiquant quelle est l'erreur, et peut même contenir des informations spécifiques, comme "Mauvais paramètre X qui avait la valeur Y au lieu de Z". Obtenez un code d'erreur de 0xDEADBEEF et qu'est-ce qui s'est exactement passé? J'espère bien que la documentation est complète et à jour, et même si vous obtenez "Mauvais paramètre", cela ne vous dira jamais lequel, quelle valeur il avait et quelle valeur il aurait dû avoir. Si vous les capturez par référence, elles peuvent également être polymorphes. Enfin, des exceptions peuvent être levées à des endroits où les codes d'erreur ne peuvent jamais, comme les constructeurs. Et qu'en est-il des algorithmes génériques? Comment std::for_eachva gérer votre code d'erreur? Astuce: ce n'est pas le cas.

Les exceptions sont largement supérieures aux codes d'erreur à tous égards. La vraie question est dans les exceptions contre les affirmations.

Voici le truc. Personne ne peut savoir à l'avance quelles conditions préalables votre programme a pour fonctionner, quelles sont les conditions d'échec inhabituelles, qui peuvent être vérifiées au préalable et quels sont les bogues. Cela signifie généralement que vous ne pouvez pas décider à l'avance si une défaillance donnée doit être une assertion ou une exception sans connaître la logique du programme. De plus, une opération qui peut se poursuivre lorsque l'une de ses sous-opérations échoue est l' exception , pas la règle.

À mon avis, des exceptions sont là pour être prises. Pas nécessairement immédiatement, mais finalement. Une exception est un problème dont vous vous attendez à ce que le programme puisse récupérer à un moment donné. Mais l'opération en question ne pourra jamais se remettre d'un problème qui mérite une exception.

Les échecs d'assertion sont toujours des erreurs fatales et irrécupérables. La corruption de la mémoire, ce genre de chose.

Donc, quand vous ne pouvez pas ouvrir un fichier, est-ce une affirmation ou une exception? Eh bien, dans une bibliothèque générique, il existe de nombreux scénarios où l'erreur peut être gérée, par exemple, en chargeant un fichier de configuration, vous pouvez simplement utiliser une valeur par défaut prédéfinie à la place, donc je dirais une exception.

En tant que note de bas de page, je voudrais mentionner qu'il y a quelque chose de "modèle d'objet nul" qui circule. Ce schéma est terrible . Dans dix ans, ce sera le prochain Singleton. Le nombre de cas dans lesquels vous pouvez produire un objet nul approprié est minuscule .

DeadMG
la source
L'alternative n'est pas nécessairement un simple int. Je serais plus susceptible d'utiliser quelque chose de similaire à NSError auquel je suis habitué d'Obj-C.
Per Johansson
Il compare non pas à C, mais à l'objectif C. C'est une grande différence. Et ses codes d'erreur expliquent les lignes. Tout ce que vous dites est correct, seulement pas de réponses à cette question. Aucune infraction signifiait.
Gangnus
Quiconque utilise Objective-C vous dira bien sûr que vous avez absolument, complètement tort.
gnasher729
5

Les exceptions ont été inventées pour une raison, qui est d'éviter que tout votre code ressemble à ceci:

bool success = function1(&result1, &err);
if (!success) {
    error_handler(err);
    return;
}

success = function2(&result2, &err);
if (!success) {
    error_handler(err);
    return;
}

Au lieu de cela, vous obtenez quelque chose qui ressemble davantage à ce qui suit, avec un gestionnaire d'exceptions en place mainou autrement situé de manière pratique:

result1 = function1();
result2 = function2();

Certaines personnes revendiquent des avantages en termes de performances à l'approche sans exception, mais à mon avis, les problèmes de lisibilité l'emportent sur les gains de performances mineurs, en particulier lorsque vous incluez le temps d'exécution pour tout le passe-partout que if (!success)vous devez saupoudrer partout, ou le risque de plus difficile à déboguer les défauts de segmentation si vous ne le faites pas. t l'inclure, et compte tenu des chances d'exceptions sont relativement rares.

Je ne sais pas pourquoi Apple décourage l'utilisation d'exceptions. S'ils essaient d'éviter de propager des exceptions non gérées, tout ce que vous accomplissez vraiment, ce sont des personnes utilisant des pointeurs nuls pour indiquer des exceptions à la place, donc une erreur de programmeur entraîne une exception de pointeur nul au lieu d'un fichier beaucoup plus utile, une exception introuvable ou quoi que ce soit.

Karl Bielefeldt
la source
1
Cela aurait plus de sens si le code sans exception ressemblait vraiment à cela, mais ce n'est généralement pas le cas. Ce modèle apparaît lorsque error_handler ne revient jamais, mais rarement dans le cas contraire.
Per Johansson
1

Dans le post que vous référencez (exceptions vs codes d'erreur), je pense qu'il y a une discussion subtilement différente en cours. La question semble être de savoir si vous avez une liste globale de codes d'erreur #define, probablement complète avec des noms comme ERR001_FILE_NOT_WRITEABLE (ou en abrégé, si vous n'avez pas de chance). Et, je pense que le point principal de ce fil est que si vous programmez dans un langage polymporphique, en utilisant des instances d'objet, une telle construction procédurale n'est pas nécessaire. Les exceptions peuvent exprimer quelle erreur se produit simplement en raison de leur type, et elles peuvent encapsuler des informations telles que le message à imprimer (et d'autres choses également). Donc, je considérerais cette conversation comme une question de savoir si vous devez programmer de manière procédurale dans un langage orienté objet ou non.

Mais, la conversation sur quand et dans quelle mesure s'appuyer sur des exceptions pour gérer les situations qui surviennent dans le code est différente. Lorsque des exceptions sont levées et interceptées, vous introduisez un paradigme de flux de contrôle complètement différent de la pile d'appels traditionnelle. La levée d'exception est fondamentalement une instruction goto qui vous arrache de votre pile d'appels et vous envoie vers un emplacement indéterminé (partout où votre code client décide de gérer votre exception). Cela rend la logique d'exception très difficile à raisonner .

Et donc, il y a un sentiment comme celui exprimé par jheriko que ceux-ci devraient être minimisés. Autrement dit, disons que vous lisez un fichier qui peut ou non exister sur le disque. Jheriko et Apple et ceux qui pensent comme eux (moi y compris) diraient que lever une exception n'est pas approprié - l'absence de ce fichier n'est pas exceptionnelle , c'est prévu. Les exceptions ne doivent pas remplacer le flux de contrôle normal. Il est tout aussi simple que votre méthode ReadFile () retourne, disons, un booléen, et que le code client voie à la valeur de retour false que le fichier n'a pas été trouvé. Le code client peut alors dire que le fichier utilisateur n'a pas été trouvé, ou gérer tranquillement ce cas, ou tout ce qu'il veut faire.

Lorsque vous jetez une exception, vous imposez un fardeau à vos clients. Vous leur donnez du code qui, parfois, les arrachera à la pile d'appels normaux et les forcera à écrire du code supplémentaire pour empêcher leur application de se bloquer. Un fardeau aussi puissant et discordant ne devrait leur être imposé que si cela est absolument nécessaire au moment de l'exécution, ou dans l'intérêt de la philosophie du «premier échec». Donc, dans le premier cas, vous pouvez lever des exceptions si le but de votre application est de surveiller le fonctionnement du réseau et que quelqu'un débranche le câble réseau (vous signalez une panne catastrophique). Dans ce dernier cas, vous pouvez lever une exception si votre méthode attend un paramètre non nul et que vous êtes remis à zéro (vous signalez que vous êtes utilisé de manière inappropriée).

Quant à ce qu'il faut faire au lieu d'exceptions dans le monde orienté objet, vous avez des options au-delà de la construction du code d'erreur procédurale. Un qui me vient immédiatement à l'esprit est que vous pouvez créer des objets appelés OperationResult ou quelque chose de ce genre. Une méthode peut renvoyer un OperationResult et cet objet peut avoir des informations sur le succès ou l'échec de l'opération, et sur les autres informations que vous souhaitez lui donner (un message d'état, par exemple, mais aussi, plus subtilement, il peut encapsuler des stratégies de récupération d'erreur ). Renvoyer cela au lieu de lever une exception permet le polymorphisme, préserve le flux de contrôle et rend le débogage beaucoup plus simple.

Erik Dietrich
la source
Je suis d'accord avec vous, mais c'est la raison pour laquelle la plupart semblent ne pas m'avoir fait poser la question.
Per Johansson
0

Il y a quelques beaux chapitres dans Clean Code qui parlent de ce sujet même - moins le point de vue d'Apple.

L'idée de base est que vous ne renvoyez jamais rien de vos fonctions, car cela:

  • Ajoute beaucoup de code dupliqué
  • Fait un gâchis de votre code - c'est-à-dire qu'il devient plus difficile à lire
  • Peut souvent entraîner des erreurs de pointeur nul - ou des violations d'accès en fonction de votre "religion" de programmation particulière

L'autre idée qui l'accompagne est que vous utilisez des exceptions car cela aide à réduire davantage l'encombrement de votre code, et plutôt que d'avoir à créer une série de codes d'erreur qui deviennent finalement difficiles à gérer et à maintenir (en particulier sur plusieurs modules et plates-formes) et sont difficiles à déchiffrer pour vos clients, vous créez plutôt des messages significatifs qui identifient la nature exacte du problème, simplifiez le débogage et pouvez réduire votre code de gestion des erreurs à quelque chose d'aussi simple qu'une try..catchinstruction de base .

À l'époque de mon développement COM, j'avais un projet où chaque échec soulevait une exception, et chaque exception devait utiliser un identifiant de code d'erreur unique basé sur l'extension des exceptions COM Windows standard. C'était un maintien d'un projet précédent où des codes d'erreur avaient été précédemment utilisés, mais la société voulait aller tout orientée objet et sauter dans le train de la COM sans changer la façon dont ils faisaient trop les choses. Il ne leur a fallu que 4 mois pour manquer de numéros de code d'erreur et c'est à moi de refactoriser de grandes portions de code pour utiliser des exceptions à la place. Mieux vaut vous épargner l'effort à l'avance et utiliser simplement les exceptions judicieusement.

S.Robins
la source
Merci, mais je n'envisage pas de retourner NULL. Cela ne semble pas possible face aux constructeurs de toute façon. Comme je l'ai mentionné, je devrais probablement utiliser un indicateur d'erreur dans l'objet.
Per Johansson