Ostrzeżenie! Przybywa tutaj programista C ++ z prawdopodobnie różnymi pomysłami na to, jak należy obsługiwać wyjątki, próbując odpowiedzieć na pytanie, które z pewnością dotyczy innego języka!
Biorąc pod uwagę ten pomysł:
Na przykład wyobraźmy sobie, że mamy zasób pobierania, który wykonuje żądanie HTTP i zwraca pobrane dane. I zamiast błędów takich jak ServiceTemporaryUnavailable lub RateLimitExceeded po prostu podnosimy błąd RetryableError sugerujący konsumentowi, że powinien po prostu ponowić żądanie i nie przejmować się konkretną awarią.
... jedną rzeczą, którą sugeruję, jest mieszanie obaw związanych z raportowaniem błędu z działaniami, aby na nie zareagować w sposób, który może obniżyć ogólność kodu lub wymagać wielu „punktów tłumaczenia” z powodu wyjątków .
Na przykład, jeśli modeluję transakcję obejmującą ładowanie pliku, może się to nie powieść z wielu powodów. Być może ładowanie pliku obejmuje ładowanie wtyczki, która nie istnieje na komputerze użytkownika. Być może plik jest po prostu uszkodzony i napotkaliśmy błąd podczas jego analizowania.
Bez względu na to, co się stanie, powiedzmy, że sposób działania polega na zgłoszeniu użytkownikowi tego, co się stało, i zapytaniu go, co chce z tym zrobić („spróbuj ponownie, załaduj inny plik, anuluj”).
Thrower vs. Catcher
Ten sposób postępowania obowiązuje niezależnie od rodzaju błędu, jaki napotkaliśmy w tym przypadku. Nie jest osadzony w ogólnej idei błędu parsowania, nie jest osadzony w ogólnej idei niepowodzenia ładowania wtyczki. Jest to osadzone w idei napotykania takich błędów podczas dokładnego kontekstu ładowania pliku (połączenie ładowania pliku i niepowodzenia). Tak więc zazwyczaj postrzegam to, mówiąc brutalnie, jako catcher's
odpowiedzialność za określenie kierunku działania w odpowiedzi na zgłoszony wyjątek (np. Podpowiadanie użytkownikowi opcji), a nie thrower's
.
Innymi słowy, witryny, w których throw
wyjątki zazwyczaj nie mają tego rodzaju informacji kontekstowych, szczególnie jeśli generowane funkcje mają ogólne zastosowanie. Nawet w całkowicie zdegenerowanym kontekście, gdy mają one te informacje, ostatecznie zagłębiasz się w kwestii odzyskiwania po osadzeniu ich na throw
stronie. Witryny catch
, które generalnie mają najwięcej dostępnych informacji, aby określić kierunek działania, i dają jedno centralne miejsce do modyfikacji, jeśli ten kierunek działania powinien się kiedykolwiek zmienić dla danej transakcji.
Gdy zaczniesz próbować zgłaszać wyjątki, nie będziesz już raportować, co jest nie tak, ale spróbujesz ustalić, co robić, co może pogorszyć ogólność i elastyczność twojego kodu. Błąd parsowania nie zawsze powinien prowadzić do tego rodzaju monitu, różni się w zależności od kontekstu, w którym generowany jest taki wyjątek (transakcja, w której został zgłoszony).
Ślepy Miotacz
Ogólnie rzecz biorąc, wiele rozwiązań dotyczących obsługi wyjątków często opiera się na koncepcji ślepego rzucającego. Nie wie, w jaki sposób zostanie przechwycony wyjątek ani gdzie. To samo dotyczy nawet starszych form odzyskiwania błędów przy użyciu ręcznej propagacji błędów. Witryny, w których występują błędy, nie uwzględniają sposobu działania użytkownika, umieszczają jedynie minimalne informacje, aby zgłosić rodzaj napotkanego błędu.
Odwrócone obowiązki i generalizowanie łapacza
Zastanawiając się nad tym, starałem się wyobrazić sobie rodzaj bazy kodu, w którym może to stać się pokusą. Moją wyobraźnią (być może błędną) jest to, że Twój zespół nadal odgrywa tutaj rolę „konsumenta” i implementuje większość kodu wywołującego. Być może masz wiele różnych transakcji (wiele try
bloków), które mogą napotkać te same zestawy błędów i wszystkie powinny, z perspektywy projektowej, prowadzić do jednolitego przebiegu działań naprawczych.
Biorąc pod uwagę mądrą radę z Lightness Races in Orbit's
dobrej odpowiedzi (która, jak sądzę, naprawdę pochodzi z zaawansowanego sposobu myślenia zorientowanego na bibliotekę), możesz nadal mieć ochotę rzucić wyjątki „co robić”, tylko bliżej strony odzyskiwania transakcji.
Może być możliwe znalezienie pośredniej, wspólnej strony zajmującej się obsługą transakcji tutaj, która faktycznie centralizuje obawy dotyczące „co robić”, ale wciąż w kontekście chwytania.
Miałoby to zastosowanie tylko wtedy, gdy można zaprojektować jakąś funkcję ogólną, z której korzystają wszystkie te zewnętrzne transakcje (np. Funkcja, która wprowadza inną funkcję do wywołania lub abstrakcyjna klasa bazowa transakcji z nadpisywalnym zachowaniem, modelująca tę stronę transakcji pośredniej, która wykonuje wyrafinowane przechwytywanie ).
Jednak ten może być odpowiedzialny za scentralizowanie sposobu działania użytkownika w odpowiedzi na różne możliwe błędy i nadal w kontekście chwytania, a nie rzucania. Prosty przykład (pseudokod Python-ish, a ja nie jestem doświadczonym programistą w Pythonie, więc może istnieć bardziej idiomatyczny sposób rozwiązania tego problemu):
def general_catcher(task):
try:
task()
except SomeError1:
# do some uniformly-designed recovery stuff here
except SomeError2:
# do some other uniformly-designed recovery stuff here
...
[Mam nadzieję, że ma lepszą nazwę niż general_catcher
]. W tym przykładzie możesz przekazać funkcję zawierającą zadanie do wykonania, ale nadal korzystać z uogólnionego / ujednoliconego zachowania dla wszystkich typów wyjątków, które Cię interesują, i kontynuować rozszerzanie lub modyfikowanie części „co robić”. lubisz z tej centralnej lokalizacji i nadal w catch
kontekście, w którym jest to zwykle zalecane. Co najlepsze, możemy powstrzymać strony rzucające przed zajmowaniem się „tym, co robić” (zachowując pojęcie „ślepego rzucającego”).
Jeśli żadna z tych sugestii nie okaże się pomocna, a mimo to istnieje silna pokusa, by rzucić wyjątki „co robić”, przede wszystkim pamiętaj, że jest to przynajmniej bardzo antyidiomatyczne, a także potencjalnie zniechęca do ogólnego myślenia.