Jak pokazują twoje przykłady, każdy taki przypadek musi zostać oceniony osobno, a pomiędzy „wyjątkowymi okolicznościami” a „kontrolą przepływu” istnieje znaczne spektrum szarości, zwłaszcza jeśli twoja metoda ma być wielokrotnego użytku i może być stosowana w zupełnie innych wzorach niż pierwotnie został zaprojektowany. Nie oczekuj, że wszyscy tutaj uzgodnimy, co oznacza „nietypowy”, zwłaszcza jeśli od razu omówisz możliwość zastosowania „wyjątków” w celu wdrożenia tego.
Możemy również nie zgadzać się co do tego, który projekt sprawia, że kod jest najłatwiejszy do odczytania i utrzymania, ale założę, że projektant biblioteki ma jasną osobistą wizję tego i musi jedynie zrównoważyć go z innymi rozważaniami.
Krótka odpowiedź
Podążaj za swoimi przeczuciami, chyba że projektujesz dość szybkie metody i oczekujesz możliwości nieprzewidzianego ponownego użycia.
Długa odpowiedź
Każdy przyszły dzwoniący może dowolnie tłumaczyć między kodami błędów i wyjątkami, jak chce w obu kierunkach; To sprawia, że te dwa podejścia projektowe są prawie równoważne, z wyjątkiem wydajności, łatwości debugowania i niektórych ograniczonych kontekstów interoperacyjności. Zwykle sprowadza się to do wydajności, więc skupmy się na tym.
Zasadniczo spodziewaj się, że zgłoszenie wyjątku jest 200 razy wolniejsze niż zwykły zwrot (w rzeczywistości istnieje znaczna wariancja).
Jako kolejna reguła, zgłoszenie wyjątku może często pozwolić na znacznie czystszy kod w porównaniu z najokrutniejszymi magicznymi wartościami, ponieważ nie polegasz na tym, że programista tłumaczy kod błędu na inny kod błędu, ponieważ przechodzi on przez wiele warstw kodu klienta w kierunku punkt, w którym jest wystarczający kontekst do obsługi go w spójny i odpowiedni sposób. (Przypadek szczególny: null
ma się tutaj lepiej niż inne wartości magiczne ze względu na jego tendencję do automatycznego przekładania się na NullReferenceException
niektóre, ale nie wszystkie rodzaje wad; zwykle, ale nie zawsze, całkiem blisko źródła defektu. )
Więc jaka jest lekcja?
W przypadku funkcji, która jest wywoływana tylko kilka razy w trakcie życia aplikacji (np. Inicjalizacja aplikacji), użyj czegokolwiek, co zapewnia czystszy, łatwiejszy do zrozumienia kod. Wydajność nie może być problemem.
Aby użyć funkcji „wyrzucania”, użyj czegokolwiek, co zapewni czystszy kod. Następnie wykonaj profilowanie (w razie potrzeby) i zmień wyjątki, aby zwracać kody, jeśli znajdują się wśród podejrzanych górnych wąskich gardeł na podstawie pomiarów lub ogólnej struktury programu.
W przypadku drogiej funkcji wielokrotnego użytku użyj wszystkiego, co zapewni czystszy kod. Jeśli w zasadzie zawsze trzeba przejść przez sieć w obie strony lub przeanalizować plik XML na dysku, narzut związany z rzuceniem wyjątku jest prawdopodobnie nieistotny. Ważniejsze jest, aby nie stracić szczegółów jakiejkolwiek awarii, nawet przypadkowo, niż wyjątkowo szybko powrócić z „awarii wyjątkowej”.
Lean funkcja wielokrotnego użytku wymaga więcej przemyślenia. Stosując wyjątki, wymuszasz coś w rodzaju 100-krotnego spowolnienia wywołujących, którzy zobaczą wyjątek w połowie (wielu) wywołań, jeśli ciało funkcji działa bardzo szybko. Wyjątki są nadal opcją projektową, ale będziesz musiał zapewnić alternatywę o niskim koszcie dla dzwoniących, którzy nie mogą sobie na to pozwolić. Spójrzmy na przykład.
Podajesz świetny przykład Dictionary<,>.Item
, który, mówiąc luźno, zmienił się ze zwracania null
wartości na rzucanie KeyNotFoundException
między .NET 1.1 i .NET 2.0 (tylko jeśli jesteś gotów uważać Hashtable.Item
się za jego praktycznego, nietypowego prekursora). Przyczyna tej „zmiany” nie jest tutaj bez zainteresowania. Optymalizacja wydajności typów wartości (koniec boksu) sprawiła, że oryginalna magiczna wartość ( null
) nie była opcją; out
parametry po prostu przywróciłyby niewielką część kosztów wydajności. Ta ostatnia kwestia wydajności jest całkowicie nieistotna w porównaniu z narzutem związanym z rzucaniem a KeyNotFoundException
, ale projekt wyjątku jest tutaj nadal lepszy. Dlaczego?
- parametry ref / out ponoszą swoje koszty za każdym razem, nie tylko w przypadku „awarii”
- Każdy, kogo to obchodzi, może zadzwonić
Contains
przed każdym połączeniem z indeksem, a ten wzorzec czyta się całkowicie naturalnie. Jeśli programista chce, ale zapomina zadzwonić Contains
, nie mogą wkraść się żadne problemy z wydajnością; KeyNotFoundException
jest wystarczająco głośny, aby zostać zauważonym i naprawiony.