Exceptions, codes d'erreur et syndicats discriminés

J'ai récemment commencé un travail en programmation C#, mais j'ai pas mal d'expérience en Haskell.

Mais je comprends que C# est un langage orienté objet, je ne veux pas forcer une cheville ronde dans un trou carré.

J'ai lu l'article Lancement d'exceptions de Microsoft, qui États:

NE PAS renvoyer les codes d'erreur.

Mais étant habitué à Haskell, j'utilise le type de données C# OneOf , en renvoyant le résultat sous forme de valeur "droite" ou l'erreur (le plus souvent une énumération) sous forme de valeur "à gauche".

Cela ressemble beaucoup à la convention de Soit dans Haskell.

Pour moi, cela semble plus sûr que les exceptions. En C#, ignorer les exceptions ne génère pas d'erreur de compilation et si elles ne sont pas interceptées, elles ne font que bouillonner et faire planter votre programme. C'est peut-être mieux que d'ignorer un code d'erreur et de produire un comportement indéfini, mais bloquer le logiciel de votre client n'est toujours pas une bonne chose, en particulier lorsqu'il effectue de nombreuses autres tâches importantes en arrière-plan.

Avec OneOf , il faut être assez explicite sur son décompression et sa gestion. valeur de retour et les codes d'erreur. Et si on ne sait pas comment le gérer à ce stade de la pile d'appels, il doit être placé dans la valeur de retour de la fonction actuelle, afin que les appelants sachent qu'une erreur peut survenir.

Mais cela ne semble pas être l'approche suggérée par Microsoft.

Utilise OneOf au lieu des exceptions pour la gestion des exceptions "ordinaires" (comme Fichier Not Found etc) une approche raisonnable ou est-ce une pratique terrible?


Il est à noter que j'ai entendu dire que les exceptions en tant que flux de contrôle sont considérées comme un anti-modèle sérieux , donc si" l'exception "est quelque chose que vous devriez normalement gérer sans mettre fin au programme, n'est-ce pas un" flux de contrôle "? Je comprends qu'il y a une zone d'ombre.

Remarque Je n'utilise pas OneOf pour des tâches comme "Mémoire insuffisante". , conditions que je ne compte pas récupérer, jetteront encore des exceptions. Mais j’ai l’impression que des problèmes tout à fait raisonnables, tels que les entrées d’utilisateur qui n’analysent pas sont essentiellement du "flux de contrôle" et ne devraient probablement pas renvoyer d’exceptions.


Réflexions suivantes:

Ce que je retiens actuellement de cette discussion est le suivant:

  1. Si vous attendez l'appelant immédiat pour intercepter , gérer l'exception la plupart du temps et poursuivre son travail, peut-être par un autre chemin, il devrait probablement faire partie du type de retour. Facultatif ou OneOf peut être utile ici.
  2. Si vous vous attendez à ce que l'appelant immédiat ne détecte pas l'exception la plupart du temps, lancez une exception pour ne pas perdre la bêtise de la transmettre manuellement dans la pile.
  3. Si vous n'êtes pas sûr de ce que l'appelant immédiat va faire, indiquez les deux, comme Parse et TryParse .
70
ajouté édité
Vues: 1
Je pense qu'il serait utile de mettre un exemple de code sur la façon dont vous utilisez OneOf dans votre question, car je pense qu'il y a une certaine confusion à propos du fait que OneOf est lui-même un "code d'erreur".
ajouté l'auteur TravisO, source
@ T.Sar Je ne suis pas celui qui manque le point. Oui, mieux vaut s’effondrer que de continuer dans un état incertain. J'en suis conscient, et la question l'indique aussi précisément. Mais, planter n'est pas bon (cela peut causer des problèmes supplémentaires). Ce qui est bien, c'est de ne pas être dans un état incertain. Cette question demande s'il est logique d'utiliser une technique (en C #) qui attire l'attention sur des sources potentielles d'état incertain et vous oblige à prendre une décision sur la façon de les gérer. La question reconnaît que cette technique ne fonctionnera pas toujours (MOO), mais concerne les nombreux cas dans lesquels elle fonctionne.
ajouté l'auteur steve, source
@ T.Sar Les tests nécessitent généralement l'exécution (au moins une partie) du code. Je parle de détecter les problèmes lors de la compilation. Avec les unions discriminées, au lieu de renvoyer un Fichier lors de l'ouverture d'un fichier, vous renverriez un Résultat . Un Résultat n'est pas un Fichier . Par conséquent, si vous oubliez de le gérer, essayez simplement de l'utiliser comme un Fichier , vous obtiendrez une erreur de compilation. Au lieu de cela, vous devez vérifier ce qu'il contient et extraire le Fichier ou Erreur , ou vous pouvez différer le traitement de l'erreur et utiliser des méthodes telles que map pour opérer en toute sécurité sur le fichier contenu ou noop sur un erreur
ajouté l'auteur steve, source
@ T.Sar Avez-vous envisagé la possibilité de détecter le problème avant l'exécution du programme, en précisant le numéro de ligne exact (peut-être même le caractère) où il se produit? Et puisque vous ne travailliez probablement que sur ce code (vous essayez de compiler vos fichiers de temps en temps, n'est-ce pas?), Vous pouvez probablement trouver un traitement approprié en moins de 30 secondes à 30 minutes. Il semblerait que ce serait la meilleure option que d’espérer que les tests rencontrent la bonne situation pour déclencher l’exception et un crash avant que celui-ci ne s’échappe dans la nature et que la journée de certains utilisateurs malchanceux s’arrête brutalement.
ajouté l'auteur steve, source
C # n'a pas traditionnellement réservé d'exceptions, pour des circonstances exceptionnelles. Par exemple. cela se fera par une exception pour End Of File. D'autres langues renverront une erreur ou utiliseront les méthodes de requête is_end_of_file , et ne lèveront qu'une exception, si une tentative de lecture dépasse la fin du fichier.
ajouté l'auteur Vanja, source
@ SørenD.Ptæus Cela devrait être une réponse (et ce serait, incidemment, une meilleure réponse que toutes les réponses existantes).
ajouté l'auteur Mark Ransom, source
Comment se fait-il qu'aucune des réponses ne précise que le Soit monade n'est pas un code d'erreur , ni OneOf ? Elles sont fondamentalement différentes et la question semble par conséquent reposer sur un malentendu. (Bien que sous une forme modifiée, cela reste une question valide.)
ajouté l'auteur Mark Ransom, source
Votre approche ressemble à réinventer les exceptions vérifiées. Avec tous les avantages et problèmes, sauf que vous devez mettre votre programme en panne vous-même si vous ne pouvez pas gérer l'erreur. Dans l’ensemble, c’est toujours beaucoup mieux que les codes de retour.
ajouté l'auteur Navaar, source
... la deuxième opération est effectuée, mais cette approche peut garantir que les erreurs non gérées ne passent pas inaperçues sans imposer le coût des mécanismes de traitement des exceptions dans les cas où le code est prêt à traiter les erreurs.
ajouté l'auteur supercat, source
@ jpmc26: L'une des approches que j'ai observées avant que la POO ne devienne populaire et que j'aime assez, mais que je n'aie pas discuté récemment, est d'avoir la première erreur à la fois verrouiller et renvoyer un code d'erreur et obliger l'appelant à reconnaître explicitement l'erreur avant de tenter l'opération suivante. Tenter une seconde opération sans accuser réception d'erreur déclenchera une interruption. Il faut faire attention à ne pas perdre la cause de l'erreur initiale avec un risque de confusion (ou même de problèmes de sécurité) pouvant en résulter si elle est rapportée quand ...
ajouté l'auteur supercat, source
@ jpmc26: vous pouvez également ne pas gérer correctement une exception dans une clause catch. Je ne vois pas ce qui est perdu ici. Un appelant qui ne veut pas traiter les erreurs peut simplement les renvoyer en les renvoyant.
ajouté l'auteur mukesh ojha, source
@ SørenD.Ptæus Je ne comprends pas pourquoi la fonction d’ouverture de fichier ne peut tout simplement pas renvoyer ‘Option ’ pour éviter la condition de concurrence et l’exception.
ajouté l'auteur mukesh ojha, source
Le rappel a également moins de valeur si votre code est quand même sans état, auquel cas vous n'avez pas particulièrement besoin d'un rappel pour faire quoi que ce soit.
ajouté l'auteur jmibanez, source
Un peu hors sujet, mais notez que l'approche que vous envisagez présente la vulnérabilité générale suivante: il est impossible de garantir que le développeur gère correctement l'erreur. Bien sûr, le système de dactylographie peut servir de rappel, mais vous perdez le défaut de faire exploser le programme pour ne pas avoir géré une erreur, ce qui augmente le risque que vous vous retrouviez dans un état inattendu. Que le rappel vaille la peine de perdre ce défaut est un débat ouvert, cependant. J'ai tendance à penser qu'il est préférable d'écrire tout votre code en supposant qu'une exception le finira à un moment donné; alors le rappel a moins de valeur.
ajouté l'auteur jmibanez, source
Lorsqu'une méthode renvoie null, vous n'avez pas la motivation de vérifier la valeur renvoyée, car vous ne pouvez pas, car il n'y en a pas. Si vous remplacez cela par une solution basée sur un code d'erreur, vous pouvez oublier autant que vous pouvez oublier avec une exception. De plus, les exceptions .NET ne coûtent pas cher, il n’ya pas de coût d’exécution pour une tentative d’attrape, sauf s’il ya une exception. Mais vérifier la valeur de retour a toujours un coût.
ajouté l'auteur Joel, source
Je pense que vous avez bien résumé la pensée. L'opinion d'une personne a évolué vers un modèle d'erreur plus similaire à Clu, et je pense que vous comprendrez pourquoi c'est mieux: joeduffyblog.com/2016/02/07/the-error-model Dans un tel modèle, vous pouvez utiliser le style fonctionnel ou d'exception, mais la propagation de l'exception s'arrête par défaut à certaines limites. . Les exceptions consistent spécifiquement à jeter l'état et à récupérer; souvent, tout le processus est la seule chose que nous puissions rejeter, et la récupération en nécessite une nouvelle.
ajouté l'auteur Frank Hileman, source
@Clinton - J'avais déjà posé une question similaire il y a quelque temps - softwareengineering.stackexchange.com/questions/271127/…
ajouté l'auteur Carl kay, source
Je suis peut-être seul à ce sujet, mais je pense que le crash est bon . Il n’existe pas de meilleure preuve que votre logiciel pose problème, c’est un journal des collisions avec le suivi approprié. Si votre équipe est capable de corriger et de déployer un correctif en 30 minutes ou moins, laisser ces vilains copains se présenter peut ne pas être une mauvaise chose.
ajouté l'auteur T. Sar, source
@ 8bittree Vous manquez le point. Crashing ne consiste pas à ne rien manipuler. Il s'agit d'arrêter le système lorsqu'il se trouve dans un état incertain. Vous pouvez mettre beaucoup de traitement des erreurs dans votre code, vous pouvez vérifier au moment de la compilation, faire ce que vous voulez - cela ne signifie pas que vous allez couvrir tous les cas extrêmes, et si cela échoue, bien - arrêtez et prenez feu.
ajouté l'auteur T. Sar, source
@ 8bittree Bien sûr, je l'ai fait et le fais - les tests unitaires et d'intégration sont là pour cela. Cependant, je ne serai pas naïf pour dire que nos tests unitaires couvriront tous les cas et que le logiciel n’aura jamais de bugs ou d’échecs - plus encore lorsque nous devons intégrer notre système à un système sous le contrôle d’une tierce partie, comme le gouvernement ou une université. Plus que cela - il y a plusieurs types de problèmes que vous ne pouvez pas détecter avant que le code ne soit exécuté (un problème dans la base de données, un fichier incorrect de l'utilisateur, un problème de pilote, etc.).
ajouté l'auteur T. Sar, source
@MarkAmery Je conviens que la terminologie prête à confusion, personnellement, je suis d'accord avec le cas limite du "flux de contrôle" dans lequel une méthode API ne lève pas d'exception mais renvoie des données valides mais inutilisables, par exemple, try { data = apiObj.ReadData (); if (data == null) jette une nouvelle SomeException ("Les données contenues étaient nuls") (...)} catch (SomeException ex) {jette une nouvelle SomeException ($ "Impossible de lire les données depuis X: {ex.Message}" ", ex)} - s'il se termine quand même au même endroit et que cela signifie conceptuellement la même chose (par exemple, le fichier n'était pas valide), il se peut également que l'erreur ne soit traitée que dans le bloc catch
ajouté l'auteur jrh, source
C’est mon approche préférée: elle est fonctionnelle et va dans le même sens que la réponse de DavidArno: enterprisecraftsmanship.com/2015/03/20/…
ajouté l'auteur user9993, source
J'avais l'impression que l'oxymoronique "n'utilisez pas d'exceptions comme contrôle de flux" était une retenue de C ++, où les exceptions sont relativement peu performantes par rapport à quelque chose comme boost :: optional (ou std :: optionnel , en date récente). Vous pouvez utiliser des exceptions dans de rares cas, tels qu'une erreur matérielle. mais pas pour les "échecs" hautement probables tels que la vérification d'un jeton particulier dans un analyseur (dans ce cas, une douzaine de vérifications pourraient "échouer" jusqu'à ce qu'une correspondance soit trouvée).
ajouté l'auteur Shabble, source
Je pense que nous devrions distinguer entre les "codes d'erreur" tels que errno de C et les syndicats discriminés. Peut-être que l'union discriminée utilise un numéro en interne comme balise indiquant quel cas est actif, mais il s'agit d'un détail d'implémentation qu'une bonne implémentation ne vous exposera pas.
ajouté l'auteur Alexander, source
Article connexe: Eric Lippert sur les quatre "compartiments" d'exceptions . L'exemple qu'il donne sur les exceptions exogènes montre qu'il existe des scénarios dans lesquels vous devez simplement gérer des exceptions, à savoir FileNotFoundException .
ajouté l'auteur user197693, source
Il convient également de noter que les exceptions remontent la pile jusqu'à ce qu'elles soient capturées, les valeurs renvoyées n'étant renvoyées qu'une seule fois, puis l'appelant func de l'appelant func ne peut jamais recevoir un avis d'erreur, si l'erreur n'est pas à nouveau gérée dans chaque fonction d'appel de la fonction sujette aux erreurs et ainsi de suite
ajouté l'auteur xinthose, source
Le lancement d'une exception est littéralement un mécanisme de flux de contrôle. Le dicton commun qui dit que cela ne devrait pas être fait "en tant que flux de contrôle" n'a donc pas de sens si on le prend à la lettre (ou du moins, pourrait être abrégé en simplement "ne pas utiliser d'exceptions"). Bien sûr, les gens qui utilisent le dicton ne ne le prennent pas littéralement; ils infèrent plutôt ce qu'ils considèrent être une interprétation de sens commun de ce que cela signifie. Le problème, c’est qu’ils ne précisent presque jamais exactement ce que cela signifie… et différents utilisateurs du dicton semblent y lire différentes significations.
ajouté l'auteur Mark Amery, source
Utilisez F # Résultat ;-)
ajouté l'auteur francolino, source

10 Réponses

mais planter le logiciel de votre client n'est toujours pas une bonne chose

C'est très certainement une bonne chose.

Vous voulez que tout ce qui laisse le système dans un état non défini arrête le système car un système non défini peut faire des choses désagréables comme des données corrompues, formater le disque dur et envoyer des courriels au président. Si vous ne pouvez pas restaurer le système et le remettre dans un état défini, alors le crash est la chose la plus responsable. C'est exactement pourquoi nous construisons des systèmes qui plantent au lieu de se déchirer tranquillement. Maintenant, bien sûr, nous voulons tous un système stable qui ne se bloque jamais, mais nous ne le souhaitons vraiment que lorsque le système reste dans un état de sécurité prévisible et défini.

J'ai entendu dire que les exceptions en tant que flux de contrôle sont considérées comme un anti-modèle sérieux

C'est absolument vrai, mais c'est souvent mal compris. Quand ils ont inventé le système d'exception, ils avaient peur de casser les programmations structurées. La programmation structurée explique pourquoi nous avons pour , tant que , jusqu'à , break et continue quand tout ce dont nous avons besoin, pour faire tout cela, est goto .

Dijkstra taught us that using goto informally (that is, jumping around wherever you like) makes reading code a nightmare. When they gave us the exception system they were afraid they were reinventing goto. So they told us not to "use it for flow control" hoping we'd understand. Unfortunately, many of us didn't.

Étrangement, nous n'utilisons pas souvent les exceptions pour créer du code spaghetti comme nous le faisions auparavant avec goto. Le conseil lui-même semble avoir causé plus de problèmes.

Fondamentalement, les exceptions consistent à rejeter une hypothèse. Lorsque vous demandez qu'un fichier soit enregistré, vous supposez qu'il peut et sera sauvegardé. L'exception que vous obtenez quand il ne peut pas être peut-être sur le nom étant illégal, le HD étant plein, ou parce qu'un rat a rongé à travers votre câble de données. Vous pouvez gérer toutes ces erreurs différemment, de la même manière ou les laisser arrêter le système. Il y a un chemin heureux dans votre code où vos hypothèses doivent être vérifiées. D'une manière ou d'une autre, des exceptions vous éloignent de cette voie heureuse. Strictement parlant, oui, c'est une sorte de "contrôle de flux", mais ce n'est pas pour cela qu'ils vous ont mis en garde. Ils parlaient de sottises comme celle-ci :

enter image description here

"Les exceptions devraient être exceptionnelles". Cette petite tautologie est née car les concepteurs de systèmes d'exception ont besoin de temps pour créer des traces de pile. Comparé aux sauts, c'est lent. Il mange du temps de calcul. Mais si vous êtes sur le point de vous connecter et d’arrêter le système, ou du moins d’interrompre le traitement qui prend beaucoup de temps avant de commencer le suivant, vous avez un peu de temps à perdre. Si les gens commencent à utiliser des exceptions "pour le contrôle de flux", ces hypothèses sur le temps disparaissent toutes. Donc, "Les exceptions devraient être exceptionnelles" nous a été vraiment donné comme une considération de performance.

Bien plus important que cela ne nous confond pas. Combien de temps vous a-t-il fallu pour repérer la boucle infinie dans le code ci-dessus?

NE PAS renvoyer les codes d'erreur.

... est un bon conseil lorsque vous vous trouvez dans une base de code qui n'utilise généralement pas de codes d'erreur. Pourquoi? Parce que personne ne se souviendra d’enregistrer la valeur de retour et de vérifier vos codes d’erreur. C'est toujours une bonne convention quand on est en C.

OneOf

Vous utilisez encore une autre convention. C'est bien tant que vous établissez la convention et que vous ne vous contentez pas d'en combattre une autre. Il est déroutant d'avoir deux conventions d'erreur dans la même base de code. Si d'une manière ou d'une autre vous vous êtes débarrassé de tout le code qui utilise l'autre convention, continuez.

J'aime la convention moi-même. L’une des meilleures explications que j’ai trouvées est ici :

enter image description here

But much as I like it I'm still not going to mix it with the other conventions. Pick one and stick with it.1

1: Je veux dire par là ne me faites pas penser à plus d'une convention à la fois.


Subsequent thoughts:

From this discussion what I'm taking away currently is as follows:

  1. If you expect the immediate caller to catch and handle the exception most of the time and continue its work, perhaps through another path, it probably should be part of the return type. Optional or OneOf can be useful here.
  2. If you expect the immediate caller to not catch the exception most of the time, throw an exception, to save the silliness of manually passing it up the stack.
  3. If you're not sure what the immediate caller is going to do, maybe provide both, like Parse and TryParse.

Ce n'est vraiment pas si simple. Une des choses fondamentales que vous devez comprendre est ce qu'est un zéro.

Combien de jours reste-t-il en mai? 0 (parce que ce n'est pas mai, c'est déjà juin).

Les exceptions sont un moyen de rejeter une hypothèse, mais ce n'est pas le seul moyen. Si vous utilisez des exceptions pour rejeter l'hypothèse, vous laissez le chemin heureux. Mais si vous choisissez des valeurs pour indiquer le chemin heureux qui indique que les choses ne sont pas aussi simples qu'on le supposait, vous pouvez rester sur ce chemin tant qu'il peut gérer ces valeurs. Parfois, 0 est déjà utilisé pour signifier quelque chose, vous devez donc trouver une autre valeur pour mapper votre hypothèse de rejet de l'idée sur. Vous pouvez reconnaître cette idée de son utilisation dans le bon vieux algèbre . Les monades peuvent aider, mais il ne faut pas toujours que ce soit une monade.

Par exemple, 2 :

IList ParseAllTheInts(String s) { ... }

Pouvez-vous penser à une bonne raison pour que cela soit conçu de manière à ce qu’il jette délibérément quoi que ce soit? Devinez ce que vous obtenez quand aucun int ne peut être analysé? Je n'ai même pas besoin de te dire.

C'est un signe d'un bon nom. Désolé, mais TryParse n'est pas mon idée d'un réputation.

Nous évitons souvent de faire une exception pour ne rien obtenir alors que la réponse pourrait être plus d'une chose à la fois, mais pour une raison quelconque, si la réponse est une chose ou rien, nous sommes obsédés d'insister pour qu'elle nous donne une chose ou un jet:

IList Intersection(Line a, Line b) { ... }

Les lignes parallèles doivent-elles vraiment causer une exception ici? Est-ce vraiment si grave que cette liste ne contienne jamais plus d'un point?

Peut-être que vous ne pouvez pas prendre cela d'un point de vue sémantique. Si c'est le cas, c'est dommage. Mais peut-être que Monads, qui n'a pas une taille arbitraire comme le fait Liste , vous aidera à vous sentir mieux.

Maybe Intersection(Line a, Line b) { ... }

Les Monads sont de petites collections à usage spécial destinées à être utilisées de manière spécifique, évitant ainsi de les tester. Nous sommes censés trouver des moyens de les gérer, peu importe ce qu’ils contiennent. De cette façon, le chemin heureux reste simple. Si vous ouvrez et testez chaque Monad que vous touchez, vous l'utiliserez mal.

Je sais, c'est bizarre. Mais c'est un nouvel outil (enfin, pour nous). Alors donnez-lui du temps. Les marteaux ont plus de sens lorsque vous cessez de les utiliser avec des vis.


Si vous me le permettez, j'aimerais répondre à ce commentaire:

Comment se fait-il qu'aucune des réponses ne précise que l'une des deux monades n'est pas un code d'erreur, et OneOf non plus? Elles sont fondamentalement différentes et la question semble par conséquent reposer sur un malentendu. (Bien que sous une forme modifiée, la question reste valide.) - Konrad Rudolph 4 juin 18 à 14:08

C'est absolument vrai. Les monades sont beaucoup plus proches des collections que les exceptions, les drapeaux ou les codes d'erreur. Ils fabriquent de beaux contenants pour de telles choses quand ils sont utilisés judicieusement.

127
ajouté
Il n’ya rien de mal avec les conventions de mélange si vous pouvez établir des limites, c’est en quoi consiste l’encapsulation.
ajouté l'auteur sgwill, source
La programmation ferroviaire semble utiliser des exceptions vérifiées avec une syntaxe différente (et parfois une différence significative entre ne pas supporter/payer les informations de trace de pile).
ajouté l'auteur Tomas Petricek, source
Dans la plupart des cas, il serait nécessaire de subdiviser un travail en plusieurs parties qui pourraient se bloquer sans perturber le processus dans son ensemble. Si un trou dans la carte perforée pour un enregistrement de facturation a causé à un programme un numéro négatif qu’il ne pouvait pas gérer, il peut être approprié d’abandonner le traitement du compte de ce client, mais si 20 000 comptes devaient être traités et si une défaillance survient à mi-parcours, il devrait être possible de traiter les 10 000 points restants tout en diagnostiquant le problème, sans avoir à retraiter les 10 000 premiers.
ajouté l'auteur supercat, source
La première partie de cette réponse se lit comme si vous aviez arrêté de lire la question après la phrase citée. La question reconnaît qu'il est préférable de planter le programme que de passer à un comportement indéfini: ils opposent des collisions d'exécution non pas avec une exécution non définie, mais avec des erreurs de compilation qui vous empêchent même de construire le programme sans prendre en compte l'erreur potentielle. et le traiter (ce qui peut devenir un crash s'il n'y a rien d'autre à faire, mais ce sera un crash parce que vous le souhaitez, pas parce que vous l'avez oublié).
ajouté l'auteur LessIsMore, source
Ne serait-il pas plus approprié que la piste rouge soit en-dessous des cases, et donc qu’elle ne les traverse pas?
ajouté l'auteur Flater, source
Logiquement, vous avez raison. Cependant, chaque zone de ce diagramme représente une opération de liaison monadique. bind inclut (peut-être crée!) la piste rouge. Je recommande de regarder la présentation complète. C'est intéressant et Scott est un excellent orateur.
ajouté l'auteur Gusdor, source
Je pense aux exceptions plutôt à une instruction COMEFROM plutôt qu'à une instruction GOTO, car jeter ne sait pas réellement/dit où nous allons sauter;)
ajouté l'auteur Warbo, source
"nous n'abusons pas souvent des exceptions pour créer du code spaghetti comme nous le faisions avec goto" - lit-on en Python récemment? ;)
ajouté l'auteur OrangeDog, source
Je suppose qu’il est intéressant d’ajouter dans la réponse que le renvoi de codes d’erreur omettra la trace de la pile. Le code de OP peut ne pas être le problème; mais le code provenant d'autres sources utilisera toujours Exception et OP retournera un code d'erreur dans lequel les données de trace de pile seront perdues. Le débogage sera très difficile dans ce cas. Grande réponse au fait; +1
ajouté l'auteur Bharat Khatri, source

C # n'est pas Haskell et vous devriez suivre le consensus des experts de la communauté C #. Si vous essayez plutôt de suivre les pratiques Haskell dans un projet C#, vous allez aliéner tous les autres membres de votre équipe et, éventuellement, vous découvrirez probablement les raisons pour lesquelles la communauté C# fait les choses différemment. Une des principales raisons est que C# ne soutient pas convenablement les syndicats discriminés.

J'ai entendu dire que les exceptions en tant que flux de contrôle sont considérées comme un anti-modèle sérieux,

Ce n'est pas une vérité universellement acceptée. Le choix dans chaque langue prenant en charge les exceptions est soit de lever une exception (que l'appelant est libre de ne pas gérer), soit de retourner une valeur composée (que l'appelant DOIT gérer).

La propagation des conditions d'erreur dans la pile d'appels requiert une condition à chaque niveau, ce qui double la complexité cyclomatique de ces méthodes et par conséquent le nombre de tests unitaires. Dans les applications métier classiques, de nombreuses exceptions vont au-delà de la récupération et peuvent être laissées au niveau le plus élevé (par exemple, le point d'entrée de service d'une application Web).

35
ajouté
@Basilevs Il nécessite un support pour propager des monades tout en maintenant le code lisible, ce que C# n'a pas. Vous obtenez soit des instructions répétées si-retour ou des chaînes de lambdas.
ajouté l'auteur Allen Bargi, source
@Basilevs D'un point de vue syntaxique, oui, mais du point de vue des performances, je suppose qu'ils pourraient être moins attrayants.
ajouté l'auteur Pharap, source
La propagation des monades ne nécessite rien.
ajouté l'auteur Basilevs, source
@SebastianRedl lambdas semblent OK en C #.
ajouté l'auteur Basilevs, source
En C# moderne, vous pouvez probablement utiliser partiellement les fonctions locales pour récupérer un peu de vitesse. Dans tous les cas, la plupart des logiciels de gestion sont considérablement ralentis par IO.
ajouté l'auteur savvy2, source

Vous êtes arrivé à C# à un moment intéressant. Jusqu'à récemment, le langage était fermement ancré dans l'espace de programmation impératif. L'utilisation d'exceptions pour communiquer des erreurs était définitivement la norme. L'utilisation de codes de retour souffrait d'un manque de prise en charge linguistique (par exemple autour des unions discriminées et des correspondances de modèle). Assez raisonnablement, les directives officielles de Microsoft étaient d'éviter les codes de retour et d'utiliser des exceptions.

There have always been exceptions to this though, in the form of TryXXX methods, which would return a boolean success result and supply a second value result via an out parameter. These are very similar to the "try" patterns in functional programming, save that the result comes via an out parameter, rather than through a Maybe return value.

But things are changing. Functional programming is becoming even more popular and languages like C# are responding. C# 7.0 introduced some basic pattern matching features to the language; C# 8 will introduce much more, including a switch expression, recursive patterns etc. Along with this, there has been a growth in "functional libraries" for C#, such as my own Succinc library, which provides support for discriminated unions too.

Ces deux éléments combinés signifient que la popularité du code comme celui-ci augmente lentement.

static Maybe TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

C'est encore assez une niche pour le moment, même si au cours des deux dernières années, j'ai constaté un net changement de l'hostilité générale des développeurs C# vers ce type de code et un intérêt croissant pour l'utilisation de telles techniques.

Nous ne sommes pas encore là pour dire " NE PAS renvoyer les codes d'erreur". est désuet et doit prendre sa retraite. Mais la marche vers la réalisation de cet objectif est bien avancée. Alors, faites votre choix: restez fidèle aux anciennes méthodes de rejet des exceptions avec abandon gay si vous aimez faire les choses comme vous le souhaitez; ou commencez à explorer un nouveau monde où les conventions des langages fonctionnels sont de plus en plus populaires en C #.

26
ajouté
@Agent_L: Vous connaissez le java.util.Stream ajouté par Java et un lambdas à présent, n'est-ce pas? :)
ajouté l'auteur Arcturus, source
@Aleksandr Quelqu'un n'a pas fait attention . Ce n'est pas que la communauté Java ne veuille pas ajouter de nouvelles fonctionnalités, il y a beaucoup de projets à différentes étapes. L'une des principales raisons de la lenteur de l'amélioration de Java est le fait qu'Oracle affame le projet de ressources (il ne faut pas gagner beaucoup d'argent pour développer un langage, quelle surprise), de sorte qu'il est très long d'ajouter de nouvelles choses, même triviales.
ajouté l'auteur Kyle Hodgson, source
@AleksandrDubinsky ne vous détestez pas quand ils ajoutent maintenant des fonctionnalités des années 1960.
ajouté l'auteur Vanja, source
@Voo: D'après ce que j'ai compris, s'il y a bien eu des problèmes entre 1,6 et 1,7, les choses se passent mieux maintenant.
ajouté l'auteur Aaron, source
@AleksandrDubinsky: NLog vous offre la possibilité de vous connecter à un fichier texte, à une base de données ou aux deux. Cela ne vous dit pas comment vous devriez vous connecter, cela vous permet de mettre votre journal où vous voulez. Il en va de même pour C #: ils ne vous disent pas d'adopter une approche particulière, mais vous permettent plutôt d'utiliser votre approche préférée. Cela crée un avantage commercial énorme: la compatibilité en amont. Plutôt que de créer un langage/un cadre distinct pour chaque variété d’approche existante, leur unification en une seule langue signifie que les entreprises à rotation lente peuvent adhérer à de nouvelles approches de manière sélective.
ajouté l'auteur Flater, source
@AleksandrDubinsky Je programme en C# depuis les toutes premières versions du langage, et je dois dire que c'est un beau voyage. Avoir une nouvelle fonctionnalité de temps en temps, une nouvelle technique, être capable d’apprendre de nouvelles choses, être capable de faire de nouvelles choses avec le même langage que je suis déjà habitué a fait grandir tellement! Il est passé de quelque chose de légèrement meilleur que C à quelque chose qui m'a permis d’écrire énormément de fonctionnalités en très peu de temps. Le fait que la langue croisse constamment, du moins pour moi, est l’une de ses plus grandes forces.
ajouté l'auteur T. Sar, source
@ AleksandrDubinsky, vous avez trouvé un problème fondamental avec les langages de programmation en général, pas seulement en C #. De nouvelles langues sont créées, minces, fraîches et pleines d’idées modernes. Et très peu de gens les utilisent. Au fil du temps, ils obtiennent des utilisateurs et de nouvelles fonctionnalités, qui s'ajoutent aux anciennes fonctionnalités qui ne peuvent pas être supprimées car cela endommagerait le code existant. Le ballot grossit. Ainsi, les gens créent de nouvelles langues maigres, fraîches et pleines d’idées modernes ...
ajouté l'auteur David Arno, source
Cet article est également utile et pertinent enterprisecraftsmanship.com/2015/03/20/& hellip;
ajouté l'auteur user9993, source
Droite. Mais je veux juste dire que c’est la raison pour laquelle j’aime Java sur C #. Désolé pour la discussion hors sujet. Aucune raison de continuer.
ajouté l'auteur Aleksandr Dubinsky, source
C'est pourquoi je déteste le C# :) Ils continuent d'ajouter de nouvelles méthodes pour écorcher un chat et, bien sûr, les anciennes méthodes ne disparaissent jamais. En fin de compte, il n'y a pas de convention pour rien, un programmeur perfectionniste peut rester assis pendant des heures à décider quelle méthode est la plus «agréable» et un nouveau programmeur doit tout apprendre avant de pouvoir lire le code des autres.
ajouté l'auteur Aleksandr Dubinsky, source
@ AleksandrDubinsky Ouais. Parce que Java a essayé d’ajouter une chose une fois (génériques), ils ont échoué. Nous devons maintenant vivre avec le désordre horrible qui a résulté. Ils ont donc appris la leçon et ont cessé d’ajouter quoi que ce soit:)
ajouté l'auteur Agent_L, source
@AleksandrDubinsky, il existe quelques comparaisons utiles ici: Java est souvent mal qualifié pour être un dinosaure, C ++ est souvent mal conçu pour avoir des avertissements: récursions infinies pour habiller un chat. Trouver un équilibre est difficile . Et ne me lancez pas sur JavaScript.
ajouté l'auteur Sujan, source

Je pense qu'il est parfaitement raisonnable d'utiliser un DU pour renvoyer des erreurs, mais je dirais alors que j'ai écrit OneOf :) Mais je dirais quand même, car c'est une pratique courante en F #, etc. (par exemple, le type de résultat, mentionné par d'autres ).

Je ne pense pas que les erreurs que je retourne généralement comme des situations exceptionnelles, mais plutôt comme des situations normales, mais espérons que rarement rencontrées des situations qui peuvent, ou ne doivent pas nécessairement être traitées, mais qui doivent être modélisées.

Voici l'exemple de la page du projet.

public OneOf CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

Lancer des exceptions pour ces erreurs semble exagéré - elles ont une surcharge de performances, outre les inconvénients de ne pas être explicite dans la signature de la méthode, ou de fournir une correspondance exhaustive.

Dans quelle mesure OneOf est idiomatique en C#, c'est une autre question. La syntaxe est valide et raisonnablement intuitive. Les DU et la programmation orientée rail sont des concepts bien connus.

Je voudrais sauver des exceptions pour des choses qui indiquent que quelque chose est cassé lorsque cela est possible.

5
ajouté
Oui - et fournit un moyen facile de faire une correspondance exhaustive dans l'appelant, ce qui n'est pas possible avec une hiérarchie des résultats basée sur l'héritage.
ajouté l'auteur TravisO, source
Ce que vous dites, c’est que OneOf vise à rendre la valeur de retour plus riche, comme le retour d’un Nullable. Il remplace les exceptions pour les situations qui ne sont pas exceptionnelles pour commencer.
ajouté l'auteur Aleksandr Dubinsky, source

OneOf is about equivalent to checked exceptions in Java. There are some differences:

  • OneOf ne fonctionne pas avec les méthodes produisant des méthodes appelées pour leurs effets secondaires (car elles peuvent être appelées de manière et le résultat simplement ignoré). Évidemment, nous devrions tous essayer d’utiliser des fonctions pures, mais ce n’est pas toujours possible.

  • OneOf ne fournit aucune information sur l'emplacement du problème. C’est bien, car cela permet d’économiser les performances (les exceptions levées en Java sont coûteuses en raison du remplissage de la trace de pile et en C#, ce sera la même chose) et vous oblige à fournir suffisamment d’informations dans le membre d’erreur de OneOf lui-même. C’est également mauvais, car ces informations risquent d’être insuffisantes et vous pourriez avoir du mal à trouver l’origine du problème.

OneOf and checked exception have one important "feature" in common:

  • ils vous obligent tous les deux à les gérer partout dans la pile d'appels

Cela empêche votre peur

En C#, ignorer les exceptions ne génère pas d'erreur de compilation et, si elles ne sont pas interceptées, elles ne font que bouillonner et faire planter votre programme.

mais comme déjà dit, cette peur est irrationnelle. En gros, il y a généralement une seule place dans votre programme, où vous devez tout attraper (il est probable que vous utilisiez déjà un framework pour le faire). Comme vous et votre équipe passez généralement des semaines ou des années à travailler sur le programme, vous ne l'oublierez pas.

Le gros avantage des exceptions ignorable est qu’elles peuvent être gérées où vous le souhaitez, plutôt que n’importe où dans la trace de la pile. Cela élimine des tonnes de passe-partout (il suffit de regarder du code Java déclarant des "lancers ...." ou des exceptions d'encapsulation) et rend également votre code moins bogué: Comme toute méthode peut lancer, vous devez en être conscient. Heureusement, la bonne chose à faire est généralement de ne rien faire , c’est-à-dire de la laisser bouillonner jusqu’à un endroit où elle peut être gérée de manière raisonnable.

4
ajouté
Vous pouvez renvoyer des informations d'erreur avec OneOf en faisant en sorte que l'objet d'erreur renvoyé contienne des informations utiles, par exemple. OneOfSomeErrorInfo fournit des détails sur l'erreur.
ajouté l'auteur TravisO, source
@dcorking Edité. Bien sûr, si vous avez ignoré la valeur de retour, vous ne savez rien du problème. Si vous faites cela avec une fonction pure, c'est bon (vous venez de perdre du temps).
ajouté l'auteur David Nehme, source
@ mcintyre321 Bien sûr, vous pouvez ajouter des informations, mais vous devez le faire manuellement et c'est sujet à la paresse et à l'oubli. Je suppose que, dans les cas plus complexes, une trace de pile est plus utile.
ajouté l'auteur David Nehme, source
+1 mais pourquoi OneOf ne fonctionne-t-il pas avec des effets secondaires? (J'imagine que c'est parce que vous passerez un chèque si vous n'attribuez pas la valeur de retour, mais comme je ne connais pas OneOf, ni beaucoup de C#, je ne suis pas sûr - une clarification améliorerait votre réponse.)
ajouté l'auteur jroith, source

utilise OneOf au lieu d'exceptions pour le traitement "ordinaire"   exceptions (comme Fichier non trouvé, etc.) une approche raisonnable ou est-ce   terrible pratique?

     

Il est à noter que j'ai entendu dire que les exceptions en tant que flux de contrôle sont   considéré comme un antipattern sérieux, donc si "l'exception" est quelque chose   vous devriez normalement gérer sans mettre fin au programme, n'est-ce pas   "flux de contrôle" en quelque sorte?

Les erreurs ont un certain nombre de dimensions. J'identifie trois dimensions: peuvent-elles être évitées, à quelle fréquence, et si elles peuvent être récupérées. Pendant ce temps, la signalisation d'erreur a principalement une dimension: décider dans quelle mesure battre l'appelant au-dessus de la tête pour le forcer à gérer l'erreur ou laisser l'erreur "discrètement" s'afficher comme une exception.

Les erreurs non évitables, fréquentes et récupérables sont celles qui doivent vraiment être traitées sur le site de l'appel. Ils justifient le mieux de forcer l'appelant à les confronter. En Java, je leur fais des exceptions vérifiées, et un effet similaire est obtenu en leur faisant des méthodes Try * ou en renvoyant une union discriminée. Les unions discriminées sont particulièrement utiles lorsqu'une fonction a une valeur de retour. Ils constituent une version beaucoup plus raffinée du renvoi de null . La syntaxe des blocs try/catch n'est pas très bonne (trop de crochets), ce qui rend les alternatives plus belles. En outre, les exceptions sont légèrement lentes car elles enregistrent des traces de pile.

Les erreurs évitables (qui n'ont pas été évitées en raison d'une erreur ou d'une négligence du programmeur) et les erreurs non récupérables fonctionnent vraiment bien comme exceptions ordinaires. Les erreurs peu fréquentes fonctionnent également mieux comme exceptions ordinaires, puisqu'un programmeur peut souvent penser que cela ne vaut pas la peine d'anticiper (cela dépend de l'objectif du programme).

Un point important est que c’est souvent le site d’utilisation qui détermine la manière dont les modes d’erreur de la méthode s’adaptent à ces dimensions, ce qui doit être pris en compte lors de la prise en compte des éléments suivants.

D'après mon expérience, obliger l'appelant à faire face aux conditions d'erreur est acceptable lorsque les erreurs sont évitables, fréquentes et récupérables, mais cela devient très gênant lorsque l'appelant est obligé de sauter à travers des cerceaux lorsqu'il n'en a pas besoin ou ne le souhaite pas. à . Cela peut être dû au fait que l'appelant sait que l'erreur ne se produira pas ou parce qu'il ne veut pas (encore) rendre le code résistant. En Java, cela explique l’angoisse suscitée par le recours fréquent aux exceptions vérifiées et au renvoi de Optional (similaire à Maybe et qui a été récemment introduit au milieu de nombreuses controverses). En cas de doute, lancez des exceptions et laissez l'appelant décider de la façon dont il souhaite les gérer.

Enfin, gardez à l'esprit que la chose la plus importante concernant les conditions d'erreur, bien au-delà de toute autre considération telle que la manière dont elles sont signalées, est de les documenter de manière approfondie .

4
ajouté

Les autres réponses ont traité des exceptions par opposition aux codes d'erreur de manière suffisamment détaillée. J'aimerais donc ajouter une autre perspective spécifique à la question:

OneOf n'est pas un code d'erreur, c'est plutôt une monade

The way it is used, OneOf it is a container that either represents the actual result or some error state. It's like an Optional, except that when no value is present, it can provide more details why that is.

Le principal problème avec les codes d'erreur est que vous oubliez de les vérifier. Mais ici, vous ne pouvez littéralement pas accéder au résultat sans vérifier les erreurs.

Le seul problème est que vous ne vous souciez pas du résultat. L'exemple de la documentation de OneOf donne:

public OneOf CreateUser(string username) { ... }

... puis ils créent des réponses HTTP différentes pour chaque résultat, ce qui est bien mieux que d'utiliser des exceptions. Mais si vous appelez la méthode comme ça.

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

alors vous avez un problème et des exceptions constitueraient le meilleur choix. Comparez la enregistrer de Rail à save! .

2
ajouté

C'est une "idée terrible".

C'est terrible parce que, du moins dans .net, vous ne disposez pas d'une liste exhaustive des exceptions possibles. N'importe quel code peut éventuellement renvoyer une exception. Surtout si vous exécutez la POO et que vous pouvez appeler des méthodes substituées sur un sous-type plutôt que sur le type déclaré

So you would have to change all methods and functions to OneOf, then if you wanted to handle different exception types you would have to examine the type If(exception is FileNotFoundException) etc

try catch is the equivalent of OneOf

Modifier ----

I am aware that you propose returning OneOf but I feel this is somewhat a distinction without a difference.

Vous combinez les codes de retour, les exceptions attendues et la création de branches par exception. Mais l'effet global est le même.

2
ajouté
Je ne propose pas de renvoyer des exceptions .
ajouté l'auteur mukesh ojha, source
proposez-vous que l'alternative à l'un des est le flux de contrôle via des exceptions
ajouté l'auteur Ewan, source
Les exceptions «normales» sont également une idée terrible. l'indice est dans le nom
ajouté l'auteur Ewan, source

Une chose à considérer est que le code en C# n'est généralement pas pur. Dans une base de code pleine d’effets secondaires et avec un compilateur qui ne se soucie pas de ce que vous faites avec la valeur de retour d’une fonction, votre approche peut vous causer un chagrin considérable. Par exemple, si vous avez une méthode qui supprime un fichier, vous voulez vous assurer que l'application avertit quand la méthode a échoué, même s'il n'y a aucune raison de vérifier le code d'erreur "sur le chemin heureux". C'est une différence considérable par rapport aux langages fonctionnels qui vous obligent à ignorer explicitement les valeurs de retour que vous n'utilisez pas. En C#, les valeurs de retour sont toujours implicitement ignorées (la seule exception étant les getters de propriétés IIRC).

La raison pour laquelle MSDN vous a dit de "ne pas renvoyer de codes d'erreur" est exactement la même: personne ne vous oblige à même lire le code d'erreur. Le compilateur ne vous aide pas du tout. Cependant, si votre fonction est libre d'effets secondaires, vous pouvez utiliser en toute sécurité quelque chose comme Soit - le fait est que même si vous ignorez l'erreur résultat (le cas échéant), vous ne pouvez le faire que si vous n'utilisez pas non plus le "résultat correct". Vous pouvez y parvenir de différentes manières. Par exemple, vous pouvez uniquement autoriser la "lecture" de la valeur en transmettant un délégué pour le traitement des cas d'erreur et , et vous pouvez facilement combiner cela avec des approches. comme Programmation axée sur les chemins de fer (cela fonctionne très bien en C #).

int.TryParse is somewhere in the middle. It's pure. It defines what the values of the two results are at all times - so you know that if the return value is false, the output parameter will be set to 0. It still doesn't prevent you from using the output parameter without checking the return value, but at least you're guaranteed what the result is even if the function fails.

Mais une chose absolument cruciale ici est la cohérence . Le compilateur ne va pas vous sauver si quelqu'un change cette fonction pour avoir des effets secondaires. Ou jeter des exceptions. Ainsi, bien que cette approche fonctionne parfaitement en C# (et que je l’utilise bien), vous devez vous assurer que tous les membres de votre équipe le comprennent et l’utilisent . La plupart des invariants nécessaires au bon fonctionnement de la programmation fonctionnelle ne sont pas imposés par le compilateur C# et vous devez vous assurer qu'ils sont suivis par vous-même. Si vous ne suivez pas les règles que Haskell applique par défaut, vous devez vous tenir au courant de ce qui ne fonctionne pas. Vous devez en tenir compte pour tous les paradigmes fonctionnels que vous introduisez dans votre code.

1
ajouté

The correct statement would be Don't use error codes for exceptions! And don't use exceptions for non-exceptions.

Si quelque chose arrive que votre code ne peut pas récupérer (c’est-à-dire le faire fonctionner), c’est une exception. Votre code immédiat ne ferait rien avec un code d'erreur, si ce n'est de le remonter pour le connecter ou éventuellement de fournir le code à l'utilisateur d'une manière ou d'une autre. Cependant, c’est précisément à cela que servent les exceptions - elles traitent de toutes les tâches et permettent d’analyser ce qui s’est réellement passé.

Cependant, si quelque chose se produit, telle qu'une entrée invalide que vous pouvez gérer, soit en la reformatant automatiquement, soit en demandant à l'utilisateur de corriger les données (et vous y avez pensé), ce n'est pas vraiment une exception, car attendez-le. Vous pouvez vous sentir libre de gérer ces cas avec des codes d'erreur ou toute autre architecture. Juste pas des exceptions.

(Notez que je ne suis pas très expérimenté en C#, mais ma réponse devrait être valable dans le cas général en fonction du niveau conceptuel des exceptions.)

0
ajouté
La question de savoir s'il est préférable qu'une fonction lève une exception lorsqu'une condition survient dépend de la capacité de l'appelant immédiat de la fonction à gérer efficacement cette condition. Dans les cas où cela est possible, le lancement d'une exception que l'appelant immédiat doit intercepter représente un travail supplémentaire pour l'auteur du code appelant et un travail supplémentaire pour la machine qui le traite. Si un appelant n'est pas prêt à gérer une condition, toutefois, des exceptions évitent à l'appelant de le coder explicitement.
ajouté l'auteur supercat, source
Je suis fondamentalement en désaccord avec la prémisse de cette réponse. Si les concepteurs de langage ne souhaitaient pas que des exceptions soient utilisées pour les erreurs récupérables, le mot clé catch n'existerait pas. Et comme nous parlons dans un contexte C#, il convient de noter que la philosophie adoptée ici n’est pas suivie par le framework .NET; L’exemple le plus simple imaginable de "saisie invalide que vous puissiez gérer" est l’utilisateur qui saisit un non-nombre dans un champ où un nombre est attendu ... et int.Parse lève une exception.
ajouté l'auteur Mark Amery, source
@MarkAmery Pour int.Parse, rien ne peut être récupéré, ni rien d’attendu. La clause catch est généralement utilisée pour éviter l'échec complet d'une vue extérieure, elle ne demandera pas à l'utilisateur de nouvelles informations d'identification de base de données ou autres, cela évitera le blocage du programme. C'est une défaillance technique et non un flux de contrôle d'application.
ajouté l'auteur redfox26, source
@Darkwing "Vous pouvez vous sentir libre de gérer ces cas avec des codes d'erreur ou toute autre architecture". Oui, vous êtes libre de le faire. Cependant, vous ne recevez aucune aide de .NET pour ce faire, puisque le CL .NET lui-même utilise des exceptions au lieu de codes d'erreur. Ainsi, dans de nombreux cas, vous êtes obligé de capturer les exceptions .NET et de renvoyer le code d'erreur correspondant au lieu de laisser les exceptions bouillonner, mais vous forcez également les membres de votre équipe à utiliser un autre concept de gestion des erreurs qu'ils doivent utiliser parallèlement aux exceptions, le concept de traitement des erreurs standard.
ajouté l'auteur Ikaros, source