Pracuję nad książką „Head First Python” (to jest mój język do nauki w tym roku) i doszedłem do sekcji, w której dyskutują o dwóch technikach kodu:
Sprawdzanie obsługi First vs. Exception.
Oto przykład kodu Python:
# Checking First
for eachLine in open("../../data/sketch.txt"):
if eachLine.find(":") != -1:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
# Exception handling
for eachLine in open("../../data/sketch.txt"):
try:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
except:
pass
Pierwszy przykład dotyczy bezpośrednio problemu w .split
funkcji. Drugi pozwala po prostu sobie z tym poradzić programowi obsługi wyjątków (i ignoruje problem).
W książce twierdzą, że zamiast sprawdzania najpierw używać wyjątków. Argument polega na tym, że kod wyjątku przechwytuje wszystkie błędy, przy czym sprawdzanie w pierwszej kolejności przechwytuje tylko to, o czym myślisz (i brakuje ci przypadków narożnych). Najpierw nauczono mnie sprawdzać, więc mój instynkt był taki, ale ich pomysł jest interesujący. Nigdy nie myślałem o użyciu obsługi wyjątków do załatwiania spraw.
Który z nich jest ogólnie uważany za lepszą praktykę?
źródło
if -1 == eachLine.find(":"): continue
wtedy reszta pętli również nie byłaby wcięta.Odpowiedzi:
W .NET powszechną praktyką jest unikanie nadużywania wyjątków. Jednym argumentem jest wydajność: w .NET zgłoszenie wyjątku jest drogie obliczeniowo.
Innym powodem, dla którego należy unikać ich nadużywania, jest to, że bardzo trudno jest odczytać kod, który zbytnio na nich polega. Wpis na blogu Joela Spolsky'ego dobrze opisuje problem.
Sednem argumentu jest następujący cytat:
Osobiście zgłaszam wyjątki, gdy mój kod nie może robić tego, co jest zlecone. Zwykle używam try / catch, gdy mam zamiar poradzić sobie z czymś poza granicami mojego procesu, na przykład wywołaniem SOAP, wywołaniem bazy danych, wywołaniem pliku lub wywołaniem systemowym. W przeciwnym razie próbuję kodować defensywnie. Nie jest to trudna i szybka reguła, ale jest to ogólna praktyka.
Scott Hanselman pisze tutaj także o wyjątkach w .NET . W tym artykule opisuje kilka praktycznych zasad dotyczących wyjątków. Mój ulubiony?
źródło
W szczególności w Pythonie zwykle uważa się, że lepszą praktyką jest złapanie wyjątku. Zwykle nazywa się go Łatwiej prosić o przebaczenie niż pozwolenie (EAFP), w porównaniu do Look Before You Leap (LBYL). Są przypadki, w których LBYL da ci subtelne błędy w niektórych przypadkach.
Jednak należy być ostrożnym z gołymi
except:
sprawozdania , jak również zbyt szeroko wyjątkiem sprawozdania, ponieważ mogą one również maskować błędy zarówno - coś takiego byłoby lepiej:źródło
try
/except
zamiast konfigurowania warunkowego dla „jest następujący prawdopodobnie wyjątek”, a Python praktyką jest zdecydowanie preferują pierwsze. Nie przeszkadza to, że takie podejście jest (prawdopodobnie) bardziej wydajne, ponieważ w przypadku, gdy podział się powiedzie, chodzisz po łańcuchu tylko raz. Jeśli chodzi o to, czysplit
należy tu rzucić wyjątek, powiedziałbym, że zdecydowanie powinien - jedną powszechną zasadą jest to, że powinieneś rzucać wyjątek, gdy nie możesz zrobić tego, co mówi twoje imię i nie możesz rozdzielić przy brakującym ograniczniku.Podejście pragmatyczne
Powinieneś być defensywny, ale do pewnego stopnia. Powinieneś napisać obsługę wyjątków, ale do pewnego stopnia. Będę używał programowania internetowego jako przykładu, ponieważ tutaj mieszkam.
Wynika to z doświadczenia pracy w scenariuszach dużych zespołów.
Analogia
Wyobraź sobie, że przez cały czas nosiłeś kombinezon kosmiczny w ISS. W ogóle trudno byłoby pójść do łazienki lub zjeść. Poruszanie się w module kosmicznym byłoby bardzo duże. To by było do bani. Pisanie kilku próbnych haczyków w kodzie jest trochę podobne. Musisz mieć punkt, w którym powiesz: hej, zabezpieczyłem ISS, a moi astronauci w środku są w porządku, więc po prostu nie jest praktyczne noszenie skafandra na każdy możliwy scenariusz.
źródło
Głównym argumentem tej książki jest to, że wersja wyjątkowa kodu jest lepsza, ponieważ przechwyci wszystko, co można przeoczyć, jeśli spróbujesz napisać własne sprawdzanie błędów.
Myślę, że to stwierdzenie jest prawdziwe tylko w bardzo szczególnych okolicznościach - gdy nie obchodzi cię, czy dane wyjściowe są prawidłowe.
Nie ma wątpliwości, że zgłaszanie wyjątków jest rozsądną i bezpieczną praktyką. Powinieneś to zrobić za każdym razem, gdy czujesz, że jest coś w bieżącym stanie programu, z którym (jako programista) nie możesz lub nie chcesz sobie poradzić.
Twój przykład dotyczy jednak wychwytywania wyjątków. Jeśli złapiesz wyjątek, nie chronisz się przed scenariuszami, które mogłeś przeoczyć. Robisz dokładnie odwrotnie: zakładasz, że nie przeoczyłeś żadnego scenariusza, który mógłby spowodować tego rodzaju wyjątek, i dlatego masz pewność, że można go złapać (a tym samym uniemożliwić wyjście programu, jak każdy niewyłapany wyjątek).
Używając podejścia wyjątkowego, jeśli widzisz
ValueError
wyjątek, pomijasz linię. Stosując tradycyjne podejście nie będące wyjątkami, zliczasz liczbę zwróconych wartościsplit
, a jeśli jest mniejsza niż 2, pomijasz linię. Czy powinieneś czuć się bezpieczniej dzięki podejściu wyjątkowemu, ponieważ mogłeś zapomnieć o innych sytuacjach „błędu” w tradycyjnym sprawdzaniu błędów iexcept ValueError
złapałbyś je dla siebie?To zależy od charakteru twojego programu.
Jeśli piszesz na przykład przeglądarkę internetową lub odtwarzacz wideo, problem z danymi wejściowymi nie powinien powodować awarii z niewyłapanym wyjątkiem. O wiele lepiej jest wydać coś sensownie (nawet jeśli, ściśle mówiąc, niepoprawnego), niż wyjść.
Jeśli piszesz aplikację, w której ważna jest poprawność (np. Oprogramowanie biznesowe lub inżynierskie), byłoby to okropne podejście. Jeśli zapomniałeś o jakimś scenariuszu, który wywołuje
ValueError
, najgorsze, co możesz zrobić, to po cichu zignorować ten nieznany scenariusz i po prostu pominąć linię. W ten sposób bardzo subtelne i kosztowne błędy kończą się w oprogramowaniu.Możesz pomyśleć, że jedynym sposobem na zobaczenie
ValueError
tego kodu jestsplit
zwrócenie tylko jednej wartości (zamiast dwóch). Ale co, jeśli twojeprint
oświadczenie zacznie później używać wyrażenia, które pojawia sięValueError
w pewnych warunkach? Spowoduje to, że pominiesz niektóre linie nie dlatego, że nie trafiają:
, ale dlatego, żeprint
zawodzą. To jest przykład subtelnego błędu, o którym wspominałem wcześniej - niczego nie zauważysz, po prostu stracisz kilka linii.Radzę, aby unikać wychwytywania (ale nie podnoszenia!) Wyjątków w kodzie, w których wytwarzanie niepoprawnych danych wyjściowych jest gorsze niż wychodzenie. Jedyny raz, kiedy wychwytuję wyjątek w takim kodzie, ma naprawdę trywialne wyrażenie, dzięki czemu mogę łatwo zrozumieć, co może powodować każdy z możliwych typów wyjątków.
Jeśli chodzi o wpływ zastosowania wyjątków na wydajność, jest to trywialne (w Pythonie), chyba że często występują wyjątki.
Jeśli używasz wyjątków do obsługi rutynowo występujących warunków, możesz w niektórych przypadkach ponieść ogromne koszty wydajności. Załóżmy na przykład, że zdalnie wykonujesz jakieś polecenie. Możesz sprawdzić, czy tekst polecenia spełnia przynajmniej minimalne wymagania sprawdzania poprawności (np. Składnia). Możesz też poczekać na zgłoszenie wyjątku (co dzieje się dopiero po zdalnym przeanalizowaniu polecenia przez zdalny serwer i znalezieniu problemu). Oczywiście ten pierwszy jest o rząd wielkości szybszy. Kolejny prosty przykład: możesz sprawdzić, czy liczba jest zero ~ 10 razy szybsza niż próba wykonania podziału, a następnie wyłapanie wyjątku ZeroDivisionError.
Te rozważania mają znaczenie tylko wtedy, gdy często wysyłasz zniekształcone ciągi poleceń do zdalnych serwerów lub otrzymujesz argumenty o zerowej wartości, których używasz do dzielenia.
Uwaga: Zakładam, że użyłbyś
except ValueError
zamiast sprawiedliwegoexcept
; jak zauważyli inni, i jak sama książka mówi na kilku stronach, nigdy nie powinieneś używać gołyexcept
.Inna uwaga: właściwym podejściem nie będącym wyjątkiem jest policzenie liczby zwracanych wartości
split
zamiast szukania:
. Ten ostatni jest zdecydowanie zbyt wolny, ponieważ powtarza pracę wykonaną przezsplit
i może prawie podwoić czas wykonania.źródło
Zasadniczo, jeśli wiesz, że instrukcja może wygenerować niepoprawny wynik, przetestuj ją i sobie z tym poradzić. Używaj wyjątków dla rzeczy, których nie oczekujesz; rzeczy, które są „wyjątkowe”. Sprawia, że kod jest jaśniejszy w sensie umownym (na przykład „nie powinno być zerowe”).
źródło
Używaj tego, co zawsze działa dobrze w ...
Zarówno obsługa wyjątków, jak i programowanie obronne są różnymi sposobami wyrażania tego samego zamiaru.
źródło
TBH, nie ma znaczenia, czy użyjesz
try/except
mechaniki czy kontroliif
wyciągów. Zazwyczaj zarówno EAFP, jak i LBYL występują w większości linii bazowych języka Python, przy czym EAFP jest nieco bardziej powszechny. Czasami EAFP jest znacznie bardziej czytelny / idiomatyczny, ale w tym konkretnym przypadku myślę, że i tak jest dobrze.Jednak...
Będę ostrożny przy użyciu twojego aktualnego odniesienia. Kilka rażących problemów z kodem:
with
idiomu podczas czytania plików w Pythonie: istnieje bardzo niewiele wyjątków. To nie jest jeden z nich.except
instrukcja, która nie przechwytuje konkretnego wyjątku)role, lineSpoken = eachLine.split(":",1)
Ivc ma dobrą odpowiedź na ten temat i EAFP, ale przecieka także deskryptor.
Wersja LBYL niekoniecznie jest tak wydajna jak wersja EAFP, więc twierdzenie, że zgłaszanie wyjątków jest „drogie pod względem wydajności”, jest kategorycznie fałszywe. To zależy od rodzaju przetwarzanych ciągów:
źródło
Zasadniczo obsługa wyjątków powinna być bardziej odpowiednia dla języków OOP.
Drugi punkt to wydajność, ponieważ nie musisz wykonywać
eachLine.find
dla każdej linii.źródło
Myślę, że programowanie defensywne szkodzi wydajności. Powinieneś także wychwytywać tylko wyjątki, które zamierzasz obsłużyć, pozwól środowisku wykonawczemu zająć się wyjątkiem, którego nie wiesz, jak sobie poradzić.
źródło