Wie Ihre Beispiele bereits zeigen, muss jeder dieser Fälle separat bewertet werden, und zwischen "außergewöhnlichen Umständen" und "Flusskontrolle" besteht ein beträchtliches Spektrum an Graustufen, insbesondere wenn Ihre Methode wiederverwendbar sein soll und möglicherweise in ganz unterschiedlichen Mustern verwendet wird als es ursprünglich vorgesehen war. Erwarten Sie nicht, dass wir uns hier alle darauf einigen, was "nicht außergewöhnlich" bedeutet, insbesondere wenn Sie sofort die Möglichkeit diskutieren, "Ausnahmen" zu verwenden, um dies umzusetzen.
Wir sind uns vielleicht auch nicht einig darüber, durch welches Design der Code am einfachsten zu lesen und zu warten ist, aber ich gehe davon aus, dass der Bibliotheksdesigner eine klare persönliche Vision davon hat und diese nur mit den anderen Überlegungen in Einklang bringen muss.
Kurze Antwort
Folgen Sie Ihrem Bauchgefühl, es sei denn, Sie entwerfen recht schnelle Methoden und erwarten die Möglichkeit einer unvorhergesehenen Wiederverwendung.
Lange Antwort
Jeder zukünftige Anrufer kann frei zwischen Fehlercodes und Ausnahmen in beide Richtungen übersetzen. Dies macht die beiden Entwurfsansätze bis auf Leistung, Debuggerfreundlichkeit und einige eingeschränkte Interoperabilitätskontexte nahezu gleich. Das läuft normalerweise auf Leistung hinaus, also konzentrieren wir uns darauf.
Als Faustregel gilt, dass das Auslösen einer Ausnahme 200x langsamer ist als eine reguläre Rückkehr (in Wirklichkeit gibt es eine signifikante Abweichung davon).
Eine andere Faustregel ist, dass das Auslösen einer Ausnahme im Vergleich zu den gröbsten magischen Werten häufig einen deutlich saubereren Code ermöglicht, da Sie sich nicht darauf verlassen müssen, dass der Programmierer den Fehlercode in einen anderen Fehlercode übersetzt, da er durch mehrere Client-Code-Ebenen in Richtung wandert Ein Punkt, an dem genügend Kontext vorhanden ist, um ihn konsistent und angemessen zu behandeln. (Sonderfall: null
Neigt dazu, hier besser abzuschneiden als bei anderen magischen Werten, da sie dazu neigen, sich NullReferenceException
bei einigen, aber nicht allen Arten von Fehlern automatisch in einen zu übersetzen ; normalerweise, aber nicht immer, recht nahe an der Fehlerquelle. )
Was ist die Lektion?
Verwenden Sie für eine Funktion, die nur einige Male während der Lebensdauer einer Anwendung aufgerufen wird (z. B. die App-Initialisierung), alles, was Sie zu einem saubereren und besser verständlichen Code führt. Leistung kann kein Problem sein.
Verwenden Sie für eine Einwegfunktion alles, was Ihnen einen saubereren Code bietet. Führen Sie dann eine Profilerstellung durch (falls erforderlich) und ändern Sie die Ausnahmen, um Codes zurückzugeben, wenn sie aufgrund von Messungen oder der gesamten Programmstruktur zu den vermuteten oberen Engpässen gehören.
Verwenden Sie für eine teure wiederverwendbare Funktion alles, was Ihnen saubereren Code bietet. Wenn Sie grundsätzlich immer einen Netzwerk-Roundtrip durchführen oder eine XML-Datei auf der Festplatte analysieren müssen, ist der Aufwand für das Auslösen einer Ausnahme wahrscheinlich vernachlässigbar. Es ist wichtiger, nicht einmal versehentlich Details eines Fehlers zu verlieren, als besonders schnell von einem "nicht außergewöhnlichen Fehler" zurückzukehren.
Eine schlanke wiederverwendbare Funktion erfordert mehr Nachdenken. Durch die Verwendung von Ausnahmen erzwingen Sie eine 100-fache Verzögerung für Anrufer, die die Ausnahme bei der Hälfte ihrer (vielen) Aufrufe sehen, wenn der Funktionskörper sehr schnell ausgeführt wird. Ausnahmen sind immer noch eine Gestaltungsoption, aber Sie müssen Anrufern, die sich dies nicht leisten können, eine Alternative mit geringem Overhead anbieten. Schauen wir uns ein Beispiel an.
Sie führen ein großartiges Beispiel dafür an Dictionary<,>.Item
, das sich lose gesagt von der Rückgabe von null
Werten zum Werfen KeyNotFoundException
zwischen .NET 1.1 und .NET 2.0 geändert hat (nur, wenn Sie bereit sind, es als Hashtable.Item
praktischen, nicht generischen Vorläufer zu betrachten). Der Grund dieser "Veränderung" ist hier nicht ohne Interesse. Die Leistungsoptimierung von Werttypen (kein Boxen mehr) machte den ursprünglichen Zauberwert ( null
) zu einer Nichtoption. out
Parameter würden nur einen kleinen Teil der Leistungskosten zurückbringen. Diese letztere Leistungsüberlegung ist im Vergleich zum Aufwand für das Werfen von a völlig vernachlässigbarKeyNotFoundException
, aber das Ausnahmedesign ist hier immer noch überlegen. Warum?
- ref / out-Parameter verursachen jedes Mal Kosten, nicht nur im "Fehler" -Fall
- Jeder, der sich interessiert, kann
Contains
vor jedem Aufruf den Indexer aufrufen, und dieses Muster liest sich ganz natürlich. Wenn ein Entwickler den Aufruf jedoch vergessen möchte Contains
, können sich keine Leistungsprobleme einschleichen. KeyNotFoundException
ist laut genug, um bemerkt und repariert zu werden.