Utknąłem przy podejmowaniu decyzji, jak obsłużyć wyjątki w mojej aplikacji.
Wiele, jeśli moje problemy z wyjątkami wynikają z 1) dostępu do danych za pośrednictwem usługi zdalnej lub 2) deserializacji obiektu JSON. Niestety nie mogę zagwarantować sukcesu dla żadnego z tych zadań (przerwanie połączenia sieciowego, źle sformułowany obiekt JSON, który jest poza moją kontrolą).
W rezultacie, jeśli napotkam wyjątek, po prostu łapię go w funkcji i zwracam FALSE do dzwoniącego. Moja logika jest taka, że dzwoniącego naprawdę obchodzi tylko to, czy zadanie się powiodło, a nie dlaczego się nie powiodło.
Oto przykładowy kod (w języku JAVA) typowej metody)
public boolean doSomething(Object p_somthingToDoOn)
{
boolean result = false;
try{
// if dirty object then clean
doactualStuffOnObject(p_jsonObject);
//assume success (no exception thrown)
result = true;
}
catch(Exception Ex)
{
//don't care about exceptions
Ex.printStackTrace();
}
return result;
}
Myślę, że to podejście jest w porządku, ale jestem naprawdę ciekawy, jakie są najlepsze praktyki zarządzania wyjątkami (czy naprawdę powinienem bąbelkować wyjątek na całej wysokości stosu wywołań?).
Podsumowując kluczowe pytania:
- Czy można po prostu wyłapać wyjątki, ale nie podświetlać ich ani formalnie powiadamiać systemu (za pośrednictwem dziennika lub powiadomienia dla użytkownika)?
- Jakie sprawdzone metody są dostępne dla wyjątków, które nie powodują, że wszystko wymaga blokady try / catch?
Śledzenie / edycja
Dziękuję za wszystkie opinie, znalazłem doskonałe źródła dotyczące zarządzania wyjątkami w Internecie:
- Najlepsze praktyki dotyczące obsługi wyjątków | O'Reilly Media
- Najlepsze rozwiązania dotyczące obsługi wyjątków w .NET
- Sprawdzone metody: zarządzanie wyjątkami (artykuł wskazuje teraz kopię archive.org)
- Antywzory obsługujące wyjątki
Wydaje się, że zarządzanie wyjątkami jest jedną z tych rzeczy, które różnią się w zależności od kontekstu. Ale co najważniejsze, należy konsekwentnie zarządzać wyjątkami w systemie.
Dodatkowo uważaj na gnicie kodu poprzez nadmierne try / catch lub nie nadawanie wyjątku jego szacunku (wyjątkiem jest ostrzeżenie systemu, co jeszcze należy ostrzec?)
Jest to również dobry komentarz ze strony m3rLinEz .
Zgadzam się z Andersem Hejlsbergiem i Tobą, że większości dzwoniących zależy tylko na tym, czy operacja się powiedzie, czy nie.
Z tego komentarza nasuwa się kilka pytań, o których należy pomyśleć podczas radzenia sobie z wyjątkami:
- Jaki jest sens zgłaszania tego wyjątku?
- Jak to ma sens, aby sobie z tym poradzić?
- Czy dzwoniącego naprawdę obchodzi wyjątek, czy po prostu obchodzi go, czy połączenie się powiodło?
- Czy zmuszanie dzwoniącego do zarządzania potencjalnym wyjątkiem jest wdzięczne?
- Czy szanujesz idomy języka?
- Czy naprawdę musisz zwrócić flagę sukcesu, taką jak wartość logiczna? Zwracanie wartości logicznej (lub int) jest bardziej nastawieniem na język C niż w Javie (w Javie wystarczyłoby obsłużyć wyjątek).
- Postępuj zgodnie z konstrukcjami zarządzania błędami związanymi z językiem :)!
źródło
Odpowiedzi:
Wydaje mi się dziwne, że chcesz wychwycić wyjątki i przekształcić je w kody błędów. Jak myślisz, dlaczego dzwoniący wolałby kody błędów od wyjątków, skoro ten drugi jest domyślny zarówno w Javie, jak i C #?
Jeśli chodzi o Twoje pytania:
źródło
Zależy to od zastosowania i sytuacji. Jeśli budujesz komponent biblioteki, powinieneś zgrupować wyjątki, chociaż powinny one być opakowane, aby były kontekstowe z komponentem. Na przykład, jeśli budujesz bazę danych XML i powiedzmy, że używasz systemu plików do przechowywania danych, a do zabezpieczenia danych używasz uprawnień systemu plików. Nie chciałbyś wyświetlać wyjątku FileIOAccessDenied, ponieważ wycieka on z implementacji. Zamiast tego zawinąłbyś wyjątek i zgłosił błąd AccessDenied. Jest to szczególnie ważne, jeśli udostępniasz komponent stronom trzecim.
Jeśli chodzi o to, czy można przełknąć wyjątki. To zależy od twojego systemu. Jeśli Twoja aplikacja może obsłużyć przypadki awarii i nie ma korzyści z powiadomienia użytkownika, dlaczego się nie powiodło, to śmiało, chociaż zdecydowanie zalecam, abyś zarejestrował błąd. Zawsze uważałem, że frustrujące jest wezwanie do pomocy w rozwiązaniu problemu i stwierdzenie, że połykali wyjątek (lub zastępowali go i zamiast tego rzucali nowy bez ustawiania wewnętrznego wyjątku).
Generalnie stosuję następujące zasady:
Uważam, że następujący kod to zapach:
Taki kod nie ma sensu i nie powinien być uwzględniany.
źródło
// do something
zawiera jakiekolwiektry/finally
bloki wokół punktu, który rzuca,finally
bloki zostaną wykonane przedcatch
blokiem. Beztry/catch
wyjątku poleci na sam szczyt stosu bez wykonywania żadnychfinally
bloków. Pozwala to programowi obsługi najwyższego poziomu zdecydować, czy wykonaćfinally
bloki, czy nie .Chciałbym polecić inne dobre źródło na ten temat. To wywiad z wynalazcami C # i Java, odpowiednio Andersem Hejlsbergiem i Jamesem Goslingiem, na temat Java's Checked Exception.
Awaria i wyjątki
Na dole strony znajdują się również świetne zasoby.
Zgadzam się z Andersem Hejlsbergiem i Tobą, że większości dzwoniących zależy tylko na tym, czy operacja się powiedzie, czy nie.
EDYCJA: Dodano więcej szczegółów na temat konwergencji
źródło
FetchData
i zgłasza wyjątek nieoczekiwanego typu, nie ma możliwości sprawdzenia, czy ten wyjątek oznacza po prostu, że dane są niedostępne (w takim przypadku zdolność kodu do obejścia go bez niego „rozwiązałaby” problem) lub czy oznacza to, że procesor się pali i system powinien wykonać „wyłączenie awaryjne” przy pierwszej okazji. Wygląda na to, że pan Hejlsberg sugeruje, że kod powinien zakładać to pierwsze; być może jest to najlepsza możliwa strategia, biorąc pod uwagę istniejące hierarchie wyjątków, ale mimo to wydaje się nieprzyjemna.Zaznaczone wyjątki są ogólnie kontrowersyjną kwestią, aw Javie szczególnie (później postaram się znaleźć kilka przykładów dla tych, którzy są za i przeciw).
Ogólnie rzecz biorąc, obsługa wyjątków powinna obejmować te wytyczne, bez określonej kolejności:
printStackTrace()
ani tego nie robisz , istnieje szansa, że któryś z twoich użytkowników w końcu otrzyma jeden z tych śladów stosu i nie będzie miał dokładnie zerowej wiedzy, co z nim zrobić.Exception
, jest bardzo prawdopodobne, że połkniesz inne ważne wyjątki.Error
! , co oznacza: Nigdy nie łapajThrowable
s, ponieważError
s są podklasami tej ostatniej.Error
s to problemy, z którymi najprawdopodobniej nigdy nie będziesz w stanie sobie poradzić (np.OutOfMemory
inne problemy z maszyną JVM)Jeśli chodzi o Twój konkretny przypadek, upewnij się, że każdy klient wywołujący Twoją metodę otrzyma odpowiednią wartość zwracaną. Jeśli coś się nie powiedzie, metoda zwracająca wartość logiczną może zwrócić wartość false, ale upewnij się, że miejsca, które wywołujesz tę metodę, są w stanie to obsłużyć.
źródło
Powinieneś wychwytywać tylko wyjątki, z którymi możesz sobie poradzić. Na przykład, jeśli masz do czynienia z czytaniem przez sieć, a połączenie przekroczyło limit czasu i pojawi się wyjątek, możesz spróbować ponownie. Jednak jeśli czytasz przez sieć i otrzymujesz wyjątek IndexOutOfBounds, naprawdę nie możesz sobie z tym poradzić, ponieważ nie (cóż, w tym przypadku nie wiesz), co go spowodowało. Jeśli zamierzasz zwrócić false, -1 lub null, upewnij się, że dotyczy to określonych wyjątków. Nie chcę, aby biblioteka, której używam, zwracała wartość false podczas odczytu sieciowego, gdy zgłoszony wyjątek oznacza, że sterta zabraknie pamięci.
źródło
Wyjątkami są błędy, które nie są częścią normalnego wykonywania programu. W zależności od tego, co robi twój program i jakie są jego zastosowania (np. Edytor tekstu kontra monitor pracy serca), będziesz chciał robić różne rzeczy, gdy napotkasz wyjątek. Pracowałem z kodem, który używa wyjątków jako część normalnego wykonywania i zdecydowanie jest to zapach kodu.
Dawny.
Ten kod sprawia, że jestem barfem. IMO nie powinieneś odzyskiwać sprawności po wyjątkach, chyba że jest to krytyczny program. Jeśli rzucasz wyjątki, dzieje się źle.
źródło
Wszystko to wydaje się rozsądne i często w Twoim miejscu pracy mogą obowiązywać zasady. U nas zdefiniowaliśmy typy wyjątków:
SystemException
(niezaznaczone) iApplicationException
(zaznaczone).Uzgodniliśmy, że
SystemException
jest mało prawdopodobne, aby można je było odzyskać i zostaną rozwiązane raz na szczycie. Aby zapewnić dalszy kontekst, nasiSystemException
s są exteneded aby wskazać, gdzie wystąpił one npRepositoryException
,ServiceEception
itpApplicationException
mogą mieć znaczenie biznesowe, takie jakInsufficientFundsException
kod klienta i powinny być obsługiwane.Witam, ale konkretny przykład, trudno jest skomentować twoją implementację, ale nigdy nie użyłbym kodów zwrotnych, są to kwestia konserwacji. Możesz połknąć wyjątek, ale musisz zdecydować, dlaczego i zawsze rejestrować zdarzenie i ślad stosu. Wreszcie, ponieważ twoja metoda nie ma innego przetwarzania, jest dość zbędna (z wyjątkiem hermetyzacji?), Więc
doactualStuffOnObject(p_jsonObject);
może zwrócić wartość logiczną!źródło
Po zastanowieniu się i przyjrzeniu się kodowi wydaje mi się, że po prostu ponownie zgłaszasz wyjątek jako wartość logiczną. Możesz po prostu pozwolić metodzie przejść przez ten wyjątek (nie musisz go nawet przechwytywać) i zająć się nim w obiekcie wywołującym, ponieważ jest to miejsce, w którym ma to znaczenie. Jeśli wyjątek spowoduje, że obiekt wywołujący ponowi próbę wykonania tej funkcji, obiekt wywołujący powinien przechwytywać wyjątek.
Czasami może się zdarzyć, że napotkany wyjątek nie będzie miał sensu dla dzwoniącego (tj. Jest to wyjątek sieciowy), w takim przypadku należy umieścić go w wyjątku specyficznym dla domeny.
Jeśli z drugiej strony wyjątek sygnalizuje nieodwracalny błąd w twoim programie (tj. Ostatecznym skutkiem tego wyjątku będzie zakończenie programu), osobiście lubię to wyraźnie zaznaczyć, przechwytując go i rzucając wyjątek czasu wykonania.
źródło
Jeśli zamierzasz użyć wzorca kodu w swoim przykładzie, wywołaj go TryDoSomething i wyłap tylko określone wyjątki.
Rozważ również użycie filtru wyjątków podczas rejestrowania wyjątków w celach diagnostycznych. VB obsługuje języki filtrów wyjątków. Link do bloga Greggma zawiera implementację, której można używać z poziomu C #. Filtry wyjątków mają lepsze właściwości debugowania niż przechwytywanie i ponowne generowanie. W szczególności można zarejestrować problem w filtrze i pozwolić na dalsze propagowanie wyjątku. Ta metoda umożliwia dołączenie debugera JIT (Just in Time) do pełnego oryginalnego stosu. Ponowny rzut odcina stos w miejscu, w którym został ponownie wyrzucony.
Przypadki, w których TryXXXX ma sens, występują, gdy pakujesz funkcję strony trzeciej, która zgłasza przypadki, które nie są naprawdę wyjątkowe lub są proste do przetestowania bez wywołania funkcji. Przykładem może być coś takiego:
To, czy używasz wzorca takiego jak TryXXX, czy nie, jest bardziej kwestią stylu. Kwestia wyłapywania wszystkich wyjątków i ich połykania nie jest kwestią stylu. Upewnij się, że dozwolone są nieoczekiwane wyjątki!
źródło
Proponuję wziąć wskazówki z biblioteki standardowej dla języka, którego używasz. Nie mogę mówić w języku C #, ale spójrzmy na Javę.
Na przykład java.lang.reflect.Array ma
set
metodę statyczną :Sposób C byłby
... z wartością zwracaną będącą wskaźnikiem sukcesu. Ale nie jesteś już w świecie C.
Po uwzględnieniu wyjątków powinieneś zauważyć, że sprawia to, że kod jest prostszy i bardziej przejrzysty, przenosząc kod obsługi błędów z dala od podstawowej logiki. Staraj się mieć wiele instrukcji w jednym
try
bloku.Jak zauważyli inni - powinieneś być jak najbardziej szczegółowy w rodzaju wychwytywanego wyjątku.
źródło
Jeśli zamierzasz złapać wyjątek i zwrócić fałsz, powinien to być bardzo konkretny wyjątek. Nie robisz tego, łapiesz je wszystkie i zwracasz fałsz. Jeśli otrzymam wyjątek MyCarIsOnFireException, chcę się o nim natychmiast dowiedzieć! Reszta wyjątków może mnie nie obchodzić. Powinieneś więc mieć stos modułów obsługi wyjątków, które mówią „whoa whoa, coś tu jest nie tak” dla niektórych wyjątków (wyrzuć ponownie lub złap i wrzuć nowy wyjątek, który lepiej wyjaśnia, co się stało), a dla innych po prostu zwracać fałsz.
Jeśli jest to produkt, który będziesz uruchamiać, powinieneś gdzieś rejestrować te wyjątki, co pomoże ci dostroić rzeczy w przyszłości.
Edycja: Jeśli chodzi o kwestię pakowania wszystkiego w try / catch, myślę, że odpowiedź brzmi tak. Wyjątki powinny być tak rzadkie w twoim kodzie, że kod w bloku catch jest wykonywany tak rzadko, że w ogóle nie wpływa na wydajność. Wyjątkiem powinien być stan, w którym maszyna stanu zepsuła się i nie wie, co robić. Przynajmniej wyrzuć ponownie wyjątek, który wyjaśnia, co się działo w tym czasie i zawiera przechwycony wyjątek. „Wyjątek w metodzie doSomeStuff ()” nie jest zbyt pomocny dla nikogo, kto musi dowiedzieć się, dlaczego się zepsuł, gdy jesteś na wakacjach (lub w nowej pracy).
źródło
Moja strategia:
Jeśli oryginalna funkcja zwróciła wartość void , zmieniam ją, aby zwracała wartość bool . Jeśli wystąpił wyjątek / błąd, zwróć false , jeśli wszystko było w porządku, zwróć true .
Jeśli funkcja ma coś zwrócić, to w przypadku wystąpienia wyjątku / błędu zwraca wartość null , w przeciwnym razie pozycja zwrotna.
Zamiast bool ciąg może być zwrócony zawierający opis błędu.
W każdym przypadku przed zwróceniem czegokolwiek zarejestruj błąd.
źródło
Kilka doskonałych odpowiedzi. Chciałbym dodać, że jeśli skończysz z czymś takim, jak opublikowałeś, przynajmniej wydrukuj więcej niż ślad stosu. Powiedz, co robiłeś w tym czasie, i Ex.getMessage (), aby dać deweloperowi szansę walki.
źródło
Bloki try / catch tworzą drugi zestaw logiki osadzony na pierwszym (głównym) zestawie, jako takie są świetnym sposobem na usunięcie nieczytelnego, trudnego do debugowania kodu spaghetti.
Mimo to, używane rozsądnie, zdziałają cuda w czytelności, ale powinieneś po prostu przestrzegać dwóch prostych zasad:
używaj ich (oszczędnie) na niskim poziomie, aby przechwytywać problemy związane z obsługą bibliotek i przesyłać je z powrotem do głównego przepływu logicznego. Większość potrzebnej obsługi błędów powinna pochodzić z samego kodu, jako część samych danych. Po co stawiać specjalne warunki, jeśli zwracane dane nie są specjalne?
użyj jednej dużej procedury obsługi na wyższym poziomie, aby zarządzać dowolnymi lub wszystkimi dziwnymi warunkami pojawiającymi się w kodzie, które nie są przechwytywane na niskim poziomie. Zrób coś pożytecznego z błędami (dzienniki, restart, odzyskiwanie itp.).
Poza tymi dwoma typami obsługi błędów, cała reszta kodu w środku powinna być wolna i pozbawiona kodu try / catch i obiektów błędów. W ten sposób działa prosto i zgodnie z oczekiwaniami, bez względu na to, gdzie go używasz i co z nim zrobisz.
Paweł.
źródło
Może trochę się spóźniam z odpowiedzią, ale obsługa błędów jest czymś, co zawsze możemy zmienić i ewoluować w czasie. Jeśli chcesz poczytać coś więcej na ten temat, napisałem o tym na moim nowym blogu. http://taoofdevelopment.wordpress.com
Miłego kodowania.
źródło