W jednym z naszych projektów znalazłem taki kod:
SomeClass QueryServer(string args)
{
try
{
return SomeClass.Parse(_server.Query(args));
}
catch (Exception)
{
return null;
}
}
O ile rozumiem, pomijanie takich błędów jest złą praktyką, ponieważ niszczy przydatne informacje z wyjątku oryginalnego serwera i sprawia, że kod jest kontynuowany, kiedy powinien się zakończyć.
Kiedy należy całkowicie wyeliminować wszystkie takie błędy?
programming-practices
language-agnostic
error-handling
użytkownik626528
źródło
źródło
Odpowiedzi:
Wyobraź sobie kod z tysiącami plików korzystających z wielu bibliotek. Wyobraź sobie, że wszystkie są tak zakodowane.
Wyobraź sobie na przykład, że aktualizacja twojego serwera powoduje zniknięcie jednego pliku konfiguracyjnego; a teraz wszystko, co masz, to ślad stosu, wyjątek wskaźnika zerowego, gdy próbujesz użyć tej klasy: jak byś to rozwiązał? Może to potrwać godziny, w których co najmniej rejestrowanie nieprzetworzonego śladu stosu pliku nie znaleziono [ścieżka pliku] może umożliwić natychmiastowe rozwiązanie problemu.
Albo jeszcze gorzej: awaria jednej z bibliotek używanych po aktualizacji, która powoduje awarię kodu w późniejszym czasie. Jak możesz to prześledzić z powrotem do biblioteki?
Nawet bez solidnej obsługi błędów
lub
może zaoszczędzić wiele godzin.
Ale są przypadki, w których naprawdę możesz zignorować wyjątek i zwrócić wartość null w ten sposób (lub nic nie robić). Zwłaszcza jeśli zintegrujesz się z jakimś źle zaprojektowanym starszym kodem i ten wyjątek jest oczekiwany jako normalny przypadek.
W rzeczywistości, robiąc to, powinieneś po prostu zastanawiać się, czy naprawdę ignorujesz wyjątek lub „odpowiednio obchodzisz się” ze swoimi potrzebami. Jeśli zwracanie wartości null „prawidłowo obsługuje” wyjątek w danym przypadku, zrób to. I dodaj komentarz, dlaczego to jest właściwe.
W większości przypadków należy stosować najlepsze praktyki, może 80%, może 99%, ale zawsze znajdziesz jeden przypadek skrajny, w którym nie mają zastosowania. W takim przypadku zostaw komentarz, dlaczego nie stosujesz się do praktyki dla innych (a nawet siebie), którzy przeczytają Twój kod kilka miesięcy później.
źródło
Są przypadki, w których ten wzorzec jest użyteczny - ale są one zwykle używane, gdy wygenerowany wyjątek nigdy nie powinien był być (tj. Gdy wyjątki są używane do normalnego zachowania).
Na przykład wyobraź sobie, że masz klasę, która otwiera plik i przechowuje go w zwracanym obiekcie. Jeśli plik nie istnieje, możesz uznać, że nie jest to przypadek błędu, zwracasz null i pozwalasz użytkownikowi zamiast tego utworzyć nowy plik. Metoda otwierania pliku może zgłosić tutaj wyjątek wskazujący brak pliku, więc przechwycenie go w ciszy może być prawidłową sytuacją.
Jednak nie często chcesz to zrobić, a jeśli kod jest zaśmiecony takim wzorcem, chciałbyś sobie z tym poradzić. Przynajmniej spodziewałbym się, że tak cichy haczyk napisze wiersz dziennika informujący, że tak się stało (masz śledzenie, prawda) i komentarz wyjaśniający to zachowanie.
źródło
null
jeśli jest to najlepsze, co może zaoferować wybrany język.Jest to w 100% zależne od kontekstu. Jeśli osoba wywołująca taką funkcję ma obowiązek wyświetlania lub rejestrowania błędów, jest to oczywiście nonsens. Jeśli osoba dzwoniąca zignoruje wszystkie komunikaty o błędach lub wyjątkach, zwrócenie wyjątku lub jego komunikatu nie ma większego sensu. Gdy wymaga się, aby kod zakończył się tylko bez podawania przyczyny, wówczas wywołanie
prawdopodobnie byłby wystarczający. Musisz rozważyć, czy utrudni to znalezienie przyczyny błędu przez użytkownika - w takim przypadku jest to niewłaściwa forma obsługi błędów. Jednak jeśli kod wywołujący wygląda tak
wtedy powinieneś spierać się z deweloperem podczas przeglądania kodu. Jeśli ktoś wdroży taką formę obsługi bezbłędnej tylko dlatego, że jest zbyt leniwy, aby dowiedzieć się o prawidłowych wymaganiach, kod ten nie powinien zostać wprowadzony do produkcji, a jego autor powinien zostać pouczony o możliwych konsekwencjach takiego lenistwa .
źródło
W latach, które spędziłem programując i rozwijając systemy, były tylko dwie sytuacje, w których uważałem ten wzorzec za użyteczny (w obu przypadkach eliminacja zawierała również rejestrowanie zgłoszonego wyjątku, nie uważam zwykłego chwytania i
null
powrotu za dobrą praktykę ).Dwie sytuacje są następujące:
1. Gdy wyjątek nie został uznany za stan wyjątkowy
Dzieje się tak, gdy wykonujesz operację na niektórych danych, które mogą wyrzucić, wiesz, że mogą one wyrzucić, ale nadal chcesz, aby aplikacja nadal działała, ponieważ nie potrzebujesz przetworzonych danych. Jeśli je otrzymasz, to dobrze, jeśli nie, to także dobrze.
Mogą pojawić się pewne opcjonalne atrybuty klasy.
2. Kiedy udostępniasz nową (lepszą, szybszą?) Implementację biblioteki przy użyciu interfejsu już używanego w aplikacji
Wyobraź sobie, że masz aplikację korzystającą ze starej biblioteki, która nie zgłaszała wyjątków, ale zwracała
null
błąd. Utworzyłeś więc adapter dla tej biblioteki, kopiując prawie oryginalne API biblioteki, i używasz tego nowego (wciąż nie rzucającego) interfejsu w swojej aplikacji inull
sam przeprowadzasz kontrole.Nadchodzi nowa wersja biblioteki, a może zupełnie inna biblioteka oferująca tę samą funkcjonalność, która zamiast zwracać
null
s, generuje wyjątki i chcesz z niej korzystać.Nie chcesz przeciekać wyjątków od głównej aplikacji, więc wyłącz je i zaloguj w utworzonym adapterze, aby zawinąć tę nową zależność.
Pierwszy przypadek nie stanowi problemu, jest to pożądane zachowanie kodu. Jednak w drugiej sytuacji, jeśli wszędzie
null
zwracana wartość adaptera biblioteki naprawdę oznacza błąd, refaktoryzacja interfejsu API w celu wygenerowania wyjątku i wyłapanie go zamiast sprawdzanianull
może być (i zwykle jest to kodowo) dobrym pomysłem.Osobiście używam tłumienia wyjątków tylko w pierwszym przypadku. Użyłem go tylko w drugim przypadku, gdy nie mieliśmy budżetu, aby reszta aplikacji działała z wyjątkami zamiast
null
s.źródło
Chociaż logiczne wydaje się stwierdzenie, że programy powinny wychwytywać tylko wyjątki, które potrafią obsłużyć, i nie mogą wiedzieć, jak radzić sobie z wyjątkami, których programista nie oczekiwał, takie stwierdzenie ignoruje fakt, że wiele operacji może zakończyć się niepowodzeniem prawie nieograniczona liczba sposobów, które nie wywołują skutków ubocznych, oraz że w wielu przypadkach właściwe postępowanie w przypadku większości takich awarii będzie identyczne; dokładne szczegóły awarii będą nieistotne, w związku z czym nie powinno mieć znaczenia, czy programista je przewidział.
Jeśli na przykład funkcja ma na celu odczytanie pliku dokumentu do odpowiedniego obiektu i albo utworzenie nowego okna dokumentu, aby pokazać ten obiekt, albo zgłoszenie użytkownikowi, że pliku nie można odczytać, próba załadowania niepoprawny plik dokumentu nie powinien powodować awarii aplikacji - powinien zamiast tego wyświetlać komunikat wskazujący na problem, ale pozwolić pozostałej części aplikacji kontynuować normalne działanie, chyba że z jakiegoś powodu próba załadowania dokumentu spowodowała uszkodzenie stanu innej rzeczy w systemie .
Zasadniczo, właściwa obsługa wyjątku często zależy w mniejszym stopniu od typu wyjątku niż miejsca, w którym został zgłoszony; jeśli zasób jest chroniony przez blokadę do odczytu i zapisu, a w metodzie, która uzyskała blokadę do odczytu, zgłaszany jest wyjątek, właściwe zachowanie powinno zasadniczo polegać na zwolnieniu blokady, ponieważ metoda nie mogła nic zrobić z zasobem . Jeśli wyjątek zostanie zgłoszony podczas uzyskiwania blokady do zapisu, blokada powinna często być unieważniana, ponieważ strzeżony zasób może być w niepoprawnym stanie; jeśli operacja podstawowa blokowania nie ma stanu „unieważnionego”, należy dodać flagę, aby śledzić takie unieważnienie. Zwolnienie blokady bez unieważnienia jest złe, ponieważ inny kod może widzieć chroniony obiekt w nieprawidłowym stanie. Pozostawienie zwisającego zamka nie jest jednak albo właściwe rozwiązanie. Właściwym rozwiązaniem jest unieważnienie blokady, aby wszelkie oczekujące lub przyszłe próby przejęcia natychmiast zakończyły się niepowodzeniem.
Jeśli okaże się, że nieważny zasób zostanie porzucony przed próbą jego użycia, nie ma powodu, aby miał on wyłączyć aplikację. Jeśli unieważniony zasób ma krytyczne znaczenie dla kontynuacji działania aplikacji, aplikacja będzie musiała zostać wyłączona, ale unieważnienie zasobu prawdopodobnie tak się stanie. Kod, który otrzymał pierwotny wyjątek, często nie ma możliwości dowiedzenia się, która sytuacja ma zastosowanie, ale jeśli unieważni on zasób, może zapewnić, że w obu przypadkach zostanie wykonane prawidłowe działanie.
źródło
Połknięcie tego rodzaju błędu nie jest szczególnie przydatne dla nikogo. Tak, oryginalny rozmówca może nie dbają o wyjątku ale ktoś inny potęgi .
Więc co oni robią? Dodają kod do obsługi wyjątku, aby zobaczyć, jaki smak wyjątku otrzymają. Świetny. Ale jeśli zostanie pozostawiony moduł obsługi wyjątków, oryginalny obiekt wywołujący nie będzie już miał wartości zerowej i coś się zepsuje w innym miejscu.
Nawet jeśli zdajesz sobie sprawę, że jakiś kod nadrzędny może zgłosić błąd, a tym samym zwrócić wartość null, byłoby poważnie obojętne, abyś przynajmniej nie próbował wyłączyć wyjątku w kodzie wywołującym IMHO.
źródło
Widziałem przykłady, w których biblioteki stron trzecich miały potencjalnie przydatne metody, z wyjątkiem tego, że wprowadzają wyjątki w niektórych przypadkach, które powinny działać dla mnie. Co mogę zrobić?
Na przykład metoda biblioteczna
zwraca pierwsze foo, ale jeśli nie ma, zgłasza wyjątek. Ale potrzebuję metody zwrotu pierwszego foo lub wartości zerowej. Więc piszę ten:
Nie schludnie. Ale czasami pragmatyczne rozwiązanie.
źródło