Zawsze uważałem, że jeśli metoda może zgłosić wyjątek, nierozsądnie jest nie chronić tego wywołania za pomocą znaczącego bloku try.
Właśnie napisałem: „ ZAWSZE należy zawijać połączenia, które mogą próbować łapać bloki. „na to pytanie i powiedziano mi, że była to„ wyjątkowo zła rada ”- chciałbym zrozumieć, dlaczego.
Odpowiedzi:
Metoda powinna wychwycić wyjątek tylko wtedy, gdy może sobie z tym poradzić w rozsądny sposób.
W przeciwnym razie przekaż go w górę, mając nadzieję, że metoda znajdująca się wyżej w stosie wywołań ma sens.
Jak zauważyli inni, dobrą praktyką jest posiadanie nieobsługiwanego modułu obsługi wyjątków (z rejestrowaniem) na najwyższym poziomie stosu wywołań, aby zapewnić rejestrowanie błędów krytycznych.
źródło
try
bloki wiążą się z kosztami (pod względem generowanego kodu) . Dobra dyskusja w „Effective C ++” Scotta Meyersa.try
bloki są wolne w każdym nowoczesnym kompilatorze C, informacja ta jest opatrzona datą Nick. Nie zgadzam się również z posiadaniem modułu obsługi wyjątków najwyższego poziomu, ponieważ tracisz informacje o lokalizacji (faktyczne miejsce, w którym instrukcja się nie powiodła).terminate
) . To bardziej mechanizm bezpieczeństwa. Ponadtotry/catch
są mniej lub bardziej bezpłatne, jeśli nie ma wyjątku. Kiedy jest jedna rozmnażanie, zużywa czas za każdym razem, gdy jest rzucana i łapana, więc łańcuchtry/catch
tego jedynego rzutu nie jest bezkosztowy.Jak stwierdził Mitch i inni , nie powinieneś wychwytywać wyjątku, którego nie planujesz w żaden sposób obsługiwać. Podczas projektowania należy rozważyć sposób, w jaki aplikacja będzie systematycznie obsługiwać wyjątki. Zwykle prowadzi to do tego, że warstwy obsługi błędów oparte na abstrakcjach - na przykład obsługujesz wszystkie błędy związane z SQL w kodzie dostępu do danych, dzięki czemu część aplikacji, która wchodzi w interakcję z obiektami domeny, nie jest narażona na fakt, że istnieje gdzieś jest DB pod maską.
Istnieje kilka powiązanych zapachów kodu, których zdecydowanie chcesz uniknąć oprócz zapachu „łap wszystko wszędzie” .
„catch, log, rethrow” : jeśli chcesz rejestrować na podstawie zakresu, napisz klasę, która emituje instrukcję logu w swoim destruktorze, gdy stos jest rozwijany z powodu wyjątku (ala
std::uncaught_exception()
). Wszystko, co musisz zrobić, to zadeklarować instancję rejestrowania w zakresie, który Cię interesuje, i, voila, masz rejestrację i nie ma potrzebytry
/catch
logiki.„złap, rzuć przetłumaczone” : zwykle oznacza to problem z abstrakcją. O ile nie wdrażasz rozwiązania stowarzyszonego, w którym tłumaczysz kilka konkretnych wyjątków na jeden bardziej ogólny, prawdopodobnie masz niepotrzebną warstwę abstrakcji ... i nie mów, że „może potrzebuję go jutro” .
„złap, posprzątaj, wyrzuć” : to jedna z moich petów. Jeśli widzisz dużo tego, powinieneś zastosować techniki pozyskiwania zasobów to techniki inicjalizacji i umieścić część czyszczenia w destruktorze instancji obiektu janitor .
Uważam kod zaśmiecony
try
/catch
blokami za dobry cel do przeglądania i refaktoryzacji kodu. Wskazuje to, że obsługa wyjątków nie jest dobrze zrozumiana lub kod stał się amba i wymaga poważnego refaktoryzacji.źródło
Logger
klasy podobnej dolog4j.Logger
tej, która zawiera identyfikator wątku w każdym wierszu dziennika i wysłałem ostrzeżenie w destruktorze, gdy wyjątek był aktywny.Ponieważ następne pytanie brzmi: „Złapałem wyjątek, co mam teraz zrobić?” Co zrobisz? Jeśli nic nie zrobisz - to ukrywanie błędów, a program może „po prostu nie działać” bez szansy na znalezienie tego, co się stało. Musisz zrozumieć, co dokładnie zrobisz, gdy złapiesz wyjątek i złap tylko, jeśli wiesz.
źródło
Nie musisz pokrywać każdego bloku try-catch, ponieważ try-catch może nadal wychwycić nieobsługiwane wyjątki zgłaszane w funkcjach w dalszej części stosu wywołań. Zamiast więc wypróbowywać każdą funkcję, możesz mieć ją na najwyższym poziomie logiki aplikacji. Na przykład, może istnieć procedura
SaveDocument()
najwyższego poziomu, która wywołuje wiele metod, które wywołują inne metody itp. Te pod-metody nie potrzebują własnych try-SaveDocument()
catch , ponieważ jeśli wyrzucą, nadal będą łapane przez catch.Jest to przydatne z trzech powodów: jest to przydatne, ponieważ masz jedno miejsce do zgłoszenia błędu:
SaveDocument()
blok (y) catch. Nie ma potrzeby powtarzania tego we wszystkich pod-metodach, ai tak chcesz: jedno miejsce, aby dać użytkownikowi użyteczną diagnostykę na temat czegoś, co poszło nie tak.Po drugie, zapis jest anulowany za każdym razem, gdy zostanie zgłoszony wyjątek. Z każdym sub-metody try wzrok, jeśli jest wyjątek, można dostać się do bloku catch tej metody jest, liści wykonania funkcja, a to prowadzi przez
SaveDocument()
. Jeśli coś już poszło nie tak, prawdopodobnie chcesz się na tym zatrzymać.Po trzecie, wszystkie twoje pod-metody mogą zakładać, że każde połączenie zakończy się powodzeniem . Jeśli wywołanie nie powiedzie się, wykonanie przeskoczy do bloku catch i kolejny kod nigdy nie zostanie wykonany. Może to uczynić twój kod znacznie czystszym. Oto na przykład kody błędów:
Oto, jak można to napisać z wyjątkami:
Teraz jest o wiele wyraźniej, co się dzieje.
Uwaga: bezpieczny kod wyjątku może być trudniejszy do napisania na inne sposoby: nie chcesz przeciekać pamięci, jeśli zostanie zgłoszony wyjątek. Upewnij się, że wiesz o RAII , kontenerach STL, inteligentnych wskaźnikach i innych obiektach, które zwalniają swoje zasoby w destruktorach, ponieważ obiekty są zawsze niszczone przed wyjątkami.
źródło
try
-catch
bloki że próba flagą każdą nieco innej permutacji jakiegoś błędu z nieco innej wiadomości, podczas gdy w rzeczywistości wszystkie one powinny zakończyć samo: transakcja lub programu awarii i wyjścia! Jeśli zdarzy się wyjątkowa awaria, zakładam, że większość użytkowników chce po prostu uratować to, co może, lub przynajmniej zostać w spokoju, bez konieczności radzenia sobie z 10 poziomami wiadomości na ten temat.Herb Sutter pisał o tym problemie tutaj . Na pewno warto przeczytać.
Teaser:
źródło
Jak stwierdzono w innych odpowiedziach, powinieneś wychwycić wyjątek tylko wtedy, gdy możesz zrobić z nim jakąś sensowną obsługę błędów.
Na przykład w pytaniu, które zrodziło twoje pytanie, pytający pyta, czy bezpiecznie można zignorować wyjątki
lexical_cast
od liczby całkowitej do ciągu. Taka obsada nigdy nie powinna zawieść. Jeśli się nie powiedzie, coś poszło nie tak w programie. Co możesz zrobić, aby odzyskać w tej sytuacji? Prawdopodobnie najlepiej jest pozwolić programowi umrzeć, ponieważ jest on w stanie, któremu nie można ufać. Więc nie obsłużenie wyjątku może być najbezpieczniejszym rozwiązaniem.źródło
Jeśli zawsze obsłużysz wyjątki natychmiast w obiekcie wywołującym metodę, która może zgłosić wyjątek, wyjątki stają się bezużyteczne i lepiej użyć kodów błędów.
Chodzi o to, że wyjątki nie muszą być obsługiwane w każdej metodzie w łańcuchu połączeń.
źródło
Najlepsza rada, jaką słyszałem, to że zawsze powinieneś wychwytywać wyjątki tylko w punktach, w których możesz rozsądnie zrobić coś z wyjątkowym stanem, i że „łapanie, rejestrowanie i uwalnianie” nie jest dobrą strategią (choć czasami nieuniknioną w bibliotekach).
źródło
Zgadzam się z podstawowym kierunkiem twojego pytania, aby obsłużyć jak najwięcej wyjątków na najniższym poziomie.
Niektóre z istniejących odpowiedzi brzmią: „Nie musisz obsługiwać wyjątku. Ktoś inny zrobi to na stosie”. Z mojego doświadczenia jest to kiepska wymówka, aby nie myśleć o obsłudze wyjątków w aktualnie opracowanym fragmencie kodu, czyniąc wyjątek obsługą problemu innej osoby lub później.
Problem ten dramatycznie narasta w rozwoju rozproszonym, w którym może być konieczne wywołanie metody wdrożonej przez współpracownika. Następnie musisz sprawdzić zagnieżdżony łańcuch wywołań metod, aby dowiedzieć się, dlaczego on / ona rzuca na ciebie jakiś wyjątek, który można było rozwiązać znacznie łatwiej przy najgłębiej zagnieżdżonej metodzie.
źródło
Rada, którą dał mi kiedyś mój profesor informatyki, brzmiała: „Używaj bloków Try i Catch tylko wtedy, gdy nie można poradzić sobie z błędem przy użyciu standardowych środków”.
Jako przykład powiedział nam, że jeśli program napotka poważny problem w miejscu, w którym nie można zrobić czegoś takiego:
Następnie powinieneś użyć try, catch bloki. Chociaż do obsługi tego można użyć wyjątków, generalnie nie jest to zalecane, ponieważ wyjątki są kosztowne pod względem wydajności.
źródło
Jeśli chcesz przetestować wynik każdej funkcji, użyj kodów powrotu.
Celem wyjątków jest częste testowanie wyników. Chodzi o to, aby oddzielić bardziej wyjątkowe (nietypowe, rzadsze) warunki od zwykłego kodu. Dzięki temu zwykły kod jest czystszy i prostszy - ale nadal jest w stanie poradzić sobie z tymi wyjątkowymi warunkami.
W dobrze zaprojektowanym kodzie głębsze funkcje mogą rzucać, a wyższe funkcje mogą przechwytywać. Kluczem jest jednak to, że wiele funkcji „pomiędzy” będzie w ogóle wolnych od obciążeń związanych z obsługą wyjątkowych warunków. Muszą tylko być „wyjątkowo bezpieczne”, co nie oznacza, że muszą złapać.
źródło
Chciałbym dodać do tej dyskusji, że od C ++ 11 ma to sens, pod warunkiem, że każdy
catch
blokrethrow
jest wyjątkiem aż do momentu, w którym można / należy go obsłużyć. W ten sposób można wygenerować ślad zwrotny . Dlatego uważam, że poprzednie opinie są częściowo nieaktualne.Użyj
std::nested_exception
istd::throw_with_nested
Jest to opisane na StackOverflow tutaj i tutaj , jak to osiągnąć.
Ponieważ możesz to zrobić z dowolną pochodną klasą wyjątku, możesz dodać wiele informacji do takiego śladu! Możesz także spojrzeć na moją MWE na GitHub , gdzie ślad może wyglądać mniej więcej tak:
źródło
Dano mi „możliwość” uratowania kilku projektów, a kadra kierownicza zastąpiła cały zespół programistów, ponieważ aplikacja zawierała zbyt wiele błędów, a użytkownicy byli zmęczeni problemami i rozbieganiem się. Wszystkie te podstawy kodu miały scentralizowane zarządzanie błędami na poziomie aplikacji, jak opisuje to najlepsza głosowana odpowiedź. Jeśli ta odpowiedź jest najlepszą praktyką, dlaczego nie zadziałała i nie pozwoliła poprzedniemu zespołowi programistów rozwiązać problemów? Może czasem to nie działa? Powyższe odpowiedzi nie wspominają, jak długo deweloperzy spędzają na rozwiązywaniu pojedynczych problemów. Jeśli kluczową miarą jest czas na rozwiązanie problemów, lepszym rozwiązaniem jest kodowanie przy użyciu bloków try..catch.
Jak mój zespół naprawił problemy bez znaczącej zmiany interfejsu? Proste, każda metoda została oprzyrządowana z blokadą try..catch, a wszystko było rejestrowane w punkcie błędu z nazwą metody, wartości parametrów metody połączonych w ciąg przekazywany wraz z komunikatem o błędzie, komunikacie o błędzie, nazwie aplikacji, dacie, i wersja. Dzięki tym informacjom programiści mogą uruchamiać analizy błędów, aby zidentyfikować wyjątek, który występuje najczęściej! Lub przestrzeń nazw o największej liczbie błędów. Może także sprawdzić, czy błąd występujący w module jest poprawnie obsługiwany i nie jest spowodowany wieloma przyczynami.
Inną zaletą tego rozwiązania jest to, że programiści mogą ustawić jeden punkt przerwania w metodzie rejestrowania błędów, a jednym punktem przerwania i jednym kliknięciem przycisku debugowania „step out” są w metodzie, która zakończyła się niepowodzeniem z pełnym dostępem do faktycznego obiekty w punkcie awarii, wygodnie dostępne w bezpośrednim oknie. Ułatwia to debugowanie i umożliwia przeciąganie wykonania z powrotem na początek metody w celu zduplikowania problemu w celu znalezienia dokładnej linii. Czy scentralizowana obsługa wyjątków pozwala deweloperowi zreplikować wyjątek w 30 sekund? Nie.
Stwierdzenie „Metoda powinna wychwycić wyjątek tylko wtedy, gdy może sobie z tym poradzić w rozsądny sposób”. Oznacza to, że programiści mogą przewidzieć lub napotkają każdy błąd, który może wystąpić przed wydaniem. Gdyby tak było na najwyższym poziomie, obsługa wyjątków aplikacji nie byłaby potrzebna i nie byłoby rynku elastycznego wyszukiwania i przechowywania danych.
Takie podejście pozwala również programistom znajdować i naprawiać sporadyczne problemy w produkcji! Czy chcesz debugować bez debugera w produkcji? A może wolisz odbierać połączenia i otrzymywać e-maile od zdenerwowanych użytkowników? Pozwala to naprawić problemy, zanim ktokolwiek się dowie, bez konieczności wysyłania wiadomości e-mail, wiadomości błyskawicznych lub usługi Slack ze wsparciem, ponieważ wszystko, co jest potrzebne do rozwiązania problemu, znajduje się właśnie tam. 95% problemów nigdy nie musi być powielanych.
Aby działać poprawnie, należy go połączyć ze scentralizowanym rejestrowaniem, które może przechwytywać przestrzeń nazw / moduł, nazwę klasy, metodę, dane wejściowe oraz komunikat o błędzie i przechowywać w bazie danych, aby można je było agregować w celu wyróżnienia, która metoda najbardziej zawodzi, dzięki czemu może być naprawione jako pierwsze.
Czasami programiści wybierają wyjątki w górę stosu z bloku catch, ale takie podejście jest 100 razy wolniejsze niż normalny kod, który nie rzuca. Preferowane jest przechwytywanie i zwalnianie z rejestrowaniem.
Technikę tę wykorzystano do szybkiego ustabilizowania aplikacji, która co godzinę nie działała u większości użytkowników w firmie z listy Fortune 500 opracowanej przez 12 deweloperów w ciągu 2 lat. Korzystając z tego 3000 różnych wyjątków zidentyfikowano, naprawiono, przetestowano i wdrożono w ciągu 4 miesięcy. Średnio poprawia się to co 15 minut średnio przez 4 miesiące.
Zgadzam się, że nie jest fajnie wpisywać wszystkiego, co potrzebne do instrumentowania kodu i wolę nie patrzeć na powtarzalny kod, ale dodanie 4 linii kodu do każdej metody jest tego warte na dłuższą metę.
źródło
Oprócz powyższej porady osobiście używam try + catch + throw; z następującego powodu:
źródło
Czuję się zmuszony dodać kolejną odpowiedź, chociaż odpowiedź Mike'a Wheata całkiem dobrze podsumowuje główne punkty. Myślę o tym w ten sposób. Kiedy masz metody, które robią wiele rzeczy, zwielokrotniasz złożoność, nie dodając jej.
Innymi słowy, metoda zawinięta w catch catch ma dwa możliwe wyniki. Masz wynik niebędący wyjątkiem i wynik wyjątku. Kiedy masz do czynienia z wieloma metodami, to wykładniczo wysadza w powietrze nie do zrozumienia.
Wykładniczo, ponieważ jeśli każda metoda rozgałęzia się na dwa różne sposoby, to za każdym razem, gdy wywołujesz inną metodę, poprawiasz poprzednią liczbę potencjalnych wyników. Do czasu wywołania pięciu metod masz co najmniej 256 możliwych wyników. Porównaj to z brakiem wykonywania try / catch w każdej metodzie, a masz tylko jedną ścieżkę do naśladowania.
Tak po prostu na to patrzę. Możesz mieć pokusę, aby argumentować, że każdy rodzaj rozgałęzienia robi to samo, ale try / catch są szczególnym przypadkiem, ponieważ stan aplikacji w zasadzie staje się niezdefiniowany.
Krótko mówiąc, try / catch sprawia, że kod jest trudniejszy do zrozumienia.
źródło
Nie musisz ukrywać każdej części kodu wewnątrz
try-catch
. Głównym zastosowaniem tegotry-catch
bloku jest obsługa błędów i występowanie błędów / wyjątków w twoim programie. Niektóre użycietry-catch
-try-catch
bloku.źródło
try
/catch
jest całkowicie oddzielny / ortogonalny. Jeśli chcą Państwo usunąć obiekty w mniejszym zakresie, można po prostu otworzyć nowy{ Block likeThis; /* <- that object is destroyed here -> */ }
- nie ma potrzeby, by zakończyć to wtry
/catch
chyba, że rzeczywiście trzebacatch
niczego, oczywiście.