Conventions pour les exceptions ou codes d'erreur


118

Hier, j'ai eu un débat houleux avec un collègue sur ce que serait la méthode préférée pour signaler les erreurs. Nous discutions principalement de l'utilisation d'exceptions ou de codes d'erreur pour signaler les erreurs entre les couches ou modules d'application.

Quelles règles utilisez-vous pour décider si vous lancez des exceptions ou renvoyez des codes d'erreur pour le rapport d'erreur?

Réponses:


81

Dans les trucs de haut niveau, les exceptions; dans les trucs de bas niveau, les codes d'erreur.

Le comportement par défaut d'une exception est de dérouler la pile et d'arrêter le programme, si j'écris un script et que je cherche une clé qui n'est pas dans un dictionnaire, c'est probablement une erreur, et je veux que le programme s'arrête et me laisse tout savoir à ce sujet.

Si, cependant, j'écris un morceau de code dont je dois connaître le comportement dans toutes les situations possibles, alors je veux des codes d'erreur. Sinon, je dois connaître toutes les exceptions qui peuvent être lancées par chaque ligne de ma fonction pour savoir ce qu'elle va faire (lisez l'exception qui a ancré une compagnie aérienne pour avoir une idée de la difficulté). Il est fastidieux et difficile d'écrire du code qui réagit de manière appropriée à chaque situation (y compris les malheureuses), mais c'est parce que l'écriture de code sans erreur est fastidieuse et difficile, pas parce que vous passez des codes d'erreur.

Les deux Raymond Chen et Joel ont fait des arguments éloquents contre l' utilisation de tout exceptions.


5
+1 pour avoir souligné que le contexte a quelque chose à voir avec la stratégie de gestion des erreurs.
alx9r

2
@Tom, Bons points, mais les exceptions sont garanties. Comment pouvons-nous nous assurer que les codes d'erreur sont détectés et non ignorés en silence en raison d'erreurs?
Pacerier

13
Donc, votre seul argument contre les exceptions est que quelque chose de mauvais peut se produire lorsque vous oubliez d'attraper une exception mais que vous ne considérez pas la situation tout aussi probable consistant à oublier de vérifier la valeur renvoyée pour les erreurs? Sans oublier que vous obtenez une trace de pile lorsque vous oubliez d'attraper une exception alors que vous n'obtenez rien lorsque vous oubliez de vérifier un code d'erreur.
Esailija

3
C ++ 17 introduit l' nodiscardattribut qui donnera un avertissement au compilateur si la valeur de retour d'une fonction n'est pas stockée. Aide un peu à attraper la vérification du code d'erreur oublié. Exemple: godbolt.org/g/6i6E0B
Zitrax

5
Le commentaire de @ Esailija est exactement pertinent ici. L'argument contre les exceptions ici prend une API basée sur un code d'erreur hypothétique où tous les codes d'erreur sont documentés et un programmeur hypothétique qui lit la documentation, identifie correctement tous les cas d'erreur qui sont logiquement possibles dans son application et écrit du code pour gérer chacun d'entre eux , compare ensuite ce scénario à une API hypothétique basée sur les exceptions et à un programmeur où, pour une raison quelconque, l'une de ces étapes tourne mal ... même s'il est tout aussi facile (sans doute plus facile ) de faire toutes ces étapes correctement dans une API basée sur les exceptions.
Mark Amery

62

Je préfère normalement les exceptions, car elles ont plus d'informations contextuelles et peuvent transmettre (lorsqu'elles sont correctement utilisées) l'erreur au programmeur de manière plus claire.

En revanche, les codes d'erreur sont plus légers que les exceptions mais sont plus difficiles à maintenir. La vérification des erreurs peut être omise par inadvertance. Les codes d'erreur sont plus difficiles à gérer car vous devez conserver un catalogue avec tous les codes d'erreur, puis activer le résultat pour voir quelle erreur a été générée. Les plages d'erreur peuvent être utiles ici, car si la seule chose qui nous intéresse est de savoir si nous sommes en présence d'une erreur ou non, c'est plus simple à vérifier (par exemple, un code d'erreur HRESULT supérieur ou égal à 0 est le succès et moins de zéro est un échec). Ils peuvent être omis par inadvertance car il n'y a pas de forçage programmatique pour que le développeur vérifie les codes d'erreur. D'un autre côté, vous ne pouvez pas ignorer les exceptions.

Pour résumer, je préfère les exceptions aux codes d'erreur dans presque toutes les situations.


4
«les codes d'erreur sont plus légers que les exceptions» dépend de ce que vous mesurez et de la façon dont vous mesurez. Il est assez facile de proposer des tests qui montrent que les API basées sur les exceptions peuvent être beaucoup plus rapides.
Mooing Duck

1
@smink, bons points, mais comment aborder la surcharge des exceptions? Les codes d'erreur ne sont pas seulement légers, ils sont fondamentalement sans poids ; les exceptions ne sont pas seulement de poids moyen, ce sont des objets lourds contenant des informations de pile et des choses diverses que nous n'utilisons pas de toute façon.
Pacerier

3
@Pacerier: Vous envisagez uniquement des tests où des exceptions sont levées. Vous avez parfaitement raison de dire que lever une exception C ++ est beaucoup plus lent que renvoyer un code d'erreur. Sans conteste, pas de débat. Là où nous ne différons, est l'autre 99,999% du code. À quelques exceptions près, nous n'avons pas à vérifier les retours de code d'erreur entre chaque instruction, ce qui rend ce code de 1 à 50% plus rapide (ou non, selon votre compilateur). Ce qui signifie que le code complet peut être plus rapide ou plus lent, en fonction entièrement de la façon dont le code est écrit et de la fréquence à laquelle les exceptions sont levées.
Mooing Duck

1
@Pacerier: Avec ce test artificiel que je viens d'écrire, le code basé sur les exceptions est aussi rapide que les codes d'erreur dans MSVC et Clang, mais pas GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (minutages en bas). Il semble que les indicateurs GCC que j'ai utilisés ont généré des exceptions inhabituellement lentes par rapport aux autres compilateurs. Je suis partial, alors n'hésitez pas à critiquer mon test et n'hésitez pas à essayer une autre variante.
Mooing Duck le

4
@Mooing Duck, Si vous avez testé les codes d'erreur et les exceptions en même temps, vous devez avoir activé les exceptions. Si tel est le cas, vos résultats suggèrent que l'utilisation de codes d'erreur avec une surcharge de gestion des exceptions n'est pas plus lente que d'utiliser uniquement des exceptions. Les tests de code d'erreur doivent être exécutés avec les exceptions désactivées pour obtenir des résultats significatifs.
Mika Haarahiltunen

24

Je préfère les exceptions car

  • ils interrompent le flux de la logique
  • ils bénéficient d'une hiérarchie de classes qui donne plus de fonctionnalités / fonctionnalités
  • lorsqu'il est utilisé correctement, il peut représenter un large éventail d'erreurs (par exemple, une InvalidMethodCallException est également une exception LogicException, car les deux se produisent lorsqu'il y a un bogue dans votre code qui devrait être détectable avant l'exécution), et
  • ils peuvent être utilisés pour améliorer l'erreur (c'est-à-dire qu'une définition de classe FileReadException peut alors contenir du code pour vérifier si le fichier existe, ou est verrouillé, etc.)

2
Votre quatrième point n'est pas juste: un statut d'erreur lorsqu'il est converti en objet, peut également contenir du code pour vérifier si le fichier existe, ou est verrouillé, etc. C'est simplement une variante de stackoverflow.com/a/3157182/632951
Pacerier

1
"ils interrompent le flux de la logique". Les exceptions ont plus ou moins les effets degoto
peterchaula

22

Les codes d'erreur peuvent être ignorés (et le sont souvent!) Par les appelants de vos fonctions. Les exceptions les forcent au moins à gérer l'erreur d'une manière ou d'une autre. Même si leur version du traitement est d'avoir un gestionnaire de capture vide (soupir).


17

Exceptions sur les codes d'erreur, sans aucun doute. Vous obtenez les mêmes avantages des exceptions que les codes d'erreur, mais aussi bien plus encore, sans les lacunes des codes d'erreur. Le seul problème, c'est qu'il est légèrement plus frais; mais de nos jours, ces frais généraux devraient être considérés comme négligeables pour presque toutes les applications.

Voici quelques articles discutant, comparant et opposant les deux techniques:

Il y a quelques bons liens dans ceux qui peuvent vous donner plus de lecture.


16

Je ne mélangerais jamais les deux modèles ... c'est trop difficile de convertir de l'un à l'autre lorsque vous passez d'une partie de la pile qui utilise des codes d'erreur, à une pièce plus élevée qui utilise des exceptions.

Les exceptions sont pour "tout ce qui arrête ou empêche la méthode ou le sous-programme de faire ce que vous lui avez demandé de faire" ... NE PAS renvoyer des messages sur des irrégularités ou des circonstances inhabituelles, ou sur l'état du système, etc. Utilisez des valeurs de retour ou ref (ou out) paramètres pour cela.

Les exceptions permettent d'écrire (et d'utiliser) des méthodes avec une sémantique qui dépend de la fonction de la méthode, c'est-à-dire qu'une méthode qui retourne un objet Employee ou une liste d'employés peut être saisie pour faire exactement cela, et vous pouvez l'utiliser en appelant.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Avec les codes d'erreur, toutes les méthodes renvoient un code d'erreur, donc, pour celles qui doivent renvoyer quelque chose d'autre à utiliser par le code appelant, vous devez passer une variable de référence à remplir avec ces données et tester la valeur de retour pour le code d'erreur et gérez-le à chaque appel de fonction ou de méthode.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Si vous codez de manière à ce que chaque méthode fasse une et une seule chose simple, vous devez alors lever une exception chaque fois que la méthode ne peut pas atteindre l'objectif souhaité de la méthode. Les exceptions sont beaucoup plus riches et plus faciles à utiliser de cette manière que les codes d'erreur. Votre code est beaucoup plus propre - Le flux standard du chemin de code "normal" peut être consacré strictement au cas où la méthode EST capable d'accomplir ce que vous vouliez qu'elle fasse ... Et puis le code pour nettoyer, ou gérer le Des circonstances "exceptionnelles" où quelque chose de mauvais se produit qui empêche la méthode de se terminer correctement peuvent être isolées du code normal. De plus, si vous ne pouvez pas gérer l'exception là où elle s'est produite et que vous devez la transmettre de la pile à une interface utilisateur (ou pire, à travers le fil d'un composant de niveau intermédiaire à une interface utilisateur), alors avec le modèle d'exception,


Très bonne réponse! Solution prête à l'emploi juste à temps!
Cristian E.

11

Dans le passé, j'ai rejoint le camp des codes d'erreur (j'ai trop programmé en C). Mais maintenant j'ai vu la lumière.

Oui, les exceptions sont un peu un fardeau pour le système. Mais ils simplifient le code, réduisant le nombre d'erreurs (et de WTF).

Utilisez donc l'exception, mais utilisez-les judicieusement. Et ils seront vos amis.

En remarque. J'ai appris à documenter quelle exception peut être levée par quelle méthode. Malheureusement, cela n'est pas requis par la plupart des langues. Mais cela augmente les chances de gérer les bonnes exceptions au bon niveau.


1
yap C laisse quelques habitudes en nous tous;)
Jorge Ferreira

11

Il peut y avoir quelques situations où l'utilisation d'exceptions de manière claire, claire et correcte est fastidieuse, mais la grande majorité des exceptions de temps sont le choix évident. Le plus grand avantage de la gestion des exceptions par rapport aux codes d'erreur est qu'elle modifie le flux d'exécution, ce qui est important pour deux raisons.

Lorsqu'une exception se produit, l'application ne suit plus son chemin d'exécution «normal». La première raison pour laquelle cela est si important est qu'à moins que l'auteur du code ne se démène pour être mauvais, le programme s'arrêtera et ne continuera pas à faire des choses imprévisibles. Si un code d'erreur n'est pas vérifié et que les actions appropriées ne sont pas prises en réponse à un mauvais code d'erreur, le programme continuera à faire ce qu'il fait et qui sait quel sera le résultat de cette action. Il existe de nombreuses situations dans lesquelles le fait de faire «n'importe quoi» par le programme pourrait s'avérer très coûteux. Envisagez un programme qui récupère les informations sur les performances de divers instruments financiers vendus par une entreprise et qui fournit ces informations aux courtiers / grossistes. Si quelque chose ne va pas et que le programme continue, il pourrait envoyer des données de performance erronées aux courtiers et aux grossistes. Je ne connais personne d'autre, mais je ne veux pas être celui assis dans un bureau de vice-président expliquant pourquoi mon code a amené l'entreprise à obtenir 7 chiffres d'amendes réglementaires. Délivrer un message d'erreur aux clients est généralement préférable à la fourniture de données erronées qui pourraient sembler `` réelles '', et cette dernière situation est beaucoup plus facile à rencontrer avec une approche beaucoup moins agressive comme les codes d'erreur.

La deuxième raison pour laquelle j'aime les exceptions et leur rupture de l'exécution normale est qu'il est beaucoup, beaucoup plus facile de séparer la logique «des choses normales se passent» de la logique «quelque chose s'est mal passé». Pour moi, ceci:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... est préférable à ceci:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Il y a d'autres petites choses sur les exceptions qui sont également intéressantes. Avoir un tas de logique conditionnelle pour savoir si l'une des méthodes appelées dans une fonction a eu un code d'erreur renvoyé, et renvoyer ce code d'erreur plus haut est beaucoup de plaques chauffantes. En fait, c'est beaucoup de plaques chauffantes qui peuvent mal tourner. J'ai beaucoup plus confiance dans le système d'exceptions de la plupart des langues que dans un nid de rats de déclarations if-else-if-else que Fred a écrites `` Fresh-out-of-college '', et j'ai beaucoup mieux à faire avec mon temps que le code passant en revue ledit nid de rat.


8

Vous devez utiliser les deux. La chose est de décider quand utiliser chacun d'eux .

Il existe quelques scénarios où les exceptions sont le choix évident :

  1. Dans certaines situations, vous ne pouvez rien faire avec le code d'erreur , et il vous suffit de le gérer à un niveau supérieur de la pile d'appels , généralement simplement enregistrer l'erreur, afficher quelque chose à l'utilisateur ou fermer le programme. Dans ces cas, les codes d'erreur vous obligeraient à remonter manuellement les codes d'erreur niveau par niveau, ce qui est évidemment beaucoup plus facile à faire avec des exceptions. Le fait est que c'est pour des situations inattendues et impossibles à gérer .

  2. Pourtant, à propos de la situation 1 (où quelque chose d'inattendu et d'incapable se produit, vous ne voulez tout simplement pas le consigner), les exceptions peuvent être utiles car vous pouvez ajouter des informations contextuelles . Par exemple, si j'obtiens une SqlException dans mes assistants de données de niveau inférieur, je voudrais attraper cette erreur dans le bas niveau (où je connais la commande SQL qui a causé l'erreur) afin que je puisse capturer ces informations et renvoyer avec des informations supplémentaires . Veuillez noter le mot magique ici: relancer et ne pas avaler . La première règle de gestion des exceptions: ne pas avaler les exceptions . Notez également que ma capture interne n'a pas besoin de consigner quoi que ce soit car la capture externe aura toute la trace de la pile et peut la journaliser.

  3. Dans certaines situations, vous avez une séquence de commandes, et si l'une d'entre elles échoue, vous devez nettoyer / éliminer les ressources (*), qu'il s'agisse ou non d'une situation irrécupérable (qui devrait être lancée) ou d'une situation récupérable (auquel cas vous pouvez gérer localement ou dans le code de l'appelant mais vous n'avez pas besoin d'exceptions). De toute évidence, il est beaucoup plus facile de mettre toutes ces commandes en un seul essai, au lieu de tester les codes d'erreur après chaque méthode, et de nettoyer / supprimer dans le bloc finally. Veuillez noter que si vous voulez que l'erreur apparaisse (ce qui est probablement ce que vous voulez), vous n'avez même pas besoin de l'attraper - vous utilisez simplement le finally pour le nettoyage / l'élimination - vous ne devriez utiliser catch / retrow que si vous le souhaitez pour ajouter des informations contextuelles (voir puce 2).

    Un exemple serait une séquence d'instructions SQL à l'intérieur d'un bloc de transaction. Encore une fois, c'est aussi une situation «impossible à gérer», même si vous décidez de l'attraper tôt (traitez-la localement au lieu de remonter au sommet), c'est toujours une situation fatale d'où le meilleur résultat est de tout abandonner ou au moins d'annuler un gros partie du processus.
    (*) C'est comme celui on error gotoque nous avons utilisé dans l'ancien Visual Basic

  4. Dans les constructeurs, vous ne pouvez lever que des exceptions.

Cela dit, dans toutes les autres situations où vous retournez des informations sur lesquelles l'appelant PEUT / DEVRAIT prendre des mesures , l'utilisation de codes de retour est probablement une meilleure alternative. Cela inclut toutes les "erreurs" attendues , car elles devraient probablement être gérées par l'appelant immédiat et n'auront guère besoin d'être remontées de trop nombreux niveaux dans la pile.

Bien sûr, il est toujours possible de traiter les erreurs attendues comme des exceptions, puis d'attraper immédiatement un niveau au-dessus, et il est également possible d'englober chaque ligne de code dans une tentative de capture et de prendre des mesures pour chaque erreur possible. OMI, c'est une mauvaise conception, non seulement parce qu'elle est beaucoup plus verbeuse, mais surtout parce que les exceptions possibles qui pourraient être levées ne sont pas évidentes sans la lecture du code source - et des exceptions pourraient être lancées à partir de n'importe quelle méthode profonde, créant des gotos invisibles . Ils cassent la structure du code en créant plusieurs points de sortie invisibles qui rendent le code difficile à lire et à inspecter. En d'autres termes, vous ne devez jamais utiliser d' exceptions comme contrôle de flux, car cela serait difficile à comprendre et à maintenir pour les autres. Il peut même être difficile de comprendre tous les flux de code possibles pour les tests.
Encore une fois: pour un nettoyage / une mise au rebut corrects, vous pouvez utiliser try-finally sans rien attraper .

La critique la plus répandue concernant les codes de retour est que "quelqu'un pourrait ignorer les codes d'erreur, mais dans le même sens, quelqu'un peut également avaler des exceptions. Une mauvaise gestion des exceptions est facile dans les deux méthodes. Mais écrire un bon programme basé sur des codes d'erreur est encore beaucoup plus facile que d'écrire un programme basé sur les exceptions . Et si, pour une raison quelconque, quelqu'un décide d'ignorer toutes les erreurs (les anciennes on error resume next), vous pouvez facilement le faire avec des codes de retour et vous ne pouvez pas le faire sans beaucoup de passe-partout.

La deuxième critique la plus répandue à propos des codes de retour est qu '"il est difficile de faire des bulles" - mais c'est parce que les gens ne comprennent pas que les exceptions sont pour des situations non récupérables, alors que les codes d'erreur ne le sont pas.

Le choix entre les exceptions et les codes d'erreur est une zone grise. Il est même possible que vous ayez besoin d'obtenir un code d'erreur à partir d'une méthode commerciale réutilisable, puis que vous décidiez de l'envelopper dans une exception (éventuellement en ajoutant des informations) et de la laisser bouillonner. Mais c'est une erreur de conception de supposer que TOUTES les erreurs doivent être levées comme exceptions.

Résumer:

  • J'aime utiliser des exceptions lorsque j'ai une situation inattendue, dans laquelle il n'y a pas grand chose à faire, et généralement nous voulons abandonner un gros bloc de code ou même toute l'opération ou le programme. C'est comme l'ancien "on error goto".

  • J'aime utiliser des codes de retour lorsque j'ai prévu des situations dans lesquelles le code de l'appelant peut / doit prendre des mesures. Cela inclut la plupart des méthodes commerciales, API, validations, etc.

Cette différence entre les exceptions et les codes d'erreur est l'un des principes de conception du langage GO, qui utilise la «panique» pour les situations inattendues fatales, tandis que les situations normales prévues sont renvoyées sous forme d'erreurs.

Pourtant, à propos de GO, il autorise également plusieurs valeurs de retour , ce qui aide beaucoup à utiliser les codes de retour, car vous pouvez simultanément renvoyer une erreur et autre chose. Sur C # / Java, nous pouvons y parvenir sans paramètres out, tuples ou (mes préférés) génériques, qui, combinés avec des enums, peuvent fournir des codes d'erreur clairs à l'appelant:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

Si j'ajoute un nouveau retour possible dans ma méthode, je peux même vérifier tous les appelants s'ils couvrent cette nouvelle valeur dans une instruction switch par exemple. Vous ne pouvez vraiment pas faire cela avec des exceptions. Lorsque vous utilisez des codes de retour, vous connaissez généralement à l'avance toutes les erreurs possibles et testez-les. À quelques exceptions près, vous ne savez généralement pas ce qui pourrait arriver. Emballer les énumérations dans des exceptions (au lieu de Generics) est une alternative (tant que le type d'exceptions que chaque méthode va lancer) est clair, mais IMO, c'est toujours une mauvaise conception.


4

Mon raisonnement serait que si vous écrivez un pilote de bas niveau qui a vraiment besoin de performances, utilisez des codes d'erreur. Mais si vous utilisez ce code dans une application de niveau supérieur et qu'elle peut gérer un peu de surcharge, enveloppez ce code avec une interface qui vérifie ces codes d'erreur et déclenche des exceptions.

Dans tous les autres cas, les exceptions sont probablement la voie à suivre.


4

Je suis peut-être assis sur la clôture ici, mais ...

  1. Cela dépend de la langue.
  2. Quel que soit le modèle que vous choisissez, soyez cohérent sur la façon dont vous l'utilisez.

En Python, l'utilisation d'exceptions est une pratique standard, et je suis assez heureux de définir mes propres exceptions. En C, vous n'avez pas du tout d'exceptions.

En C ++ (au moins dans la STL), les exceptions ne sont généralement lancées que pour des erreurs vraiment exceptionnelles (je ne les vois pratiquement jamais moi-même). Je ne vois aucune raison de faire quelque chose de différent dans mon propre code. Oui, il est facile d'ignorer les valeurs de retour, mais C ++ ne vous oblige pas non plus à intercepter les exceptions. Je pense qu'il faut juste prendre l'habitude de le faire.

La base de code sur laquelle je travaille est principalement C ++ et nous utilisons des codes d'erreur presque partout, mais il y a un module qui soulève des exceptions pour toute erreur, y compris des erreurs très peu exceptionnelles, et tout le code qui utilise ce module est assez horrible. Mais c'est peut-être simplement parce que nous avons mélangé des exceptions et des codes d'erreur. Le code qui utilise systématiquement des codes d'erreur est beaucoup plus facile à utiliser. Si notre code utilisait systématiquement des exceptions, ce ne serait peut-être pas aussi grave. Mélanger les deux ne semble pas fonctionner si bien.


4

Puisque je travaille avec C ++ et que j'ai RAII pour les rendre sûrs à utiliser, j'utilise presque exclusivement des exceptions. Il extrait la gestion des erreurs du déroulement normal du programme et rend l'intention plus claire.

Je laisse cependant des exceptions pour des circonstances exceptionnelles. Si je m'attends à ce qu'une certaine erreur se produise souvent, je vérifierai que l'opération réussira avant de l'exécuter, ou j'appellerai une version de la fonction qui utilise des codes d'erreur à la place (Like TryParse())


3

Les signatures de méthode doivent vous communiquer ce que fait la méthode. Quelque chose comme long errorCode = getErrorCode (); peut être bien, mais long errorCode = fetchRecord (); prête à confusion.


3

Mon approche est que nous pouvons utiliser les deux, c'est-à-dire les codes d'exceptions et d'erreurs en même temps.

J'ai l'habitude de définir plusieurs types d'exceptions (ex: DataValidationException ou ProcessInterruptExcepion) et à l'intérieur de chaque exception définir une description plus détaillée de chaque problème.

Un exemple simple en Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

De cette façon, j'utilise des exceptions pour définir les erreurs de catégorie et des codes d'erreur pour définir des informations plus détaillées sur le problème.


2
euh ... throw new DataValidationException("The input is too small")? L'un des avantages des exceptions est de permettre des informations détaillées.
Eva

2

Les exceptions concernent des circonstances exceptionnelles , c'est-à-dire lorsqu'elles ne font pas partie du flux normal du code.

Il est tout à fait légitime de mélanger les exceptions et les codes d'erreur, où les codes d'erreur représentent l'état de quelque chose, plutôt qu'une erreur dans l'exécution du code en soi (par exemple, vérifier le code de retour d'un processus enfant).

Mais quand une circonstance exceptionnelle se produit, je crois que les exceptions sont le modèle le plus expressif.

Il y a des cas où vous pourriez préférer, ou avoir, utiliser des codes d'erreur à la place des exceptions, et ceux-ci ont déjà été correctement couverts (autres que d'autres contraintes évidentes telles que la prise en charge du compilateur).

Mais aller dans l'autre sens, utiliser les exceptions vous permet de créer des abstractions de niveau encore plus élevé pour votre gestion des erreurs, ce qui peut rendre votre code encore plus expressif et naturel. Je recommanderais vivement de lire cet excellent article, mais sous-estimé, de l'expert C ++ Andrei Alexandrescu sur ce qu'il appelle «Enforcements»: http://www.ddj.com/cpp/184403864 . Bien qu'il s'agisse d'un article C ++, les principes sont généralement applicables, et j'ai traduit le concept enforcements en C # avec beaucoup de succès.


2

Tout d'abord, je suis d'accord avec la réponse de Tom selon laquelle pour les éléments de haut niveau, utilisez des exceptions, et pour les éléments de bas niveau, utilisez des codes d'erreur, tant qu'il ne s'agit pas d'une architecture orientée services (SOA).

Dans SOA, où les méthodes peuvent être appelées sur différentes machines, les exceptions ne peuvent pas être passées sur le câble, à la place, nous utilisons des réponses succès / échec avec une structure comme ci-dessous (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

Et utilisez comme ceci:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

Lorsqu'elles sont utilisées de manière cohérente dans vos réponses de service, cela crée un très bon modèle de gestion des succès / échecs dans l'application. Cela facilite la gestion des erreurs dans les appels asynchrones au sein des services ainsi qu'entre les services.


1

Je préférerais des exceptions pour tous les cas d'erreur, sauf lorsqu'un échec est un résultat prévisible sans bogue d'une fonction qui renvoie un type de données primitif. Par exemple, trouver l'index d'une sous-chaîne dans une chaîne plus grande renverrait généralement -1 s'il n'est pas trouvé, au lieu de déclencher une NotFoundException.

Le renvoi de pointeurs non valides susceptibles d'être déréférencés (par exemple, provoquant une exception NullPointerException en Java) n'est pas acceptable.

L'utilisation de plusieurs codes d'erreur numériques différents (-1, -2) comme valeurs de retour pour la même fonction est généralement un mauvais style, car les clients peuvent effectuer une vérification "== -1" au lieu de "<0".

Une chose à garder à l'esprit ici est l'évolution des API au fil du temps. Une bonne API permet de changer et d'étendre le comportement en cas de panne de plusieurs manières sans casser les clients. Par exemple, si un descripteur d'erreur client a vérifié 4 cas d'erreur et que vous ajoutez une cinquième valeur d'erreur à votre fonction, le gestionnaire client peut ne pas le tester et interrompre. Si vous augmentez des exceptions, cela facilitera généralement la migration des clients vers une version plus récente d'une bibliothèque.

Une autre chose à considérer est lorsque vous travaillez en équipe, où tracer une ligne claire pour que tous les développeurs prennent une telle décision. Par exemple, "Exceptions pour les éléments de haut niveau, codes d'erreur pour les éléments de bas niveau" est très subjectif.

Dans tous les cas, là où plus d'un type d'erreur trivial est possible, le code source ne doit jamais utiliser le littéral numérique pour renvoyer un code d'erreur ou pour le gérer (retourne -7, si x == -7 ...), mais toujours une constante nommée (retourne NO_SUCH_FOO, si x == NO_SUCH_FOO).


1

Si vous travaillez dans le cadre d'un gros projet, vous ne pouvez pas utiliser uniquement des exceptions ou uniquement des codes d'erreur. Dans différents cas, vous devez utiliser des approches différentes.

Par exemple, vous décidez de n'utiliser que des exceptions. Mais une fois que vous décidez d'utiliser le traitement des événements asynchrones. Il n'est pas judicieux d'utiliser des exceptions pour la gestion des erreurs dans ces situations. Mais utiliser des codes d'erreur partout dans l'application est fastidieux.

Donc, à mon avis, il est normal d'utiliser à la fois des exceptions et des codes d'erreur simultanément.


0

Pour la plupart des applications, les exceptions sont meilleures. L'exception est lorsque le logiciel doit communiquer avec d'autres appareils. Le domaine dans lequel je travaille est celui des contrôles industriels. Ici, les codes d'erreur sont préférés et attendus. Ma réponse est donc que cela dépend de la situation.


0

Je pense que cela dépend également du fait que vous ayez vraiment besoin d'informations telles que la trace de pile à partir du résultat. Si oui, vous optez certainement pour Exception qui fournit un objet plein d'informations sur le problème. Cependant, si vous êtes simplement intéressé par le résultat et que vous ne vous souciez pas de la raison de ce résultat, optez pour un code d'erreur.

Par exemple, lorsque vous traitez un fichier et que vous faites face à IOException, le client peut souhaiter savoir d'où cela a été déclenché, ouvrir le fichier ou analyser le fichier, etc. Il vaut donc mieux renvoyer IOException ou sa sous-classe spécifique. Cependant, si vous avez une méthode de connexion et que vous voulez savoir si cela a réussi ou non, il vous suffit de retourner le booléen ou d'afficher le message correct, de renvoyer le code d'erreur. Ici, le client n'est pas intéressé à savoir quelle partie de la logique a causé ce code d'erreur. Il sait juste si ses identifiants sont invalides ou si son compte est verrouillé, etc.

Un autre cas d'utilisation auquel je peux penser est lorsque les données voyagent sur le réseau. Votre méthode distante peut renvoyer uniquement un code d'erreur au lieu d'exception pour minimiser le transfert de données.


0

Ma règle générale est:

  • Une seule erreur peut apparaître dans une fonction: utiliser le code d'erreur (comme paramètre de la fonction)
  • Plusieurs erreurs spécifiques peuvent apparaître: lancer une exception

-1

Les codes d'erreur ne fonctionnent pas non plus lorsque votre méthode renvoie autre chose qu'une valeur numérique ...


3
Um non. Voir le paradigme win32 GetLastError (). Je ne le défends pas, je dis juste que vous avez tort.
Tim

4
En fait, il existe de nombreuses autres façons de le faire. Une autre méthode consiste à renvoyer un objet contenant le code d'erreur et la valeur de retour réelle. Une autre manière encore est de faire passer des références.
Pacerier
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.