Zwykle zgadzam się z większością ostrzeżeń dotyczących analizy kodu i staram się ich przestrzegać. Mam jednak trudniejszy czas z tym:
CA1031: Nie przechwytuj ogólnych typów wyjątków
Rozumiem uzasadnienie tej reguły. Ale w praktyce, jeśli chcę podjąć to samo działanie niezależnie od zgłoszonego wyjątku, dlaczego miałbym obsługiwać każde z nich konkretnie? Co więcej, jeśli obsłużę określone wyjątki, co się stanie, jeśli kod, który wywołuję, zmieni się, aby wygenerować nowy wyjątek w przyszłości? Teraz muszę zmienić kod, aby obsłużyć ten nowy wyjątek. Natomiast jeśli po prostu złapię, Exception
mój kod nie musi się zmienić.
Na przykład, jeśli Foo wywołuje Bar, a Foo musi przerwać przetwarzanie niezależnie od typu wyjątku zgłoszonego przez Bar, czy jest jakaś korzyść ze sprecyzowania rodzaju wyjątku, który łapię?
Może lepszy przykład:
public void Foo()
{
// Some logic here.
LogUtility.Log("some message");
}
public static void Log()
{
try
{
// Actual logging here.
}
catch (Exception ex)
{
// Eat it. Logging failures shouldn't stop us from processing.
}
}
Jeśli nie złapiesz tutaj ogólnego wyjątku, musisz złapać każdy możliwy wyjątek. Patrick ma rację, że OutOfMemoryException
nie powinno się z tym postępować w ten sposób. Co jeśli chcę zignorować każdy wyjątek, ale OutOfMemoryException
?
źródło
OutOfMemoryException
? Taki sam kod obsługi jak wszystko inne?OutOfMemoryError
oddzielny odException
drzewa dziedziczenia z tego właśnie powoduOdpowiedzi:
Zasady te są ogólnie dobrym pomysłem i dlatego należy ich przestrzegać.
Pamiętaj jednak, że są to ogólne zasady. Nie obejmują wszystkich sytuacji. Obejmują najczęstsze sytuacje. Jeśli masz konkretną sytuację i możesz argumentować, że twoja technika jest lepsza (i powinieneś być w stanie napisać komentarz w kodzie, aby wyrazić swój argument za to), to zrób to (a następnie sprawdź go).
Po przeciwnej stronie argumentu.
Nie uważam twojego powyższego przykładu za dobrą sytuację do tego. Jeśli system rejestrowania zawiedzie (prawdopodobnie rejestruje jakiś inny wyjątek), prawdopodobnie nie chcę, aby aplikacja była kontynuowana. Wyjdź i wydrukuj wyjątek na wyjściu, aby użytkownik mógł zobaczyć, co się stało.
źródło
Tak, łapanie ogólnych wyjątków to zła rzecz. Wyjątek zwykle oznacza, że program nie może zrobić tego, o co go poprosiłeś.
Istnieje kilka typów wyjątków, które mogłyby obsługiwać:
Aha, i jako ogólna zasada: jeśli nie wiesz, co zrobić z wyjątkiem, jeśli go złapiesz, lepiej po prostu szybko zawieść (przekaż wyjątek dzwoniącemu i pozwól mu to obsłużyć)
źródło
Najwyższa zewnętrzna pętla powinna mieć jedną z nich, aby wydrukować wszystko, co może, a następnie umrzeć straszliwą, gwałtowną i SZKODLIWĄ śmiercią (ponieważ to nie powinno się zdarzyć i ktoś musi to usłyszeć).
W przeciwnym razie na ogół powinieneś być bardzo ostrożny, ponieważ najprawdopodobniej nie przewidziałeś wszystkiego, co może się zdarzyć w tym miejscu, a zatem najprawdopodobniej nie potraktujesz go poprawnie. Bądź tak dokładny, jak to możliwe, aby złapać tylko tych, o których wiesz, że się zdarzy, i pozwól tym, których wcześniej nie widziałeś, wznieść się na wyżej wspomnianą głośną śmierć.
źródło
Nie jest tak, że jest źle, tylko że konkretne połowy są lepsze. Kiedy jesteś konkretny, oznacza to, że faktycznie, bardziej konkretnie, rozumiesz, co robi twoja aplikacja i masz nad nią większą kontrolę. Ogólnie rzecz biorąc, jeśli natkniesz się na sytuację, w której po prostu złapiesz wyjątek, zaloguj go i kontynuuj, prawdopodobnie i tak dzieje się prawdopodobnie kilka złych rzeczy. Jeśli wychwytujesz wyjątki, o których wiesz, że blok kodu lub metoda mogą generować, istnieje większe prawdopodobieństwo, że możesz je odzyskać zamiast rejestrować i mieć nadzieję na najlepsze.
źródło
Dwie możliwości nie wykluczają się wzajemnie.
W idealnej sytuacji wychwyciłbyś wszystkie możliwe typy wyjątków, które twoja metoda mogłaby wygenerować, obsłużyłby je dla poszczególnych wyjątków, a na końcu dodałaby ogólną
catch
klauzulę, aby wychwycić wszelkie przyszłe lub nieznane wyjątki. W ten sposób uzyskasz to, co najlepsze z obu światów.Pamiętaj, że aby uchwycić bardziej szczegółowe wyjątki, musisz je ustawić na pierwszym miejscu.
Edycja: Z powodów podanych w komentarzach dodano powtórkę w ostatnim połowie.
źródło
Zastanawiałem się nad tym niedawno, a mój wstępny wniosek jest taki, że samo pytanie powstaje, ponieważ hierarchia wyjątków .NET jest poważnie zawalona.
Weźmy na przykład niski,
ArgumentNullException
który może być rozsądnym kandydatem na wyjątek, którego nie chcesz wychwytywać, ponieważ zwykle wskazuje on na błąd w kodzie, a nie na uzasadniony błąd w czasie wykonywania. O tak. Tak samoNullReferenceException
, z wyjątkiem tego, żeNullReferenceException
pochodziSystemException
bezpośrednio, więc nie ma segmentu, w którym można umieścić wszystkie „błędy logiczne”, aby je złapać (lub nie złapać).Potem jest poważna robota IMNSHO polegająca naKiedy dostajeszSEHException
wyprowadzeniu (viaExternalException
),SystemException
a tym samym uczynieniu go „normalnym”.SystemException
SEHException
, chcesz napisać zrzut i zakończyć tak szybko, jak to możliwe - i zaczynając od .NET 4 przynajmniej niektóre Wyjątki SEH są uważane za wyjątki dotyczące stanu skorumpowanego , które nie zostaną wyłapane. To dobra rzecz i fakt, żeCA1031
regułaczyni jąjeszcze bardziej bezużyteczną, ponieważ teraz leniwi icatch (Exception)
tak nie złapią tych najgorszych typów.Następnie wydaje się, że inne rzeczy z frameworka wywodzą się raczej niekonsekwentnie albo
Exception
bezpośrednio, albo za pośrednictwemSystemException
, próbując pogrupować klauzule catch według czegoś w rodzaju ważności.Jest taki kawałek
C#
sławy pana Lipperta , zatytułowany Vexing Exception , w którym wyróżnia kilka użytecznych kategoryzacji wyjątków: Można argumentować, że chcesz złapać tylko te „egzogeniczne”, z wyjątkiem…C#
języka i wyglądu .NET wyjątki ramowe uniemożliwiają „złapanie tylko egzogennych” w zwięzły sposób. (i np. bardzo dobrzeOutOfMemoryException
może być całkowicie normalnym, możliwym do odzyskania błędem dla interfejsu API, który musi przydzielić bufory, które są w jakiś sposób duże)Najważniejsze dla mnie jest to, że sposób
C#
działania bloków przechwytujących i sposób projektowania hierarchii wyjątków Framework, zasadaCA1031
jest całkowicie bezużyteczna . To udaje się pomóc w wydaniu głównym „nie połykać wyjątki”, ale połykania wyjątków ma zero do czynienia z tym, co można złapać, ale z tego, co wtedy zrobić:Istnieją co najmniej 4 sposoby legalnej obsługi złapanego
Exception
iCA1031
wydaje się, że powierzchownie obsługuje tylko jeden z nich (mianowicie przypadek ponownego rzucenia).Na marginesie, istnieje funkcja C # 6 o nazwie Filtry wyjątków, która sprawi, że będą
CA1031
nieco bardziej poprawne, ponieważ od tego momentu będziesz w stanie poprawnie, poprawnie, odpowiednio odfiltrować wyjątki, które chcesz złapać, i jest mniej powodów, aby pisać niefiltrowanecatch (Exception)
.źródło
OutOfMemoryException
polega na tym, że nie ma dobrego sposobu, aby kod był pewien, że „tylko” oznacza awarię określonego przydziału, dla którego przygotowano błąd. Możliwe, że został zgłoszony inny poważniejszy wyjątek iOutOfMemoryException
wystąpił podczas odwijania stosu od tego innego wyjątku. Java mogła spóźnić się na imprezę ze swoimi „próbami z zasobami”, ale obsługuje wyjątki podczas rozwijania stosu nieco lepiej niż .NET.finally
bloki przed wyjątkami, których realistycznie można się spodziewać w taki sposób, że zarówno oryginalne, jak i nowe wyjątki zostaną zarejestrowane. Niestety robienie tego często wymaga alokacji pamięci, co może powodować własne awarie.Obsługa wyjątków Pokemon (muszę złapać je wszystkie!) Z pewnością nie zawsze jest zła. Udostępniając metodę klientowi, a zwłaszcza użytkownikowi końcowemu, często lepiej jest złapać wszystko i wszystko, niż spowodować awarię i nagrywanie aplikacji.
Ogólnie jednak należy ich unikać tam, gdzie to możliwe. O ile nie możesz podjąć konkretnych działań w zależności od rodzaju wyjątku, lepiej nie obsługiwać go i pozwolić, aby wyjątek się rozwinął, a następnie połknąć wyjątek lub nieprawidłowo go obsłużyć.
Wystarczy popatrzeć na to SO odpowiedzieć więcej czytania.
źródło
LoadDocument()
do niemożliwej jest zidentyfikowanie wszystkich rzeczy, które mogą pójść nie tak, ale 99% wyjątków, które można by zgodzić, oznacza po prostu: „Nie można interpretować zawartości pliku o podanej nazwie jako dokument; załatw go. ” Jeśli ktoś spróbuje otworzyć coś, co nie jest prawidłowym plikiem dokumentu, nie powinno to spowodować awarii aplikacji i zabić żadnych innych otwartych dokumentów. Obsługa błędów pokemonów w takich przypadkach jest brzydka, ale nie znam dobrych alternatyw.Złapanie ogólnego wyjątku jest złe, ponieważ pozostawia program w nieokreślonym stanie. Nie wiesz, gdzie coś poszło nie tak, więc nie wiesz, co twój program faktycznie zrobił lub czego nie zrobił.
Pozwoliłbym złapać wszystko podczas zamykania programu. Tak długo, jak możesz to wyczyścić w porządku. Nic tak irytującego jak program, który zamykasz, który po prostu wyświetla okno dialogowe błędu, które nic nie robi, tylko siedzieć tam, nie odchodząc i uniemożliwiając zamknięcie komputera.
W środowisku rozproszonym metoda dziennika może się nie udać: wyłapanie ogólnego wyjątku może oznaczać, że program nadal blokuje plik dziennika, uniemożliwiając innym użytkownikom tworzenie dzienników.
źródło
Jak powiedzieli inni, naprawdę trudno jest (jeśli nie niemożliwe) wyobrazić sobie jakąś akcję, którą chciałbyś podjąć niezależnie od zgłoszonego wyjątku. Tylko jeden z przykładów są sytuacje, w których stan Program został uszkodzony i dowolne dalsze przetwarzanie może prowadzić do problemów (jest to uzasadnienie
Environment.FailFast
).W przypadku kodu hobby dobrze jest go złapać
Exception
, ale w przypadku kodu klasy profesjonalnej wprowadzenie nowego typu wyjątku powinno być traktowane z takim samym szacunkiem, jak zmiana podpisu metody, tj. Być traktowane jako zmiana przełomowa. Jeśli zasubskrybujesz ten punkt widzenia, natychmiast stanie się oczywiste, że powrót do zmiany (lub weryfikacji) kodu klienta jest jedynym prawidłowym działaniem.Jasne, bo nie będziesz wychwytywał tylko wyjątków
Bar
. Będą również wyjątki, które klienci Bar, a nawet środowisko wykonawcze mogą zgłaszać w czasie, któryBar
jest na stosie wywołań. Dobrze napisanyBar
powinien zdefiniować własny typ wyjątku, jeśli to konieczne, aby osoby dzwoniące mogły dokładnie wychwycić wyjątki emitowane przez siebie.IMHO to zły sposób myślenia o obsłudze wyjątków. Powinieneś działać na białych listach (łapanie typów wyjątków A i B), a nie na czarnych listach (łap wszystkie wyjątki oprócz X).
źródło
Może . Istnieją wyjątki od każdej reguły i żadnej zasady nie należy bezwzględnie przestrzegać. Przykład może być jednym z przypadków, w których sensowne jest połknięcie wszystkich wyjątków. Na przykład, jeśli chcesz dodać śledzenie do krytycznego systemu produkcyjnego i chcesz mieć absolutną pewność, że zmiany nie zakłócają głównego zadania aplikacji.
Jednakże , należy dokładnie przemyśleć temat możliwych przyczyn awarii, zanim zdecydujesz się cicho zignorować je wszystkie. Na przykład, co jeśli przyczyną wyjątku jest:
Czy nie chcesz natychmiast otrzymywać powiadomień o tym problemie, aby go naprawić? Połknięcie wyjątku oznacza, że nigdy nie wiesz, że coś poszło nie tak.
Niektóre problemy (takie jak dysk jest pełny) mogą również powodować awarie innych części aplikacji - ale ta awaria nie jest teraz rejestrowana, więc nigdy nie wiadomo!
źródło
Chciałbym zająć się tym z logicznej perspektywy, a nie technicznej,
Cóż, ktoś musiałby sobie z tym poradzić. To jest pomysł. Autorzy kodu bibliotecznego byliby ostrożni w dodawaniu nowych typów wyjątków, ponieważ ponieważ może to zepsuć klientów, nie powinieneś się z tym często spotykać.
Twoje pytanie brzmi: „Co, jeśli nie obchodzi mnie, co poszło nie tak? Czy naprawdę muszę się męczyć, aby dowiedzieć się, co to było?”
Oto część piękna: nie, nie.
„Czy mogę po prostu spojrzeć w drugą stronę i sprawić, by cokolwiek paskudnego wyskoczyło automatycznie pod dywan i skończyło się na tym?”
Nie, to też tak nie działa.
Chodzi o to, że zbiór możliwych wyjątków jest zawsze większy niż zbiór, którego oczekujesz i interesuje Cię kontekst małego lokalnego problemu. Radzisz sobie z tymi, których spodziewasz się, a jeśli wydarzy się coś nieoczekiwanego, pozostawiasz to handlerom wyższego poziomu, zamiast je połykać. Jeśli nie obchodzi Cię wyjątek, którego się nie spodziewałeś, zakładasz się, że ktoś podniesie stos wywołań, a zabicie wyjątku przed dotarciem do niego byłoby sabotażem.
„Ale… ale… to jeden z tych innych wyjątków, na których mi nie zależy, może spowodować, że moje zadanie się nie powiedzie!”
Tak. Ale zawsze będą ważniejsze niż te obsługiwane lokalnie. Jak alarm przeciwpożarowy lub szef mówi ci, abyś przestał robić i wybierał pilniejsze zadanie, które się pojawiło.
źródło
Powinieneś wychwycić ogólne wyjątki na najwyższym poziomie każdego procesu i obsłużyć go, zgłaszając błąd tak dobrze, jak to możliwe, a następnie kończąc proces.
Nie należy wychwytywać ogólnych wyjątków i próbować kontynuować wykonywanie.
źródło