Używanie try-try (bez catch) w porównaniu do sprawdzania stanu wyliczenia

9

Czytałem porady dotyczące tego pytania, w jaki sposób należy traktować wyjątek jak najbliżej miejsca, w którym został zgłoszony.

Moim dylematem dotyczącym najlepszej praktyki jest to, czy należy użyć try / catch / wreszcie, aby zwrócić enum (lub liczbę całkowitą reprezentującą wartość, 0 dla błędu, 1 dla ok, 2 dla ostrzeżenia itp., W zależności od przypadku) , aby odpowiedź jest zawsze w porządku, czy też należy pozwolić, aby wyjątek przeszedł, aby część wywołująca sobie z tym poradziła?

Z tego, co mogę zebrać, może się to różnić w zależności od przypadku, więc oryginalna rada wydaje się dziwna.

Na przykład w usłudze sieciowej zawsze chciałbyś oczywiście przywrócić stan, więc każdy wyjątek musi zostać rozwiązany na miejscu, ale powiedzmy, że wewnątrz funkcji wysyłającej / pobierającej dane przez http, chciałbyś wyjątek (na przykład w przypadku 404), aby przejść tylko do tego, który go zwolnił. Jeśli tego nie zrobisz, musisz stworzyć jakiś sposób na poinformowanie strony wywołującej o jakości wyniku (błąd: 404), a także o samym wyniku.

Chociaż można spróbować złapać wyjątek 404 wewnątrz funkcji pomocnika, która pobiera / wysyła dane, prawda? Czy tylko ja używam smallinta do oznaczania stanów w programie (i oczywiście odpowiednio je dokumentuję), a następnie używam tych informacji do celów sprawdzania poprawności poczytalności (wszystko ok / obsługa błędów) na zewnątrz?

Aktualizacja: spodziewałem się fatalnego / nieśmiertelnego wyjątku dla głównej klasyfikacji, ale nie chciałem tego uwzględniać, aby nie przesądzać o odpowiedziach. Pozwól, że wyjaśnię, o co chodzi w tym pytaniu: obsługa zgłoszonych wyjątków, a nie zgłaszanie wyjątków. Jaki jest pożądany efekt: Wykryj błąd i spróbuj go naprawić. Jeśli odzyskanie nie jest możliwe, przekaż najbardziej znaczące informacje zwrotne.

Ponownie, w przykładzie http get / post, pytanie brzmi: czy powinieneś podać nowy obiekt, który opisuje, co się stało z pierwotnym dzwoniącym? Jeśli ten pomocnik był w bibliotece, której używasz, czy spodziewałbyś się, że dostarczy ci kod statusu operacji, czy też umieściłby go w bloku try-catch? Jeśli projektujesz go, czy podasz kod statusu, czy zgłaszasz wyjątek i czy zamiast tego pozwalasz wyższemu poziomowi przetłumaczyć go na kod / wiadomość statusu?

Streszczenie: Jak wybrałeś, jeśli kawałek kodu zamiast generować wyjątek, zwraca kod statusu wraz z wszelkimi wynikami, które może przynieść?

Mihalis Bagos
źródło
1
To nie dotyczy błędu, który zmienia formę wykorzystywanej obsługi błędów. W przypadku 404 pozwoliłbyś mu przejść, ponieważ nie jesteś w stanie go obsłużyć.
stonemetal
„liczba całkowita reprezentująca wartość, 0 dla błędu, 1 dla ok, 2 dla ostrzeżenia itp.” Powiedz, że był to przykładowy mankiet! Użycie 0 w celu oznaczenia OK jest zdecydowanie zdecydowanie standardem
anaximander

Odpowiedzi:

15

W wyjątkowych warunkach należy zastosować wyjątki. Zgłoszenie wyjątku polega zasadniczo na stwierdzeniu: „Nie mogę tutaj poradzić sobie z tym warunkiem; czy ktoś znajdujący się wyżej na stosie wywołań może to dla mnie złapać i obsłużyć?”

Zwracanie wartości może być preferowane, jeśli jest jasne, że osoba dzwoniąca weźmie tę wartość i zrobi z nią coś znaczącego. Jest to szczególnie prawdziwe, jeśli zgłoszenie wyjątku ma wpływ na wydajność, tzn. Może wystąpić w ciasnej pętli. Zgłoszenie wyjątku zajmuje znacznie więcej czasu niż zwrócenie wartości (o co najmniej dwa rzędy wielkości).

Wyjątków nie należy nigdy wykorzystywać do implementacji logiki programu. Innymi słowy, nie rzucaj wyjątku, aby coś zrobić; zgłosić wyjątek, aby stwierdzić, że nie można tego zrobić.

Robert Harvey
źródło
Dzięki za odpowiedź, jest to najbardziej pouczające, ale skupiam się na obsłudze wyjątków, a nie na zgłaszaniu wyjątków. Czy powinieneś złapać wyjątek 404, jak tylko go otrzymasz, czy też pozwolić mu iść wyżej na stosie?
Mihalis Bagos
Widzę twoją edycję, ale to nie zmienia mojej odpowiedzi. Przekształć wyjątek w kod błędu, jeśli ma to znaczenie dla osoby dzwoniącej. Jeśli nie jest, obsłuż wyjątek; niech idzie w górę stosu; lub złap go, zrób coś z nim (np. zapisz go w dzienniku lub coś innego) i ponownie wrzuć.
Robert Harvey
1
+1: dla rozsądnego i zrównoważonego wyjaśnienia. Do obsługi wyjątkowych przypadków należy zastosować wyjątek. W przeciwnym razie funkcja lub metoda powinna zwrócić znaczącą wartość (typ wyliczenia lub typ opcji), a osoba wywołująca powinna obsługiwać ją poprawnie. Może warto wspomnieć o trzeciej alternatywie popularnej w programowaniu funkcjonalnym, czyli kontynuacjach.
Giorgio
4

Pewna dobra rada, którą kiedyś przeczytałem, to: rzucaj wyjątki, gdy nie możesz robić postępów, biorąc pod uwagę stan danych, z którymi masz do czynienia, jednak jeśli masz metodę, która może zgłosić wyjątek, podaj także, w miarę możliwości, metodę stwierdzenia, czy dane są rzeczywiście ważne przed wywołaniem metody

Na przykład System.IO.File.OpenRead () wyrzuci wyjątek FileNotFoundException, jeśli podany plik nie istnieje, jednak zapewnia również metodę .Exists (), która zwraca wartość logiczną wskazującą, czy plik jest obecny, którą należy wywołać wcześniej wywołanie OpenRead (), aby uniknąć nieoczekiwanych wyjątków.

Aby odpowiedzieć na pytanie „kiedy powinienem poradzić sobie z wyjątkiem”, powiedziałbym, gdziekolwiek można coś z tym zrobić. Jeśli twoja metoda nie może poradzić sobie z wyjątkiem zgłaszanym przez metodę, którą wywołuje, nie wychwytuj jej. Niech podniesie się w górę łańcucha połączeń do czegoś, co może sobie z tym poradzić. W niektórych przypadkach może to być tylko rejestrator nasłuchujący wyjątku Application.UnhandledException.

Trevor Pilley
źródło
Wiele osób, szczególnie programiści python , wolą EAFP, tj. „Łatwiej prosić o wybaczenie niż uzyskać pozwolenie”
Mark Booth
1
+1 za komentarz dotyczący unikania wyjątków jak w .Exists ().
codingoutloud
+1 To wciąż dobra rada. W kodzie, który piszę / zarządzam, wyjątek jest „wyjątkowy”, 9/10 razy wyjątek jest przeznaczony dla programistów, aby zobaczyć, mówi hej, powinieneś być programistą defensywnym! Za drugim razem jest to coś, z czym nie możemy sobie poradzić, rejestrujemy to i wychodzimy najlepiej, jak potrafimy. Zgodność jest ważna, na przykład, zgodnie z konwencją normalnie mielibyśmy prawdziwą fałszywą odpowiedź, a wewnętrzne wiadomości dla standardowej taryfy / przetwarzania. Interfejsy API innych firm, które wydają się rzucać wyjątki dla wszystkiego, można obsłużyć podczas połączenia i zwrócić zgodnie ze standardowym uzgodnionym procesem.
Gavin Howden
3

użyj try / catch / wreszcie, aby zwrócić wyliczenie (lub liczbę całkowitą, która reprezentuje wartość, 0 dla błędu, 1 dla ok, 2 dla ostrzeżenia itp., w zależności od przypadku), aby odpowiedź była zawsze w porządku,

To okropny projekt. Nie „maskuj” wyjątku, tłumacząc na kod numeryczny. Pozostaw to jako właściwy, jednoznaczny wyjątek.

czy należy przepuścić wyjątek, aby część wywołująca sobie z nim poradziła?

Od tego są wyjątki.

rozpatrywane tak blisko, jak to możliwe

To wcale nie jest uniwersalna prawda. Czasami to dobry pomysł. Innym razem nie jest to tak pomocne.

S.Lott
źródło
To nie jest straszny projekt. Microsoft implementuje go w wielu miejscach, a mianowicie na domyślnym dostawcy członkostwa asp.NET. Poza tym nie widzę, jak ta odpowiedź wnosi coś do rozmowy
Mihalis Bagos
@MihalisBagos: Wszystko, co mogę zrobić, to zasugerować, że podejście Microsoftu nie jest uwzględnione w każdym języku programowania. W językach bez wyjątków zwracanie wartości jest niezbędne. C jest najbardziej znaczącym przykładem. W językach z wyjątkami zwracanie „wartości kodu” w celu wskazania błędów jest okropnym projektem. Prowadzi to do (czasem) nieporęcznych ifinstrukcji zamiast (czasem) prostszej obsługi wyjątków. Odpowiedź („Jak wybrać ...”) jest prosta. Nie robić . Myślę, że powiedzenie tego dodaje się do rozmowy. Możesz jednak zignorować tę odpowiedź.
S.Lott,
Nie twierdzę, że twoja opinia się nie liczy, ale mówię, że twoja opinia nie została rozwinięta. Biorąc pod uwagę ten komentarz, rozumiem, że tam, gdzie możemy zastosować wyjątki, powinniśmy tylko dlatego, że możemy? Nie widzę kodu statusu jako maskowania, a nie kategoryzacji / organizacji przypadków przepływu kodu
Mihalis Bagos
1
Wyjątkiem jest już kategoryzacja / organizacja. Nie widzę wartości w zastępowaniu jednoznacznego wyjątku wartością zwracaną, którą można łatwo pomylić z „normalnymi” lub „nietypowymi” wartościami zwracanymi. Zwracane wartości powinny zawsze być wyjątkowe. Moja opinia nie jest bardzo rozwinięta: jest bardzo prosta. Nie przekształcaj wyjątku w kod numeryczny. Był to już doskonale dobry obiekt danych, który obsługuje wszystkie idealnie dobre przypadki użycia, jakie możemy sobie wyobrazić.
S.Lott,
3

Zgadzam się z S.Lott . Złapanie wyjątku jak najbliżej źródła może być dobrym lub złym pomysłem, w zależności od sytuacji. Kluczem do obsługi wyjątków jest złapanie ich tylko wtedy, gdy możesz coś z tym zrobić. Złapanie ich i zwrócenie wartości liczbowej funkcji wywołującej jest ogólnie złym projektem. Po prostu chcesz pozwolić im unosić się w górze, aż będziesz w stanie odzyskać.

Zawsze uważam obsługę wyjątków za krok od mojej logiki aplikacji. Zadaję sobie pytanie: czy ten wyjątek zostanie zgłoszony, jak daleko wstecz stosu wywołań muszę przeszukiwać, zanim moja aplikacja będzie w stanie do odzyskania? W wielu przypadkach, jeśli w aplikacji nie mogę nic zrobić, aby odzyskać, może to oznaczać, że nie łapię tego aż do najwyższego poziomu i po prostu rejestruję wyjątek, nie udaje mi się zadanie i staram się całkowicie zamknąć.

Naprawdę nie ma twardej i szybkiej reguły określającej, kiedy i jak skonfigurować obsługę wyjątków, niż pozostawienie ich w spokoju, dopóki nie będziesz wiedział, co z nimi zrobić.

DwayneAllen
źródło
1

Wyjątki to piękne rzeczy. Pozwalają one na przedstawienie jasnego opisu problemu związanego z czasem wykonywania bez niepotrzebnych dwuznaczności. Wyjątki mogą być wpisywane, podtypulowane i mogą być obsługiwane według typu. Można je przekazywać do obsługi w innym miejscu, a jeśli nie można ich obsłużyć, można je ponownie podnieść, aby zająć się wyższą warstwą w aplikacji. Będą również automatycznie wracać z Twojej metody bez konieczności przywoływania wielu szalonych elementów logicznych, aby radzić sobie z zaciemnionymi kodami błędów.

Powinieneś zgłosić wyjątek natychmiast po napotkaniu nieprawidłowych danych w kodzie. Powinieneś zawinąć wywołania innych metod w try..catch..full, aby obsłużyć wszelkie wyjątki, które mogą zostać zgłoszone, a jeśli nie wiesz, jak zareagować na dany wyjątek, wyrzuć go ponownie, aby wskazać wyższym warstwom, że jest coś złego, co powinno być rozwiązane gdzie indziej.

Zarządzanie kodami błędów może być bardzo trudne. Zwykle kończy się to niepotrzebnym powielaniem w kodzie i / lub dużą ilością niechlujnej logiki do radzenia sobie ze stanami błędów. Bycie użytkownikiem i napotkanie kodu błędu jest jeszcze gorsze, ponieważ sam kod nie będzie znaczący i nie zapewni użytkownikowi kontekstu błędu. Z drugiej strony wyjątek może dać użytkownikowi coś pożytecznego, na przykład „zapomniałeś wpisać wartość” lub „wprowadziłeś niepoprawną wartość, oto prawidłowy zakres, którego możesz użyć ...” lub „nie wiedz, co się stało, skontaktuj się z pomocą techniczną i powiedz im, że właśnie się zawiesiłem, i podaj im następujący ślad stosu ... ”.

Więc moje pytanie do PO brzmi: dlaczego na ziemi NIE chcesz używać wyjątków w stosunku do zwracania kodów błędów?

S.Robins
źródło
0

Wszystkie dobre odpowiedzi. Chciałbym również dodać, że zwracanie kodu błędu zamiast zgłaszania wyjątku może skomplikować kod osoby dzwoniącej. Powiedz metodę A wywołuje metodę B wywołuje metodę C i C napotyka błąd. Jeśli C zwraca kod błędu, teraz B musi mieć logikę, aby ustalić, czy może obsłużyć ten kod błędu. Jeśli nie może, to należy zwrócić go do A. Jeśli A nie może obsłużyć błędu, to co robisz? Rzuć wyjątek? W tym przykładzie kod jest znacznie bardziej przejrzysty, jeśli C po prostu zgłasza wyjątek, B nie przechwytuje wyjątku, więc automatycznie przerywa bez dodatkowego kodu potrzebnego do tego, a A może wychwycić pewne rodzaje wyjątków, pozwalając innym kontynuować połączenie stos.

Przypomina to dobrą zasadę kodowania poprzez:

Linie kodu są jak złote kule. Chcesz użyć jak najmniejszej ilości, aby wykonać zadanie.

Raymond Saltrelli
źródło
0

Użyłem kombinacji obu rozwiązań: dla każdej funkcji sprawdzania poprawności przekazuję rekord, który wypełniam statusem sprawdzania poprawności (kod błędu). Na końcu funkcji, jeśli istnieje błąd sprawdzania poprawności, generuję wyjątek, w ten sposób nie generuję wyjątku dla każdego pola, ale tylko raz.

Skorzystałem również z tego, że zgłoszenie wyjątku zatrzyma wykonanie, ponieważ nie chcę, aby wykonywanie było kontynuowane, gdy dane są nieprawidłowe.

Na przykład

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Jeśli polegasz tylko na wartości logicznej, programista korzystający z mojej funkcji powinien wziąć to pod uwagę pisząc:

if not Validate() then
  DoNotContinue();

ale może zapomnieć i tylko zadzwonić Validate()(wiem, że nie powinien, ale może mógłby).

Tak więc w powyższym kodzie uzyskałem dwie zalety:

  1. Tylko jeden wyjątek w funkcji sprawdzania poprawności.
  2. Wyjątek, nawet niewyłapany, zatrzyma wykonanie i pojawi się w czasie testu
Bahaa
źródło
0

Nie ma tu jednej odpowiedzi - jakby nie było jednego rodzaju HttpException.

Wydaje się sensowne, że bazowe biblioteki HTTP zgłaszają wyjątek, gdy otrzymują odpowiedź 4xx lub 5xx; ostatnim razem, gdy spojrzałem na specyfikacje HTTP, były to błędy.

Jeśli chodzi o zgłaszanie wyjątku - lub pakowanie go i ponowne rzucanie - myślę, że tak naprawdę chodzi o przypadek użycia. Na przykład, jeśli piszesz opakowanie, aby pobrać niektóre dane z interfejsu API i udostępnić je aplikacjom, możesz zdecydować, że semantycznie żądanie nieistniejącego zasobu, który zwraca HTTP 404, ma sens, aby je złapać i zwrócić wartość null. Z drugiej strony błąd 406 (niedopuszczalny) może być warty zgłoszenia błędu, ponieważ oznacza to, że coś się zmieniło, a aplikacja powinna się zawieszać, nagrywać i krzyczeć o pomoc.

Wyatt Barnett
źródło