Jako dobry programista powinieneś pisać solidne kody, które będą obsługiwały każdy wynik jego programu. Jednak prawie wszystkie funkcje z biblioteki C zwracają 0 lub -1 lub NULL, gdy wystąpi błąd.
Czasami oczywiste jest, że konieczne jest sprawdzanie błędów, na przykład podczas próby otwarcia pliku. Ale często ignoruję sprawdzanie błędów w funkcjach takich, a printf
nawet malloc
dlatego, że nie uważam tego za konieczne.
if(fprintf(stderr, "%s", errMsg) < 0){
perror("An error occurred while displaying the previous error.");
exit(1);
}
Czy dobrą praktyką jest ignorowanie niektórych błędów, czy też istnieje lepszy sposób na obsługę wszystkich błędów?
c
error-handling
Derek 朕 會 功夫
źródło
źródło
try
instrukcji, dzięki czemu nie musisz sprawdzać każdego wywołania lub operacji. (Należy również pamiętać, że niektóre języki są lepsze niż inne w wykrywaniu prostych błędów, takich jak zerowe zerowanie lub indeks tablicy poza zakresem.)errno
! W przypadku, gdy nie jesteś zaznajomiony, to prawda, że „prawie wszystkie funkcje z biblioteki C zwrócą 0 lub -1 lubNULL
gdy wystąpi błąd”, ustawiają równieżerrno
zmienną globalną , do której można uzyskać dostęp za pomocą,#include <errno.h>
a następnie po prostu czytając wartośćerrno
. Na przykład, jeśliopen
zwróci (2)-1
, możesz sprawdzić, czy toerrno == EACCES
, co wskazywałoby na błąd uprawnień, lubENOENT
, co wskazywałoby, że żądany plik nie istnieje.try
/catch
, chociaż można to symulować za pomocą skoków.Odpowiedzi:
Zasadniczo kod powinien uwzględniać wyjątkowe warunki tam, gdzie jest to właściwe. Tak, to jest niejasne stwierdzenie.
W językach wyższego poziomu z obsługą wyjątków oprogramowania jest to często określane jako „złap wyjątek w metodzie, w której możesz coś z tym zrobić”. Jeśli wystąpił błąd pliku, być może pozwolisz mu na powiększenie stosu do kodu interfejsu użytkownika, który może faktycznie powiedzieć użytkownikowi „nie udało się zapisać pliku na dysk”. Mechanizm wyjątkowy skutecznie pochłania „każdy mały błąd” i domyślnie obsługuje go w odpowiednim miejscu.
W C nie masz tego luksusu. Istnieje kilka sposobów radzenia sobie z błędami, niektóre z nich są funkcjami języka / biblioteki, niektóre z nich to praktyki kodowania.
Zignorować niektóre błędy? Może. Na przykład uzasadnione jest założenie, że zapis na standardowe wyjście nie zakończy się niepowodzeniem. Jeśli to się nie powiedzie, to jak byś powiedział użytkownikowi? Tak, dobrym pomysłem jest ignorowanie niektórych błędów lub kodowanie w celu ich uniknięcia. Na przykład sprawdź zero przed podzieleniem.
Istnieją sposoby obsługi wszystkich lub przynajmniej większości błędów:
Kaskadowe
if
s:Przetestuj pierwszy warunek, np. Otwarcie lub utworzenie pliku, a następnie załóż, że kolejne operacje się powiodły.
Solidny kod jest dobry i należy sprawdzać i obsługiwać błędy. To, która metoda jest najlepsza dla twojego kodu, zależy od tego, co robi kod, jak krytyczna jest awaria itp. I tylko Ty możesz naprawdę na to odpowiedzieć. Te metody są jednak testowane w walce i stosowane w różnych projektach typu open source, w których można sprawdzić, jak prawdziwy kod sprawdza błędy.
źródło
stdout
można go przekierować do pliku lub nawet nie istnieć w zależności od systemu.stdout
nie jest strumieniem „zapisu do konsoli”, zwykle tak jest, ale nie musi tak być.stdout
... oczywiste :-)dump_database > backups/db_dump.txt
nie zapisuje na standardowe wyjście w danym momencie, nie chciałbym, aby kontynuowało się i kończyło pomyślnie. (Nie chodzi o to, że kopie zapasowe baz danych są tworzone w ten sposób, ale kwestia nadal jest ważna)Tak, ale wiesz, którą funkcję wywołałeś, prawda?
W rzeczywistości masz wiele informacji, które możesz umieścić w komunikacie o błędzie. Wiesz, która funkcja została wywołana, nazwa funkcji, która ją wywołała, jakie parametry zostały przekazane oraz wartość zwracana przez funkcję. To mnóstwo informacji na bardzo pouczający komunikat o błędzie.
Nie musisz tego robić dla każdego wywołania funkcji. Ale kiedy po raz pierwszy zobaczysz komunikat o błędzie „Wystąpił błąd podczas wyświetlania poprzedniego błędu”, kiedy tak naprawdę potrzebna była użyteczna informacja, będzie to ostatni raz, gdy zobaczysz tam ten komunikat o błędzie, ponieważ natychmiast zamierzasz zmienić komunikat o błędzie informujący o czymś, co pomoże ci rozwiązać problem.
źródło
if (!myfunc()) {/*proceed*/}
. 0 nie było błędem, a wszystko niezerowe było jakimś błędem, a każdy rodzaj błędu miał swój własny niezerowy kod. mój przyjaciel powiedział: „jest tylko jedna Prawda, ale wiele kłamstw”. „0” powinno być „prawdą” wif()
teście, a wszystko inne niż zero powinno być „fałszem”.if(function()) { // do something }
brzmi „jeśli funkcja zostanie wykonana bez błędu, to zrób coś”. lub jeszcze lepiej „jeśli funkcja zostanie wykonana pomyślnie, zrób coś”. Poważnie, ci, którzy rozumieją konwencję, nie są tym zdezorientowani. Zwróceniefalse
(lub 0), gdy wystąpi błąd , nie pozwoli na użycie kodów błędów. Zero oznacza fałsz w C, ponieważ tak działa matematyka. Zobacz programmers.stackexchange.com/q/198284TLDR; prawie nigdy nie należy ignorować błędów.
Język C nie ma dobrej funkcji obsługi błędów, co pozwala programistom na implementację własnych rozwiązań. Bardziej nowoczesne języki mają wbudowane wyjątki, które znacznie ułatwiają obsługę tego konkretnego problemu.
Ale kiedy utkniesz z C, nie masz takich korzyści. Niestety, musisz po prostu zapłacić cenę za każdym razem, gdy wywołujesz funkcję, która jest zdalnie możliwa do awarii. W przeciwnym razie poniesiesz znacznie gorsze konsekwencje, takie jak niezamierzone nadpisywanie danych w pamięci. Dlatego ogólną zasadą jest zawsze sprawdzanie błędów.
Jeśli nie sprawdzisz powrotu
fprintf
, najprawdopodobniej pozostawisz błąd, który w najlepszym wypadku nie zrobi tego, czego oczekuje użytkownik, a co gorsza, rozbije całą rzecz podczas lotu. Nie ma usprawiedliwienia, aby podważyć się w ten sposób.Jednak jako programista C Twoim zadaniem jest również łatwe utrzymanie kodu. Czasami więc możesz bezpiecznie zignorować błędy w celu zachowania przejrzystości, jeśli (i tylko wtedy) nie stanowią żadnego zagrożenia dla ogólnego zachowania aplikacji. .
To ten sam problem, co w przypadku:
Jeśli zauważysz, że bez dobrych komentarzy w pustym bloku catch, jest to z pewnością problem. Nie ma powodu, aby sądzić, że wezwanie
malloc()
wykona się pomyślnie przez 100% czasu.źródło
malloc()
to jedna z funkcji, na której zdecydowanie chcesz sprawdzić zwracane wartości!Pytanie nie jest w rzeczywistości specyficzne dla języka, ale raczej dla użytkownika. Pomyśl o tym z perspektywy użytkownika. Użytkownik robi coś, na przykład wpisując nazwę programu w wierszu polecenia i naciskając klawisz Enter. Czego oczekuje użytkownik? Jak mogą stwierdzić, czy coś poszło nie tak? Czy stać ich na interweniowanie w przypadku wystąpienia błędu?
W wielu typach kodu takie kontrole są nadmierne. Jednak w przypadku kodu o wysokiej niezawodności o wysokiej niezawodności, takiego jak w przypadku reaktorów jądrowych, patologiczne sprawdzanie błędów i planowane ścieżki odzyskiwania są częścią codziennego charakteru zadania. Warto poświęcić czas na pytanie „Co się stanie, jeśli X się nie powiedzie? Jak wrócić do bezpiecznego stanu?” W mniej niezawodnym kodzie, takim jak w przypadku gier wideo, można uniknąć znacznie mniejszej kontroli błędów.
Innym podobnym podejściem jest to, jak bardzo możesz poprawić stan, wychwytując błąd? Nie mogę policzyć liczby programów w C ++, które z dumą wychwytują wyjątki, tylko po to, aby je ponownie rzucić, ponieważ tak naprawdę nie wiedziały, co z nimi zrobić ... ale wiedziały, że powinny obsługiwać wyjątki. Takie programy nic nie zyskały na dodatkowym wysiłku. Dodaj tylko kod sprawdzający błędy, który Twoim zdaniem może lepiej poradzić sobie z sytuacją, niż po prostu nie sprawdzając kodu błędu. Biorąc to pod uwagę, C ++ ma określone zasady obsługi wyjątków, które występują podczas obsługi wyjątków, aby złapać je w bardziej znaczący sposób (a przez to mam na myśli wywołanie terminate (), aby upewnić się, że stos pogrzebowy, który zbudowałeś dla siebie, zapali się w odpowiedniej chwale)
Jak patologicznie możesz się dostać? Pracowałem nad programem, który zdefiniował „SafetyBool”, którego prawdziwe i fałszywe wartości zostały starannie wybrane, aby mieć równomierny rozkład 1 i 0, i zostały one wybrane tak, aby jedna z wielu awarii sprzętowych (pojedyncze bity flip, szyna danych ślady ulegają uszkodzeniu itp.) nie spowodowały błędnej interpretacji wartości logicznej. Nie trzeba dodawać, że nie twierdziłbym, że jest to praktyka programowania ogólnego przeznaczenia, którą można zastosować w każdym starym programie.
źródło
źródło
Trochę abstrakcyjnego podejścia do pytania. I niekoniecznie jest to język C.
W przypadku większych programów miałbyś warstwę abstrakcji; być może część silnika, biblioteki lub w ramach. Że warstwa nie dbają o pogodzie można uzyskać poprawne dane lub wyjście byłoby jakąś wartość domyślna:
0
,-1
,null
itd.Jest też warstwa, która byłaby twoim interfejsem do warstwy abstrakcyjnej, która wymagałaby dużo obsługi błędów i być może innych rzeczy, takich jak wstrzykiwanie zależności, nasłuchiwanie zdarzeń itp.
A później będziesz miał swoją konkretną warstwę implementacyjną, w której faktycznie ustawisz reguły i obsłużysz wyniki.
Uważam więc, że czasem lepiej jest całkowicie wykluczyć obsługę błędów z części kodu, ponieważ ta część po prostu nie wykonuje tego zadania. A potem mieć procesor, który ocenia dane wyjściowe i wskazuje błąd.
Odbywa się to głównie w celu rozdzielenia obowiązków, co prowadzi do czytelności kodu i lepszej skalowalności.
źródło
Ogólnie rzecz biorąc, chyba że masz dobry powód, aby nie sprawdzać tego błędu. Jedynym dobrym powodem, dla którego mogę wymyślić, że nie sprawdzam warunku błędu, jest to, że nie możesz zrobić czegoś sensownego, jeśli się nie powiedzie. Jest to jednak bardzo trudny do spełnienia bar, ponieważ zawsze istnieje jedna realna opcja: wyjście z gracją.
źródło