Używamy wyjątków, aby pozwolić konsumentowi kodu na nieoczekiwane zachowanie w użyteczny sposób. Zwykle wyjątki są oparte na scenariuszu „co się stało” - na przykład FileNotFound
(nie mogliśmy znaleźć określonego pliku) lub ZeroDivisionError
(nie byliśmy w stanie wykonać 1/0
operacji).
Co zrobić, jeśli istnieje możliwość określenia oczekiwanego zachowania konsumenta?
Na przykład wyobraźmy sobie, że mamy fetch
zasób, który wykonuje żądanie HTTP i zwraca pobrane dane. I zamiast błędów takich jak ServiceTemporaryUnavailable
lub RateLimitExceeded
po prostu podnieślibyśmy RetryableError
sugestię konsumenta, że powinien po prostu ponowić żądanie i nie przejmować się konkretną awarią. Zasadniczo sugerujemy więc akcję dla osoby dzwoniącej - „co robić”.
Nie robimy tego często, ponieważ nie znamy wszystkich przypadków użycia konsumentów. Ale wyobraź sobie, że to jakiś konkretny element, który znamy najlepszy sposób postępowania dla osoby dzwoniącej - więc czy powinniśmy zatem zastosować podejście „co robić”?
źródło
if( ! function() ) handle_something();
, ale jesteśmy w stanie poradzić sobie z błędem w miejscu, w którym faktycznie znasz kontekst wywoływania - tj. Powiedzieć klientowi, aby zadzwonił do administratora systemu, jeśli serwer się nie powiedzie lub przeładuje się automatycznie, jeśli połączenie zostanie przerwane, ale powiadomi cię przypadek dzwoniącego to kolejna mikrousługa. Niech bloki zaczepowe poradzą sobie z chwytaniem.Odpowiedzi:
To prawie zawsze zawodzi u co najmniej jednego z twoich rozmówców, dla których to zachowanie jest niezwykle irytujące. Nie zakładaj, że wiesz najlepiej. Powiedz użytkownikom, co się dzieje, a nie, co według nich powinni zrobić. W wielu przypadkach jest już jasne, jaki powinien być rozsądny sposób działania (a jeśli nie, to zasugeruj w instrukcji obsługi).
Na przykład, nawet wyjątki podane w twoim pytaniu pokazują twoje złamane założenie: a
ServiceTemporaryUnavailable
oznacza „spróbuj ponownie później”, iRateLimitExceeded
„co tam, wyluzuj, może dostosuj parametry timera i spróbuj ponownie za kilka minut”. Ale użytkownik może równie dobrze chcieć wywołać jakiś alarmServiceTemporaryUnavailable
(który wskazuje na problem z serwerem), a nie dlaRateLimitExceeded
(co nie).Daj im wybór .
źródło
RetryableError
.Biorąc pod uwagę ten pomysł:
... 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 niethrower'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 nathrow
stronie. Witrynycatch
, 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):
[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 wcatch
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.
źródło
Myślę, że przez większość czasu lepiej byłoby przekazywać argumenty funkcji informujące ją, jak poradzić sobie z tymi sytuacjami.
Rozważmy na przykład funkcję:
Mogę przekazać RetryPolicy.noRetries () lub RetryPolicy.retries (3) lub cokolwiek innego. W przypadku niepowodzenia ponownej próby skonsultuje się z polityką, aby zdecydować, czy powinna ponowić próbę.
źródło
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
lub coś takiego, ponieważRateLimitExceeded
może być konieczne potraktowanie go inaczejServiceTemporaryUnavailable
. Po tym, jak to napisałem, pomyślałem: lepiej rzucić wyjątek, ponieważ daje on bardziej elastyczną kontrolę.