Konwencje dotyczące wyjątków lub kodów błędów

118

Wczoraj odbyłem gorącą debatę ze współpracownikiem na temat preferowanej metody zgłaszania błędów. Przede wszystkim omawialiśmy wykorzystanie wyjątków lub kodów błędów do zgłaszania błędów między warstwami aplikacji lub modułami.

Jakich reguł używasz, aby zdecydować, czy zgłaszać wyjątki, czy zwracać kody błędów na potrzeby raportowania błędów?

Jorge Ferreira
źródło

Odpowiedzi:

81

W sprawach wysokiego poziomu wyjątki; w sprawach niskiego poziomu, kody błędów.

Domyślnym zachowaniem wyjątku jest rozwinięcie stosu i zatrzymanie programu, jeśli piszę skrypt i idę po klucz, którego nie ma w słowniku, prawdopodobnie jest to błąd i chcę, aby program się zatrzymał i pozwolił mi wiedzieć wszystko o tym.

Jeśli jednak piszę fragment kodu, który muszę znać w każdej możliwej sytuacji, to chcę mieć kody błędów. W przeciwnym razie muszę znać każdy wyjątek, który może zostać zgłoszony przez każdą linię w mojej funkcji, aby wiedzieć, co zrobi (przeczytaj wyjątek, który uziemił linię lotniczą, aby dowiedzieć się, jak trudne to jest). Pisanie kodu, który odpowiednio reaguje na każdą sytuację (w tym te nieszczęśliwe) jest żmudne i trudne, ale to dlatego, że pisanie kodu wolnego od błędów jest żmudne i trudne, a nie dlatego, że przekazujesz kody błędów.

Zarówno Raymond Chen, jak i Joel przedstawili wymowne argumenty przeciwko używaniu wyjątków do wszystkiego.

Tom Dunham
źródło
5
+1 za wskazanie, że kontekst ma coś wspólnego ze strategią obsługi błędów.
alx9r
2
@Tom, dobre punkty, ale wyjątki są gwarantowane. W jaki sposób zapewniamy, że kody błędów są wychwytywane i nie są po cichu ignorowane z powodu błędów?
Pacerier,
13
Więc jedynym argumentem przeciwko wyjątkom jest to, że coś złego może się zdarzyć, gdy zapomnisz złapać wyjątek, ale nie rozważasz równie prawdopodobnej sytuacji, w której zapomnisz sprawdzić zwróconą wartość pod kątem błędów? Nie wspominając o tym, że otrzymujesz ślad stosu, gdy zapomnisz złapać wyjątek, a nic nie otrzymasz, gdy zapomnisz sprawdzić kod błędu.
Esailija
3
C ++ 17 wprowadza nodiscardatrybut, który będzie generował ostrzeżenie kompilatora, jeśli wartość zwracana funkcji nie jest przechowywana. Pomaga trochę wyłapać zapomniany kod błędu. Przykład: godbolt.org/g/6i6E0B
Zitrax
5
Komentarz @ Esailija jest dokładnie na miejscu. Argument przeciwko wyjątkom przyjmuje tutaj hipotetyczny interfejs API oparty na kodzie błędów, w którym wszystkie kody błędów są udokumentowane oraz hipotetyczny programista, który czyta dokumentację, poprawnie identyfikuje wszystkie przypadki błędów, które są logicznie możliwe w jej aplikacji i pisze kod obsługujący każdy z nich , następnie porównuje ten scenariusz z hipotetycznym interfejsem API opartym na wyjątkach i programistą, w którym z jakiegoś powodu jeden z tych kroków idzie źle ... nawet jeśli równie łatwo (prawdopodobnie łatwiej ) jest wykonać wszystkie te kroki poprawnie w interfejsie API opartym na wyjątkach.
Mark Amery,
62

Zwykle wolę wyjątki, ponieważ zawierają one więcej informacji kontekstowych i mogą przekazać (jeśli są właściwie użyte) błąd programistom w jaśniejszy sposób.

Z drugiej strony kody błędów są lżejsze niż wyjątki, ale trudniejsze w utrzymaniu. Sprawdzanie błędów można nieumyślnie pominąć. Kody błędów są trudniejsze do utrzymania, ponieważ musisz zachować katalog ze wszystkimi kodami błędów, a następnie włączyć wynik, aby zobaczyć, jaki błąd został zgłoszony. Zakresy błędów mogą być tutaj pomocne, ponieważ jeśli jedyną rzeczą, która nas interesuje, jest to, czy jesteśmy w obecności błędu, czy nie, łatwiej jest to sprawdzić (np. Kod błędu HRESULT większy lub równy 0 to sukces i mniej niż zero oznacza awarię). Można je nieumyślnie pominąć, ponieważ nie ma programowego wymuszenia, że ​​programista będzie sprawdzał kody błędów. Z drugiej strony nie możesz ignorować wyjątków.

Podsumowując, wolę wyjątki niż kody błędów w prawie wszystkich sytuacjach.

Jorge Ferreira
źródło
4
„Kody błędów są lżejsze niż wyjątki” zależy od tego, co mierzysz i jak mierzysz. Bardzo łatwo jest wymyślić testy, które pokazują, że interfejsy API oparte na wyjątkach mogą być znacznie szybsze.
Mooing Duck
1
@smink, dobre punkty, ale jak radzimy sobie z ogólnymi wyjątkami? Kody błędów są nie tylko lekkie, są w zasadzie nieważkie ; wyjątki to nie tylko przedmioty o średniej wadze, są to obiekty o dużej wadze zawierające informacje o stosie i różne rzeczy, których i tak nie używamy.
Pacerier,
3
@Pacerier: Rozważasz tylko testy, w których są wyrzucane wyjątki. Masz 100% racji, że rzucenie wyjątku C ++ jest znacznie wolniejsze niż zwracanie kodu błędu. Bez dwóch zdań, bez debaty. Gdzie nie różnią, to druga 99,999% kodu. Z wyjątkami nie musimy sprawdzać zwrotów kodu błędu między każdą instrukcją, dzięki czemu kod jest o około 1-50% szybszy (lub nie, w zależności od kompilatora). Co oznacza, że ​​pełny kod może być szybszy lub wolniejszy, w zależności całkowicie od tego, jak kod jest napisany i jak często są generowane wyjątki.
Mooing Duck,
1
@Pacerier: W tym sztucznym teście, który właśnie napisałem, kod oparty na wyjątkach jest tak szybki, jak kody błędów w MSVC i Clang, ale nie w GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (czasy na dole). Wygląda na to, że flagi GCC, których użyłem, generowały niezwykle powolne wyjątki w porównaniu z innymi kompilatorami. Jestem stronniczy, więc skrytykuj mój test i wypróbuj inny wariant.
Mooing Duck,
4
@Mooing Duck, Jeśli testowałeś zarówno kody błędów, jak i wyjątki w tym samym czasie, musisz mieć włączone wyjątki. W takim przypadku wyniki sugerują, że używanie kodów błędów z narzutem obsługi wyjątków nie jest wolniejsze niż używanie samych wyjątków. Aby uzyskać znaczące wyniki, testy kodu błędu musiałyby być wykonywane z wyłączonymi wyjątkami.
Mika Haarahiltunen
24

Wolę wyjątki, ponieważ

  • przerywają przepływ logiki
  • korzystają z hierarchii klas, która zapewnia więcej funkcji / funkcjonalności
  • gdy jest używany prawidłowo, może reprezentować szeroki zakres błędów (np. InvalidMethodCallException jest również LogicException, ponieważ oba występują, gdy w kodzie znajduje się błąd, który powinien być wykrywalny przed uruchomieniem) oraz
  • mogą być użyte do poprawienia błędu (np. definicja klasy FileReadException może wtedy zawierać kod do sprawdzenia, czy plik istnieje, czy jest zablokowany itp.)
JamShady
źródło
2
Twój czwarty punkt nie jest sprawiedliwy: stan błędu po przekonwertowaniu na obiekt, może również zawierać kod, aby sprawdzić, czy plik istnieje, czy jest zablokowany itp. Jest to po prostu odmiana stackoverflow.com/a/3157182/632951
Pacerier,
1
„przerywają przepływ logiki”. Wyjątki mają mniej więcej skutkigoto
peterchaula
22

Kody błędów mogą być ignorowane (i często są!) Przez wywołujące funkcje. Wyjątki przynajmniej zmuszają ich do radzenia sobie z błędem w jakiś sposób. Nawet jeśli ich wersja radzenia sobie z tym polega na tym, że ma pustą obsługę połowu (westchnienie).

Maxam
źródło
17

Bez wątpienia wyjątki od kodów błędów. Wyjątki dają wiele takich samych korzyści, jak w przypadku kodów błędów, ale także znacznie więcej, bez wad związanych z kodami błędów. Jedynym wyjątkiem jest to, że jest nieco bardziej napowietrzny; ale w dzisiejszych czasach ten narzut powinien być uważany za nieistotny dla prawie wszystkich zastosowań.

Oto kilka artykułów omawiających, porównujących i przeciwstawiających te dwie techniki:

W tych jest kilka dobrych linków, które mogą dać ci dalsze czytanie.

Pistos
źródło
16

Nigdy nie pomieszałbym tych dwóch modeli ... zbyt trudno jest przekonwertować jeden na drugi, gdy przechodzisz z jednej części stosu, która używa kodów błędów, do wyższej części, która używa wyjątków.

Wyjątki dotyczą „wszystkiego, co zatrzymuje lub uniemożliwia metodzie lub podprogramowi wykonanie tego, o co go prosiłeś” ... NIE przekazywać komunikatów o nieprawidłowościach lub nietypowych okolicznościach, lub o stanie systemu itp. Używaj wartości zwracanych lub ref (lub out) parametry do tego.

Wyjątki pozwalają na pisanie (i wykorzystywanie) metod z semantyką zależną od funkcji metody, tj. Metoda zwracająca obiekt pracownika lub listę pracowników może być wpisana w tym celu i można ją wykorzystać przez wywołanie.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

W przypadku kodów błędów wszystkie metody zwracają kod błędu, więc dla tych, które muszą zwrócić coś innego do użycia przez kod wywołujący, musisz przekazać zmienną referencyjną, która ma zostać wypełniona tymi danymi, i przetestować zwracaną wartość dla kod błędu i obsłuż go przy każdym wywołaniu funkcji lub metody.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Jeśli kodujesz tak, że każda metoda wykonuje jedną i tylko jedną prostą rzecz, powinieneś zgłosić wyjątek, gdy metoda nie może osiągnąć pożądanego celu metody. Wyjątki są znacznie bogatsze i łatwiejsze w użyciu w ten sposób niż kody błędów. Twój kod jest znacznie bardziej przejrzysty - Standardowy przepływ „normalnej” ścieżki kodu może być poświęcony ściśle przypadkowi, w którym metoda JEST w stanie wykonać to, co chcesz, a następnie kod do wyczyszczenia lub obsługi „Wyjątkowe” okoliczności, w których dzieje się coś złego, co uniemożliwia pomyślne wykonanie metody, można oddzielić od normalnego kodu. Dodatkowo, jeśli nie możesz obsłużyć wyjątku, w którym wystąpił, i musisz przekazać go w górę stosu do interfejsu użytkownika (lub gorzej, przez przewód od składnika średniej warstwy do interfejsu użytkownika), to w przypadku modelu wyjątku

Charles Bretana
źródło
Świetna odpowiedź! Gotowe rozwiązanie w samą porę!
Cristian E.
11

W przeszłości dołączyłem do obozu kodów błędów (zbyt dużo programowałem w C). Ale teraz zobaczyłem światło.

Tak, wyjątki są trochę obciążeniem dla systemu. Ale upraszczają kod, zmniejszając liczbę błędów (i WTF).

Więc używaj wyjątków, ale używaj ich mądrze. I będą twoimi przyjaciółmi.

Na marginesie. Nauczyłem się dokumentować, który wyjątek można zgłosić za pomocą jakiej metody. Niestety nie jest to wymagane w większości języków. Ale zwiększa szansę na obsługę właściwych wyjątków na odpowiednim poziomie.

Toon Krijthe
źródło
1
yap C pozostawia w nas wszystkich kilka nawyków;)
Jorge Ferreira
11

Może zaistnieć kilka sytuacji, w których używanie wyjątków w czysty, jasny i prawidłowy sposób jest uciążliwe, ale zdecydowana większość wyjątków czasowych jest oczywistym wyborem. Największą zaletą obsługi wyjątków w przypadku kodów błędów jest to, że zmienia przepływ wykonywania, co jest ważne z dwóch powodów.

Gdy wystąpi wyjątek, aplikacja nie podąża już swoją „normalną” ścieżką wykonywania. Pierwszym powodem, dla którego jest to tak ważne, jest to, że jeśli autor kodu nie zrobi dobrze i naprawdę nie zrobi wszystko, aby być złym, program zatrzyma się i nie będzie kontynuował wykonywania nieprzewidywalnych rzeczy. Jeśli kod błędu nie zostanie sprawdzony, a odpowiednie działania nie zostaną podjęte w odpowiedzi na zły kod błędu, program będzie dalej robił to, co robi i kto wie, jaki będzie wynik tego działania. Jest wiele sytuacji, w których wykonanie programu „cokolwiek” może okazać się bardzo kosztowne. Rozważ program, który pobiera informacje o wynikach dla różnych instrumentów finansowych sprzedawanych przez firmę i dostarcza te informacje brokerom / hurtownikom. Jeśli coś pójdzie nie tak, a program będzie działał dalej, mogłoby to wysłać błędne dane dotyczące wyników do brokerów i hurtowników. Nie wiem o nikim innym, ale nie chcę być tym, który siedzi w biurze wiceprezesa i wyjaśnia, dlaczego mój kod spowodował, że firma otrzymała 7-cyfrowy mandat regulacyjny. Dostarczanie klientom komunikatów o błędach jest generalnie lepsze niż dostarczanie błędnych danych, które mogą wyglądać na „prawdziwe”, a ta ostatnia sytuacja jest znacznie łatwiejsza do wykonania przy znacznie mniej agresywnym podejściu, takim jak kody błędów.

Drugim powodem, dla którego lubię wyjątki i ich zerwanie z normalnym wykonywaniem, jest to, że znacznie, dużo łatwiej jest oddzielić logikę „normalnych rzeczy się dzieje” od logiki „coś poszło nie tak”. Dla mnie to:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... jest lepsze od tego:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Są też inne fajne rzeczy związane z wyjątkami. Posiadanie garści logiki warunkowej do śledzenia, czy którakolwiek z metod wywoływanych w funkcji zwróciła kod błędu, i zwraca ten kod błędu wyżej, to dużo błędów. W rzeczywistości jest to dużo płyt kotłowych, które mogą się nie udać. O wiele bardziej wierzę w system wyjątków w większości języków, niż w szczurzą grono stwierdzeń „jeśli jeszcze, jeśli-inaczej”, które napisał „Świeżo po studiach” Fred i mam o wiele lepsze rzeczy do zrobienia z moim czasem niż przeglądanie kodu wspomnianego gniazda szczurów.

Psy
źródło
8

Powinieneś użyć obu. Chodzi o to, aby zdecydować, kiedy użyć każdego z nich .

Istnieje kilka scenariuszy, w których wyjątki są oczywistym wyborem :

  1. W niektórych sytuacjach nie możesz nic zrobić z kodem błędu i po prostu musisz obsłużyć go na wyższym poziomie w stosie wywołań , zwykle po prostu zarejestruj błąd, wyświetl coś użytkownikowi lub zamknij program. W takich przypadkach kody błędów wymagałyby ręcznego uzupełniania kodów błędów na poziomie, co jest oczywiście znacznie łatwiejsze w przypadku wyjątków. Chodzi o to, że dotyczy to nieoczekiwanych i trudnych do rozwiązania sytuacji.

  2. Jednak w przypadku sytuacji 1 (gdy dzieje się coś nieoczekiwanego i nie do obsłużenia, po prostu nie chcesz tego zarejestrować), wyjątki mogą być pomocne, ponieważ możesz dodać informacje kontekstowe . Na przykład, jeśli otrzymam SqlException w moich pomocnikach danych niższego poziomu, będę chciał złapać ten błąd na niskim poziomie (gdzie znam polecenie SQL, które spowodowało błąd), aby móc przechwycić te informacje i ponownie przesłać z dodatkowymi informacjami . Zwróć uwagę na magiczne słowo: rzuć ponownie, a nie połykać . Pierwsza zasada obsługi wyjątków: nie połykaj wyjątków . Zwróć również uwagę, że mój wewnętrzny zaczep nie musi niczego rejestrować, ponieważ zewnętrzny zaczep będzie zawierał cały ślad stosu i może go rejestrować.

  3. W niektórych sytuacjach masz sekwencję poleceń i jeśli którekolwiek z nich zawiedzie , powinieneś wyczyścić / pozbyć się zasobów (*), niezależnie od tego, czy jest to sytuacja nieodwracalna (która powinna zostać wyrzucona), czy sytuacja dająca się naprawić (w takim przypadku możesz obsłużyć lokalnie lub w kodzie dzwoniącego, ale nie potrzebujesz wyjątków). Oczywiście znacznie łatwiej jest umieścić wszystkie te polecenia za jednym razem, zamiast testować kody błędów po każdej metodzie i czyścić / usuwać w ostatnim bloku. Zwróć uwagę, że jeśli chcesz, aby błąd się wypalił (co prawdopodobnie jest tym, czego chcesz), nie musisz go nawet łapać - po prostu użyj ostatniego do czyszczenia / usuwania - powinieneś używać tylko catch / retrow, jeśli chcesz aby dodać informacje kontekstowe (patrz punktor 2).

    Jednym z przykładów może być sekwencja instrukcji SQL wewnątrz bloku transakcji. Ponownie, jest to również sytuacja „nie do rozwiązania”, nawet jeśli zdecydujesz się ją wcześnie złapać (lecz lokalnie zamiast bulgotać do góry), nadal jest to fatalna sytuacja, w której najlepszym wynikiem jest przerwanie wszystkiego lub przynajmniej przerwanie dużego część procesu.
    (*) To jest podobne do on error gototego, którego używaliśmy w starym Visual Basicu

  4. W konstruktorach możesz zgłaszać tylko wyjątki.

To powiedziawszy, we wszystkich innych sytuacjach, w których zwracasz informacje, na temat których dzwoniący MOŻE / POWINIEN podjąć jakąś akcję , użycie kodów powrotnych jest prawdopodobnie lepszą alternatywą. Obejmuje to wszystkie oczekiwane „błędy” , ponieważ prawdopodobnie powinny być obsługiwane przez bezpośredniego wywołującego i prawie nie będą musiały być zwiększane zbyt wiele poziomów w stosie.

Oczywiście zawsze można traktować oczekiwane błędy jako wyjątki, a następnie natychmiast przechwytywać jeden poziom wyżej, a także można objąć każdą linię kodu w try catch i podjąć działania dla każdego możliwego błędu. IMO, to zły projekt, nie tylko dlatego, że jest dużo bardziej szczegółowy, ale szczególnie dlatego, że możliwe wyjątki, które mogą zostać wyrzucone, nie są oczywiste bez czytania kodu źródłowego - a wyjątki mogą być wyrzucane z dowolnej głębokiej metody, tworząc niewidoczne goto . Łamią strukturę kodu, tworząc wiele niewidocznych punktów wyjścia, które sprawiają, że kod jest trudny do odczytania i sprawdzenia. Innymi słowy, nigdy nie powinieneś używać wyjątków jako kontroli przepływu, ponieważ innym byłoby to trudne do zrozumienia i utrzymania. Zrozumienie wszystkich możliwych przepływów kodu do testowania może być nawet trudne.
Ponownie: w celu prawidłowego oczyszczenia / usunięcia możesz użyć try-last bez łapania czegokolwiek .

Najpopularniejsza krytyka dotycząca kodów powrotnych mówi, że „ktoś mógłby zignorować kody błędów, ale w tym samym sensie ktoś może również połknąć wyjątki. Zła obsługa wyjątków jest łatwa w obu metodach. Jednak pisanie dobrego programu opartego na kodzie błędów jest nadal znacznie łatwiejsze niż pisanie programu opartego na wyjątkach . A jeśli z jakiegoś powodu zdecydujesz się zignorować wszystkie błędy (stare on error resume next), możesz to łatwo zrobić za pomocą kodów powrotu i nie możesz tego zrobić bez wielu standardowych schematów try-catch.

Drugą najpopularniejszą krytyką dotyczącą kodów powrotnych jest to, że „trudno jest wypalić” - ale to dlatego, że ludzie nie rozumieją, że wyjątki dotyczą sytuacji, których nie można odzyskać, podczas gdy kody błędów nie.

Wybór między wyjątkami a kodami błędów to szara strefa. Jest nawet możliwe, że musisz uzyskać kod błędu z jakiejś metody biznesowej wielokrotnego użytku, a następnie zdecydujesz się opakować go w wyjątek (prawdopodobnie dodając informacje) i pozwolić mu się rozwinąć. Ale błędem projektowym jest założenie, że WSZYSTKIE błędy powinny być odrzucane jako wyjątki.

Podsumowując:

  • Lubię używać wyjątków, gdy mam nieoczekiwaną sytuację, w której nie ma zbyt wiele do zrobienia, a zwykle chcemy przerwać duży blok kodu lub nawet całą operację lub program. To jest jak stare „przy błędzie goto”.

  • Lubię używać kodów powrotnych, gdy spodziewałem się sytuacji, w których kod dzwoniącego może / powinien podjąć jakieś działanie. Dotyczy to większości metod biznesowych, interfejsów API, walidacji i tak dalej.

Ta różnica między wyjątkami a kodami błędów jest jedną z zasad projektowania języka GO, który używa wyrażenia „panika” w przypadku krytycznych nieoczekiwanych sytuacji, podczas gdy zwykłe oczekiwane sytuacje są zwracane jako błędy.

Jednak w przypadku GO pozwala również na wiele zwracanych wartości , co jest czymś, co bardzo pomaga w używaniu kodów powrotu, ponieważ możesz jednocześnie zwrócić błąd i coś innego. W C # / Javie możemy to osiągnąć bez parametrów, krotek lub (moich ulubionych) typów generycznych, które w połączeniu z wyliczeniami mogą zapewnić wywołującemu wyraźne kody błędów:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

Jeśli dodam nowy możliwy zwrot w mojej metodzie, mogę nawet sprawdzić wszystkich wywołujących, jeśli na przykład pokrywają tę nową wartość w instrukcji switch. Naprawdę nie możesz tego zrobić z wyjątkami. Korzystając z kodów zwrotnych, zazwyczaj znasz z wyprzedzeniem wszystkie możliwe błędy i je testujesz. Z wyjątkami zazwyczaj nie wiesz, co może się stać. Zawijanie wyliczeń wewnątrz wyjątków (zamiast Generics) jest alternatywą (o ile jest jasne, jaki typ wyjątków będzie generować każda metoda), ale IMO to nadal zły projekt.

drizin
źródło
4

Moje rozumowanie byłoby takie, jeśli piszesz sterownik niskiego poziomu, który naprawdę potrzebuje wydajności, a następnie użyj kodów błędów. Ale jeśli używasz tego kodu w aplikacji wyższego poziomu i może on obsłużyć trochę narzutów, opakuj ten kod interfejsem, który sprawdza te kody błędów i zgłasza wyjątki.

We wszystkich innych przypadkach prawdopodobnie najlepszym rozwiązaniem są wyjątki.

Claudiu
źródło
4

Może siedzę tu na płocie, ale ...

  1. To zależy od języka.
  2. Niezależnie od wybranego modelu, konsekwentnie używaj go.

W Pythonie używanie wyjątków jest standardową praktyką i z przyjemnością definiuję własne wyjątki. W C nie masz żadnych wyjątków.

W C ++ (przynajmniej w STL) wyjątki są zwykle generowane tylko w przypadku naprawdę wyjątkowych błędów (sam ich praktycznie nigdy nie widzę). Nie widzę powodu, by robić cokolwiek innego we własnym kodzie. Tak, łatwo jest zignorować zwracane wartości, ale C ++ również nie wymusza wychwytywania wyjątków. Myślę, że po prostu musisz nabrać nawyku robienia tego.

Podstawą kodu, nad którym pracuję, jest głównie C ++ i używamy kodów błędów prawie wszędzie, ale jest jeden moduł, który zgłasza wyjątki dla każdego błędu, w tym bardzo nietypowych, a cały kod, który używa tego modułu, jest dość okropny. Ale może to być spowodowane tym, że pomieszaliśmy wyjątki i kody błędów. Kod, który konsekwentnie używa kodów błędów, jest znacznie łatwiejszy w obsłudze. Gdyby nasz kod konsekwentnie używał wyjątków, może nie byłoby tak źle. Mieszanie tych dwóch nie wydaje się działać tak dobrze.

jrb
źródło
4

Ponieważ pracuję z C ++ i mam RAII, aby były bezpieczne w użyciu, używam wyjątków prawie wyłącznie. Wyciąga obsługę błędów z normalnego przepływu programu i sprawia, że ​​intencja jest jaśniejsza.

Jednak zostawiam wyjątki dla wyjątkowych okoliczności. Jeśli spodziewam się, że jakiś błąd będzie się często zdarzał, sprawdzę przed wykonaniem operacji, czy operacja się powiedzie, lub wywołam wersję funkcji, która zamiast tego używa kodów błędów (Lubię to TryParse())

Zaćmienie
źródło
3

Podpisy metod powinny informować o tym, co robi metoda. Coś w rodzaju long errorCode = getErrorCode (); może być w porządku, ale long errorCode = fetchRecord (); jest mylący.

Paul Croarkin
źródło
3

Moje podejście jest takie, że możemy używać jednocześnie kodów wyjątków i błędów.

Jestem przyzwyczajony do definiowania kilku typów wyjątków (np .: DataValidationException lub ProcessInterruptExcepion), a wewnątrz każdego wyjątku definiuję bardziej szczegółowy opis każdego problemu.

Prosty przykład w Javie:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

W ten sposób używam wyjątków do definiowania błędów kategorii i kodów błędów do definiowania bardziej szczegółowych informacji o problemie.

sakana
źródło
2
erm ... throw new DataValidationException("The input is too small")? Jedną z zalet wyjątków jest umożliwienie szczegółowych informacji.
Eva
2

Wyjątki dotyczą wyjątkowych okoliczności - tj. Gdy nie są one częścią normalnego przepływu kodu.

Dość zasadne jest mieszanie wyjątków i kodów błędów, gdzie kody błędów reprezentują stan czegoś, a nie błąd w działaniu kodu jako takiego (np. Sprawdzanie kodu powrotu z procesu potomnego).

Ale kiedy zdarzają się wyjątkowe okoliczności, uważam, że Wyjątki są najbardziej wyrazistym modelem.

Istnieją przypadki, w których wolisz lub musisz używać kodów błędów zamiast wyjątków i zostały one już odpowiednio omówione (inne niż inne oczywiste ograniczenia, takie jak obsługa kompilatora).

Ale idąc w innym kierunku, użycie wyjątków umożliwia tworzenie abstrakcji na jeszcze wyższym poziomie do obsługi błędów, co może uczynić Twój kod jeszcze bardziej wyrazistym i naturalnym. Gorąco polecam przeczytanie tego znakomitego, ale niedocenianego artykułu eksperta C ++ Andrei Alexandrescu na temat tego, co nazywa „Enforcements”: http://www.ddj.com/cpp/184403864 . Chociaż jest to artykuł w języku C ++, zasady mają ogólne zastosowanie i całkiem pomyślnie przetłumaczyłem koncepcję wymuszania na język C #.

philsquared
źródło
2

Po pierwsze, zgadzam się z odpowiedzią Toma, że dla rzeczy wysokiego poziomu używaj wyjątków, a dla rzeczy niskiego poziomu używaj kodów błędów, o ile nie jest to architektura zorientowana na usługi (SOA).

W SOA, gdzie metody mogą być wywoływane na różnych maszynach, wyjątki nie mogą być przekazywane przez sieć, zamiast tego używamy odpowiedzi sukcesu / niepowodzenia o strukturze takiej jak poniżej (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

I użyj w ten sposób:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

Gdy są one konsekwentnie używane w odpowiedziach usług, tworzy bardzo ładny wzorzec obsługi sukcesów / niepowodzeń w aplikacji. Umożliwia to łatwiejszą obsługę błędów w wywołaniach asynchronicznych w usługach, a także w usługach.

orad
źródło
1

Wolałbym Wyjątki dla wszystkich przypadków błędów, z wyjątkiem sytuacji, gdy awaria jest oczekiwanym wynikiem działania funkcji, która zwraca pierwotny typ danych, bez błędów. Np. Znalezienie indeksu podciągu w większym ciągu zwykle zwróciło -1, jeśli nie zostanie znalezione, zamiast zgłosić NotFoundException.

Zwracanie nieprawidłowych wskaźników, które mogą być wyłuskane (np. Powodując wyjątek NullPointerException w Javie) jest niedopuszczalne.

Używanie wielu różnych numerycznych kodów błędów (-1, -2) jako wartości zwracanych dla tej samej funkcji jest zwykle niewłaściwym stylem, ponieważ klienci mogą wykonać sprawdzenie „== -1” zamiast „<0”.

Należy tutaj pamiętać o ewolucji interfejsów API w czasie. Dobre API pozwala zmieniać i rozszerzać zachowanie w przypadku awarii na kilka sposobów bez przerywania pracy klientów. Np. Jeśli uchwyt błędu klienta został sprawdzony pod kątem 4 przypadków błędów i dodasz piątą wartość błędu do swojej funkcji, program obsługi klienta może tego nie przetestować i zepsuć. Jeśli zgłosisz wyjątki, zwykle ułatwi to klientom migrację do nowszej wersji biblioteki.

Inną rzeczą do rozważenia jest praca w zespole, gdzie wyznaczyć jasną linię dla wszystkich programistów, aby podjęli taką decyzję. Np. „Wyjątki dla rzeczy wysokiego poziomu, kody błędów dla rzeczy niskiego poziomu” jest bardzo subiektywne.

W każdym przypadku, gdy możliwy jest więcej niż jeden trywialny typ błędu, kod źródłowy nie powinien nigdy używać literału numerycznego do zwracania kodu błędu lub do jego obsługi (powrót -7, jeśli x == -7 ...), ale zawsze nazwana stała (zwraca NO_SUCH_FOO, jeśli x == NO_SUCH_FOO).

tkruse
źródło
1

Jeśli pracujesz w dużym projekcie, nie możesz używać tylko wyjątków lub tylko kodów błędów. W różnych przypadkach należy zastosować różne podejścia.

Na przykład decydujesz się używać tylko wyjątków. Ale gdy zdecydujesz się użyć przetwarzania zdarzeń asynchronicznych. W takich sytuacjach używanie wyjątków do obsługi błędów jest złym pomysłem. Ale używanie kodów błędów wszędzie w aplikacji jest żmudne.

Dlatego uważam, że jednoczesne używanie zarówno wyjątków, jak i kodów błędów jest normalne.

gomony
źródło
0

W przypadku większości aplikacji wyjątki są lepsze. Wyjątkiem jest sytuacja, gdy oprogramowanie musi komunikować się z innymi urządzeniami. Domena, w której pracuję, to kontrole przemysłowe. Tutaj kody błędów są preferowane i oczekiwane. Więc odpowiadam, że to zależy od sytuacji.

Jim C
źródło
0

Myślę, że zależy to również od tego, czy naprawdę potrzebujesz informacji, takich jak ślad stosu z wyniku. Jeśli tak, zdecydowanie wybierz opcję Wyjątek, która dostarcza obiektowi wiele informacji o problemie. Jeśli jednak interesuje Cię tylko wynik i nie obchodzi Cię, dlaczego ten wynik, przejdź do kodu błędu.

np. kiedy przetwarzasz plik i napotykasz IOException, klient może być zainteresowany wiedzą, skąd to się stało, otwieraniem pliku lub analizowaniem pliku itp. Więc lepiej zwróć IOException lub jego konkretną podklasę. Jednak w scenariuszu takim, jak masz metodę logowania i chcesz wiedzieć, że zakończyła się sukcesem, czy nie, albo po prostu zwracasz wartość logiczną, albo aby wyświetlić poprawny komunikat, zwracasz kod błędu. Tutaj klient nie jest zainteresowany tym, która część logiki spowodowała ten kod błędu. Po prostu wie, czy jego poświadczenie jest nieprawidłowe lub blokada konta itp.

Innym przypadkiem, o którym przychodzi mi do głowy, jest przesyłanie danych w sieci. Twoja metoda zdalna może zwracać tylko kod błędu zamiast wyjątku, aby zminimalizować transfer danych.

Ashah
źródło
0

Moja ogólna zasada brzmi:

  • W funkcji mógł wystąpić tylko jeden błąd: użyj kodu błędu (jako parametru funkcji)
  • Może pojawić się więcej niż jeden określony błąd: zgłoszenie wyjątku
MARTWA WOŁOWINA
źródło
-1

Kody błędów również nie działają, gdy metoda zwraca coś innego niż wartość liczbowa ...

Omar Kooheji
źródło
3
Yyy ... nie. Zobacz paradygmat win32 GetLastError (). Nie bronię tego, po prostu stwierdzam, że się mylisz.
Tim
4
W rzeczywistości istnieje wiele innych sposobów, aby to zrobić. Innym sposobem jest zwrócenie obiektu, który zawiera kod błędu i rzeczywistą wartość zwracaną. Jeszcze innym sposobem jest przekazywanie odniesień.
Pacerier,