Comme vos exemples le montrent déjà, chaque cas de ce type doit être évalué séparément et il existe un large spectre de gris entre les "circonstances exceptionnelles" et le "contrôle de flux", en particulier si votre méthode est censée être réutilisable et peut être utilisée de manières très différentes. qu'il a été conçu à l'origine pour. Ne vous attendez pas à ce que tous ici s'entendent sur ce que signifie "non exceptionnel", surtout si vous discutez immédiatement de la possibilité d'utiliser des "exceptions" pour mettre cela en œuvre.
Nous pourrions également ne pas être d’accord sur la conception qui rend le code le plus facile à lire et à maintenir, mais je suppose que le concepteur de la bibliothèque en a une vision personnelle claire et qu’il ne lui faut que l’équilibrer par rapport aux autres considérations en jeu.
Réponse courte
Suivez vos instincts sauf lorsque vous concevez des méthodes assez rapides et attendez-vous à une possibilité de réutilisation imprévue.
Longue réponse
Chaque futur appelant peut librement faire la traduction entre les codes d'erreur et les exceptions comme il le souhaite dans les deux sens; Cela rend les deux approches de conception presque équivalentes, à l'exception des performances, de la convivialité du débogueur et de certains contextes d'interopérabilité restreinte. Cela se résume généralement à la performance, alors concentrons-nous là-dessus.
En règle générale, attendez-vous à ce qu'une exception soit générée 200 fois plus lentement qu'un retour régulier (en réalité, il y a un écart important).
En règle générale, le lancement d'une exception peut souvent permettre un code beaucoup plus propre comparé à la plus grossière des valeurs magiques, car vous ne comptez pas sur le programmeur qui traduit le code d'erreur en un autre code d'erreur, car il parcourt plusieurs couches de code client vers un point où il y a suffisamment de contexte pour le gérer de manière cohérente et adéquate. (Cas particulier: a null
tendance à être meilleur ici que d’autres valeurs magiques en raison de sa tendance à se traduire automatiquement NullReferenceException
en cas de défauts, mais pas tous; types de défauts; généralement, mais pas toujours, assez proches de la source du défaut. )
Alors, quelle est la leçon?
Pour une fonction appelée quelques fois au cours de la durée de vie d'une application (comme son initialisation), utilisez tout ce qui vous donne un code plus propre et plus facile à comprendre. La performance ne peut pas être une préoccupation.
Pour une fonction jetable, utilisez tout ce qui vous donne un code plus propre. Effectuez ensuite un profilage (si nécessaire) et modifiez les exceptions en codes de retour s’ils font partie des goulets d’étranglement présumés fondés sur des mesures ou la structure globale du programme.
Pour une fonction réutilisable coûteuse, utilisez tout ce qui vous donne un code plus propre. Si, fondamentalement, vous devez toujours subir un aller-retour sur le réseau ou analyser un fichier XML sur disque, le temps système nécessaire au lancement d'une exception est probablement négligeable. Il est plus important de ne pas perdre les détails de toute défaillance, même accidentellement, que de revenir très rapidement d'une "défaillance non exceptionnelle".
Une fonction maigre réutilisable nécessite plus de réflexion. En utilisant des exceptions, vous forcez quelque chose comme un ralentissement de 100 fois sur les appelants qui verront l'exception sur la moitié de leurs (nombreux) appels, si le corps de la fonction s'exécute très rapidement. Les exceptions constituent toujours une option de conception, mais vous devrez fournir une alternative peu onéreuse aux appelants qui ne peuvent se le permettre. Regardons un exemple.
Vous avez énuméré un excellent exemple de Dictionary<,>.Item
ce qui, en gros, est passé du renvoi de null
valeurs au jeté KeyNotFoundException
entre .NET 1.1 et .NET 2.0 (uniquement si vous êtes prêt à considérer Hashtable.Item
son précurseur non générique pratique). La raison de ce "changement" n’est pas sans intérêt ici. L’optimisation des performances des types de valeur (plus de boxe) a fait de la valeur magique initiale ( null
) une non-option; out
les paramètres ne rapportent qu’une petite partie des coûts de performance. Cette dernière considération de performance est totalement négligeable par rapport à la surcharge de lancer un KeyNotFoundException
, mais la conception des exceptions est toujours supérieure ici. Pourquoi?
- Les paramètres ref / out entraînent des coûts chaque fois, pas seulement dans le cas "d'échec"
- Toute personne intéressée peut appeler
Contains
avant tout appel de l'indexeur, et ce modèle se lit de manière totalement naturelle. Si un développeur souhaite appeler mais oublie d'appeler Contains
, aucun problème de performances ne peut s'y glisser; KeyNotFoundException
est assez fort pour être remarqué et corrigé.