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 courriers électroniques menaçants au président. Si vous ne pouvez pas restaurer le système et le remettre dans un état défini, il est responsable de la panne. C'est exactement pourquoi nous construisons des systèmes qui plantent plutôt que de se déchirer tranquillement. 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. Lorsqu'ils ont inventé le système d'exception, ils craignaient de rompre la programmation structurée. La programmation structurée est pourquoi nous avons for
, while
, until
, break
et continue
quand tout ce que nous devons, de faire tout cela, est goto
.
Dijkstra nous a appris que l'utilisation de goto de manière informelle (c'est-à-dire que vous pouvez sauter où bon vous semble) fait de la lecture de code un cauchemar. Quand ils nous ont donné le système d'exception, ils ont eu peur de réinventer la méthode goto. Alors ils nous ont dit de ne pas "l'utiliser pour le contrôle de flux" en espérant que nous comprendrions. Malheureusement, beaucoup d'entre nous n'ont pas.
Bizarrement, 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 la sauvegarde d’un fichier, 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 bêtises comme ceci :
"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 vraiment été 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 de sauvegarder 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. Une des meilleures explications que j'ai trouvées ici * :
Mais même si cela me plaît, je ne vais pas encore le mélanger avec les autres conventions. Choisissez en un et gardez le. 1
1: Je veux dire par là ne me fait pas penser à plus d’une convention à la fois.
Pensées suivantes:
Ce que je retiens actuellement de cette discussion est le suivant:
- Si vous vous attendez à ce que l'appelant immédiat attrape et traite l'exception la plupart du temps et poursuive son travail, peut-être par un autre chemin, elle devrait probablement faire partie du type de retour. Optionnel ou OneOf peut être utile ici.
- Si vous vous attendez à ce que l'appelant immédiat ne détecte pas l'exception la plupart du temps, lancez une exception pour éviter la stupidité de la transmettre manuellement dans la pile.
- Si vous ne savez pas ce que l'appelant immédiat va faire, fournissez peut-être les deux, comme Parse et 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 quittez la bonne voie. Mais si vous choisissez des valeurs pour indiquer le chemin heureux qui signale 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 cartographier votre hypothèse de rejet de l'idée. Vous pouvez reconnaître cette idée de son utilisation dans la bonne vieille algèbre . Les monades peuvent aider, mais il ne faut pas toujours que ce soit une monade.
Par exemple 2 :
IList<int> 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é, TryParse n'est pas mon idée d'un bon nom.
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<Point> 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, sémantiquement, vous ne pouvez pas supporter ça. Si c'est le cas, c'est dommage. Mais peut-être que les monades, qui n'ont pas une taille arbitraire List
, vous feront vous sentir mieux.
Maybe<Point> Intersection(Line a, Line b) { ... }
Les Monads sont de petites collections à usage spécial conçues pour être utilisées de manière spécifique, sans avoir à 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 donc reposer sur un malentendu. (Bien que sous une forme modifiée, cela reste une question valable.) - Konrad Rudolph le 4 juin à 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 récipients pour de telles choses quand ils sont utilisés judicieusement.
Result
;-)