Kiedy i jak należy używać obsługi wyjątków?

83

Czytam o obsłudze wyjątków. Mam trochę informacji na temat obsługi wyjątków, ale mam kilka pytań:

  1. Kiedy zgłosić wyjątek?
  2. Czy zamiast zgłaszać wyjątek, możemy użyć wartości zwracanej do wskazania błędu?
  3. Jeśli chronię wszystkie moje funkcje blokami try-catch, czy nie zmniejszy to wydajności?
  4. Kiedy używać obsługi wyjątków?
  5. Widziałem projekt, w którym każda funkcja w tym projekcie zawierała blok try-catch (tj. Kod wewnątrz całej funkcji jest otoczony blokiem try-catch). Czy to dobra praktyka?
  6. Jaka jest różnica między try-catch i __try __except?
Umesha MS
źródło
Będziesz musiał zadać bardziej szczegółowe pytania niż „Kiedy zgłosić wyjątek”. Rzucasz wyjątek, gdy dzieje się coś wyjątkowego.
Falmarri
Pisałem o tym w stosunku do PHP, ale myślę, że prawie wszystko ma zastosowanie do C ++. Sprawdź post na blogu .
ircmaxell

Odpowiedzi:

96

Oto dość obszerny przewodnik po wyjątkach, które moim zdaniem są obowiązkowe:

Wyjątki i obsługa błędów - C ++ FAQ lub C ++ FAQ lite

Zasadniczo należy zgłosić wyjątek, gdy program może zidentyfikować plik zewnętrznyproblem uniemożliwiający wykonanie. Jeśli otrzymasz dane z serwera, a te dane są nieprawidłowe, zgłoś wyjątek. Brak miejsca na dysku? Podaj wyjątek. Promienie kosmiczne uniemożliwiają przeszukiwanie bazy danych? Podaj wyjątek. Ale jeśli otrzymasz nieprawidłowe dane z własnego programu - nie zgłaszaj wyjątku. Jeśli twój problem pochodzi z twojego własnego złego kodu, lepiej jest użyć ASSERTów, aby się przed nim chronić. Obsługa wyjątków jest potrzebna do zidentyfikowania problemów, z którymi program nie może sobie poradzić, i poinformowania ich o użytkowniku, ponieważ użytkownik może sobie z nimi poradzić. Ale błędy w twoim programie nie są czymś, z czym użytkownik może sobie poradzić, więc awaria programu powie niewiele mniej niż „Wartość answer_to_life_and_universe_and_everything nie wynosi 42! To nigdy nie powinno się zdarzyć !!!! 11”.

Złap wyjątek, w którym możesz zrobić z nim coś użytecznego, na przykład wyświetlić okno komunikatu. Wolę złapać wyjątek w funkcji, która w jakiś sposób obsługuje dane wejściowe użytkownika. Na przykład, użytkownik naciska przycisk „Annihilate all hunams”, a wewnątrz funkcji annihilateAllHunamsClicked () znajduje się blok try ... catch, który mówi „Nie mogę”. Chociaż anihilacja hunamkinda jest złożoną operacją, która wymaga wywołania dziesiątek funkcji, jest tylko jedna próba ... złapanie, ponieważ dla użytkownika jest to operacja atomowa - jedno kliknięcie przycisku. Kontrole wyjątków w każdej funkcji są zbędne i brzydkie.

Nie mogę również polecić wystarczającego zaznajomienia się z RAII - to znaczy upewnienia się, że wszystkie zainicjowane dane są automatycznie niszczone. Można to osiągnąć, inicjując jak najwięcej na stosie, a kiedy chcesz zainicjować coś na stercie, użyj jakiegoś inteligentnego wskaźnika. Wszystko zainicjowane na stosie zostanie automatycznie zniszczone po wyrzuceniu wyjątku. Jeśli używasz głupich wskaźników w stylu C, ryzykujesz wyciek pamięci, gdy zostanie zgłoszony wyjątek, ponieważ nie ma nikogo, kto mógłby je wyczyścić po wyjątku (oczywiście, możesz użyć wskaźników w stylu C jako członków swojej klasy, ale upewnij się, że są zadbany w destructorze).

Septagram
źródło
Dzięki za cenny wkład. > „Obsługa wyjątków jest potrzebna do zidentyfikowania problemów, z którymi program> nie może sobie poradzić, i poinformowania ich o użytkowniku, ponieważ użytkownik może je> obsłużyć” Zamiast zgłaszać wyjątek, mogę zwrócić wartość błędu, aw funkcji wywołującej mogę sprawdzić, czy wystąpił błąd i wyświetl komunikat, który pomoże użytkownikowi sobie z tym poradzić.
Umesha MS,
Spójrz ponownie na często zadawane pytania, szczególnie na trzeci i czwarty fragment kodu w tej sekcji: parashift.com/c++-faq-lite/exceptions.html#faq-17.2 Wyjątki są świetne, ponieważ pozwalają na ujawnienie błędów z dowolnej głębokości stosu wywołań ... To tak, jakby kody błędów to batysfery, wyjątkami są łodzie podwodne :)
Septagram,
2
@Septagram "Jeśli twój problem pochodzi z twojego własnego złego kodu, lepiej jest użyć ASSERTów do ochrony przed nim.": Assert nie pozwoli ci kontynuować zadań, które nie zależą od błędnej gałęzi (tylko dlatego, że twój program ma błąd w jednej funkcji nie oznacza, że ​​nie może robić innych przydatnych rzeczy). Nie pozwoli ci używać niestandardowych rejestratorów. Pominie on destruktory, z których czerpiesz korzyści, używając RAII (chcesz, aby bufory plików zostały opróżnione przed zakończeniem programu? Lepiej nie pomijaj tych destruktorów w takim razie fclose!). Nie da ci to pełnego śladu stosu.
daemonspring
Coś do powiedzenia na temat RAII i „głupich wskaźników”. To, że używasz „głupiego wskaźnika”, nie oznacza, że ​​nie podążasz za RAII. Pamiętaj, że tylko wtedy, gdy przydzielasz zasoby systemowe, musisz się upewnić, że zostały one zwolnione. Wskaźnik to po prostu zmienna na stosie, która przechowuje adres pamięci. Alokacja i inicjalizacja obiektu nie jest wymagana, aby używać „głupiego wskaźnika”. „Głupi wskaźnik” może po prostu zawierać adres obiektu, który jest przydzielony w innym miejscu i jest całkowicie wygodny w użyciu. (ciąg dalszy w następnym komentarzu)
SeanRamey
Możesz mieć obiekt Renderer, który przechowuje adres obiektu Display we wskaźniku w celu rysowania elementów na ekranie, ale nie możesz użyć inteligentnego wskaźnika, ponieważ inteligentny wskaźnik zniszczy obiekt Display, gdy zniszczysz obiekt Renderer , czego nie chcesz. W tym przypadku „głupi wskaźnik” lub odniesienie to jedyne sposoby.
SeanRamey
12

Wyjątki są przydatne w różnych okolicznościach.

Po pierwsze, istnieją pewne funkcje, w przypadku których koszt obliczenia warunku wstępnego jest tak wysoki, że lepiej jest po prostu wykonać obliczenia i przerwać z wyjątkiem, jeśli okaże się, że warunek wstępny nie jest spełniony. Na przykład nie można odwrócić macierzy osobliwej, jednak aby obliczyć, czy jest ona pojedyncza, należy obliczyć wyznacznik, który jest bardzo drogi: może i tak trzeba będzie to zrobić wewnątrz funkcji, więc po prostu „spróbuj” odwrócić macierz i podać raport błąd, jeśli nie możesz, zgłaszając wyjątek. Jest to w zasadzie wyjątek jako negatywne użycie warunku wstępnego .

Są też inne przypadki, w których kod jest już złożony i przekazywanie informacji o błędach w górę łańcucha wywołań jest trudne. Dzieje się tak częściowo dlatego, że C i C ++ mają zepsute modele struktury danych: są inne, lepsze sposoby, ale C ++ ich nie obsługuje (na przykład używanie monad w Haskell). To użycie jest w zasadzie nie przeszkadzało mi to zrobić dobrze, więc rzucę wyjątek : to nie jest właściwy sposób, ale jest praktyczny.

Następnie istnieje główne zastosowanie wyjątków: do zgłaszania, gdy zewnętrzne warunki wstępne lub niezmienniki, takie jak wystarczające zasoby, takie jak pamięć lub miejsce na dysku, nie są dostępne. W takim przypadku zazwyczaj kończysz program lub jego główną podsekcję, a wyjątkiem jest dobry sposób przekazywania informacji o problemie. Wyjątki C ++ zostały zaprojektowane do zgłaszania błędów, które uniemożliwiają kontynuowanie programu .

Model wyjątek obsługi używane w większości współczesnych języków, w tym C ++ jest znany być złamane. Jest o wiele za potężny. Teoretycy opracowali teraz lepsze modele niż całkowicie otwarty model „rzuć czymkolwiek” i „może, a może nie złap tego”. Ponadto używanie informacji o typie do klasyfikowania wyjątków nie było dobrym pomysłem.

Więc najlepszą rzeczą, jaką możesz zrobić, jest oszczędne rzucanie wyjątków, gdy występuje rzeczywisty błąd i nie ma innego sposobu, aby sobie z nim poradzić i wychwycić wyjątki jak najbliżej punktu rzutu .

Yttrill
źródło
15
Czy mógłbyś dodać kilka linków / odniesień do „obsługi wyjątków jest zepsuta” i „Teoretycy opracowali teraz lepsze modele”?
Juraj Blaho
1
Krytyczną rzeczą, o której często trzeba wiedzieć podczas wychwytywania wyjątków, ale o tym, o której większość wyjątków nie dostarcza żadnych użytecznych danych, jest to, czy kod, który wywołał wyjątek, miał jakikolwiek wpływ na stan systemu. Czy któryś z modeli teoretyków zajmuje się tym?
supercat
@JurajBlaho Próbowałem znaleźć linki / odniesienia, o których mówisz, bez powodzenia. Czy możesz edytować jego odpowiedź, aby dodać je na końcu?
ForceMagic,
Zgadzam się z przykładem 1 i 3. Uważam jednak, że jeśli kod jest zbyt złożony, więc myślisz o rzuceniu wyjątku, być może będziesz potrzebować refaktoryzacji. Assert to także dobry sposób (nawet lepszy) na sprawdzenie, czy kod działa poprawnie i jest faktycznie tańszy niż wyjątek. Wszystkim zainteresowanym tym tematem polecam 2 rozdziały (Programowanie asertywne i Kiedy używać wyjątków) z książki: Pragmatyczny programista.
ForceMagic,
1
Nie ma potrzeby linków. Użyj rozumu. Możesz zgłosić wyjątek, który nie zostanie przechwycony. To źle. Gorzej niż goto, ponieważ goto przynajmniej zawsze gdzieś idzie. Statyczne typowanie nie radzi sobie ze specyfikacjami wyjątków: nie ma rozsądnego systemu, w którym funkcje polimorficzne mogą mieć specyfikację wyjątku, ponieważ jest ona zależna od konkretnego wystąpienia zmiennej typu. Zwróć uwagę, że zostały one usunięte z nowego standardu C ++. Nowy model nosi nazwę „rozgraniczone kontynuacje”: en.wikipedia.org/wiki/Delimited_continuation
Yttrill
4

Jeśli twój problem pochodzi z twojego własnego złego kodu, lepiej jest użyć ASSERTów, aby się przed nim chronić. Obsługa wyjątków jest potrzebna do zidentyfikowania problemów, z którymi program nie może sobie poradzić, i poinformowania ich o użytkowniku, ponieważ użytkownik może sobie z nimi poradzić. Ale błędy w twoim programie nie są czymś, z czym użytkownik może sobie poradzić, więc awaria programu niewiele powie

Nie zgadzam się z tym aspektem przyjętej odpowiedzi . Asercja nie jest lepsza niż rzucenie wyjątku. Jeśli wyjątki były odpowiednie tylko w przypadku błędów czasu wykonywania (lub „problemów zewnętrznych”), po co std::logic_error?

Błąd logiczny jest prawie z definicji rodzajem warunku, który uniemożliwia kontynuowanie programu. Jeśli program jest konstrukcją logiczną, a warunek występuje poza domeną tej logiki, jak można go kontynuować? Zbierzcie dane wejściowe, póki możecie, i rzućcie wyjątek!

To nie tak, że nie ma wcześniejszej sztuki. std::vector, żeby wymienić tylko jeden, zgłasza wyjątek błędu logicznego, a mianowicie std::out_of_range. Jeśli korzystasz z biblioteki standardowej i nie masz programu obsługi najwyższego poziomu do wychwytywania standardowych wyjątków - choćby po to, aby wywołać what () i wyjść (3) - wtedy twoje programy podlegają nagłemu cichemu zakończeniu.

Makro asertujące jest znacznie słabszym strażnikiem. Nie ma powrotu do zdrowia. Chyba że nie uruchamiasz kompilacji debugowania, w którym to przypadku nie ma wykonania . Makro asertujące należy do epoki, w której obliczenia były o 6 rzędów wielkości wolniejsze niż obecnie. Jeśli masz kłopoty z testowaniem błędów logicznych, ale nie chcesz używać tego testu, gdy się liczy, w środowisku produkcyjnym, lepiej miej duże zaufanie do swojego kodu!

Biblioteka standardowa zapewnia wyjątki błędów logicznych i wykorzystuje je. Są tam z jakiegoś powodu: ponieważ występują błędy logiczne i są wyjątkowe. Tylko dlatego, że asercje funkcji C nie są powodem, aby polegać na takim prymitywnym (i prawdopodobnie bezużytecznym) mechanizmie, gdy wyjątek obsługuje zadanie o wiele lepiej.

James K. Lowden
źródło
3

Najlepiej do tego przeczytać

O obsłudze wyjątków mówi się wiele przez ostatnie półtorej dekady. Jednak pomimo ogólnego konsensusu co do tego, jak właściwie postępować z wyjątkami, nadal istnieje przepaść w stosowaniu. Niewłaściwa obsługa wyjątków jest łatwa do wykrycia, łatwa do uniknięcia i jest prostym miernikiem jakości kodu (i programisty). Wiem, że zasady absolutne wydają się być ograniczone lub przesadzone, ale generalnie nie powinieneś używać try / catch

http://codebetter.com/karlseguin/2010/01/25/don-t-use-try-catch/

Sushan M
źródło
3
Czy ten artykuł naprawdę nie próbuje powiedzieć, nie używaj try/ catch {}?
Craig McQueen,
2

Sprawdzanie wyjątku jest zawarte w kodzie, gdy istnieje możliwość uzyskania wyjątku w wyniku lub gdzieś pomiędzy problemem.

2. Użyj bloku try-catch tylko w tych przypadkach, w których jest to wymagane. Użycie każdego bloku try-catch powoduje dodatkowe sprawdzenie warunków, które z pewnością ogranicza optymalizację kodu.

Myślę, że _try_except jest prawidłową nazwą zmiennej ...

user550830
źródło
3
FYI, __try i __except są przeznaczone do obsługi wyjątków strukturalnych firmy Microsoft (SEH). msdn.microsoft.com/en-us/library/s58ftw19(v=vs.80).aspx
axw
0

Podstawowa różnica to:

  1. one sprawiają, że obsługa błędów jest dla Ciebie.
  2. jeden to ty robisz swoje.

    • Na przykład masz wyrażenie, które możesz zrobić 0 divide error. Korzystanie z try catch 1.pomoże Ci w przypadku wystąpienia błędu. Lub potrzebujesz if a==0 then..in2.

    • Jeśli nie spróbujesz złapać wyjątku, o którym nie sądzę, jest szybszy, jest to po prostu obejście, jeśli errorwystąpi, zostanie skierowany threwdo zewnętrznego modułu obsługi.

Podejmowanie się oznacza, że ​​problem nie idzie dalej, a w wielu przypadkach ma przewagę w szybkości, ale nie zawsze.

Sugestia: Po prostu radzisz sobie, kiedy jest to proste i logicznie uzasadnione.

pinichi
źródło
-3

Wielu purystów C / C ++ całkowicie odradza wyjątki. Główne zarzuty to:

  1. Jest powolny - oczywiście, że tak naprawdę nie jest „wolny”. Jednak w porównaniu do czystego C / C ++ jest sporo narzutów.
  2. Wprowadza błędy - jeśli nie obsługujesz poprawnie wyjątków, możesz przeoczyć kod czyszczący w funkcji, która zgłasza wyjątek.

Zamiast tego sprawdzaj zwracaną wartość / kod błędu za każdym razem, gdy wywołujesz funkcję.

speedplane
źródło
15
Nonsens. Po pierwsze, mówimy o C ++ - nie ma „C / C ++” i oczywiście programiści pracujący tylko w C czułby się niekomfortowo z wyjątkami. „czysty C / C ++” nie istnieje, a wyjątki są częścią „czystego C ++” - są w standardzie. Możesz napisać błędny kod obsługi wyjątków i możesz napisać błędny kod zwracający błędy lub błędny kod stanu błędu - scharakteryzowanie wyjątków jako generalnie bardziej podatnych na błędy jest mylące. Przepraszam, ale to jedna z najgorszych rad, jakie widziałem w SO
Tony Delroy,
1
Zaznacz mnie, jeśli chcesz, ale wychodząc ze środowiska C, mogę Ci powiedzieć, że ja i wielu innych osób w ogóle nie używam wyjątków.
szybowiec
4
1. Jest wolny tylko wtedy, gdy wyjątek jest faktycznie zgłaszany. A wyjątki nazywane są wyjątkami, ponieważ są rzucane tylko w wyjątkowych okolicznościach. Program wyrzucający ponad 9000 wyjątków na sekundę robi to źle. 2. Jeśli zadbasz, aby nie zarządzać pamięcią za pomocą czystego nowego usuwania, czyszczenie zostanie wykonane za Ciebie. Zarządzanie pamięcią w stylu C jest znacznie bardziej podatne na błędy niż wyjątki. 3. Sprawdzanie wartości zwracanej powoduje dodanie wielu dodatkowych wierszy kodu, co czyni go mniej czytelnym i trudniejszym w utrzymaniu.
Septagram
3
1) w zależności od kompilatora, może to nie być prawda. A wszystkie wyjątki dodają dużą ilość kodu bajtowego, zwiększając rozmiar pliku wykonywalnego i spowalniając działanie. 2) czyste nowe usuwanie jest używane przez cały czas, nie wszystko może być obiektem stosu. 3) sprawdzanie zwracanych wartości może obejmować więcej linii kodu, ale prawdopodobnie jest łatwiejsze w utrzymaniu. Możesz się z tym nie zgodzić, ale ogólnie przyjmuje się rozsądne przekonanie, że wyjątki C ++ są w wielu przypadkach złe. Spójrz na embedded-C ++ jako przykład.
speedplane
11 głosów przeciw i 8 głosów za. Myślę, że jest jasne, że chociaż ta opinia nie odzwierciedla ogólnego konsensusu, istnieje duża grupa programistów, którzy są zwolennikami wyjątków z powodów, które opisałem.
szybowiec