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 :
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 .
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.
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 goto
que nous avons utilisé dans l'ancien Visual Basic
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.