Dlaczego „oprócz: pass” jest złą praktyką programistyczną?

324

Często widzę komentarze na temat innych pytań związanych z przepełnieniem stosu, dotyczące tego, jak except: passodradza się korzystanie z nich . Dlaczego to takie złe? Czasami po prostu nie dbam o to, jakie są błędy i chcę po prostu kontynuować kod.

try:
    something
except:
    pass

Dlaczego używanie except: passbloku jest złe? Co sprawia, że ​​jest źle? Czy to fakt, że passpopełniłem błąd lub że exceptpopełniłem błąd?

Vader
źródło
1
Przynajmniej sugerowałbym, żebyś to zalogował, więc WIESZ, jakie problemy ignorujesz. Skorzystaj z loggingmodułu na poziomie DEBUG, aby uniknąć przesyłania strumieniowego w produkcji, ale zachowaj je w fazie rozwoju.
pcurry

Odpowiedzi:

346

Jak słusznie zgadłeś, istnieją dwie strony: Wyłapanie dowolnego błędu przez określenie typu wyjątku po excepti przekazanie go bez podejmowania żadnych działań.

Moje wyjaśnienie jest „trochę” dłuższe - więc tl; dr dzieli się na następujące:

  1. Nie wychwytuj żadnych błędów . Zawsze określaj wyjątki, z których jesteś przygotowany, aby je odzyskać i tylko je wychwyć.
  2. Staraj się unikać przechodzenia poza blokami . O ile nie jest to wyraźnie pożądane, zwykle nie jest to dobry znak.

Ale przejdźmy do szczegółów:

Nie wychwytuj żadnych błędów

Korzystając z trybloku, zwykle robisz to, ponieważ wiesz, że istnieje szansa na wygenerowanie wyjątku. Jako taki, masz już przybliżone pojęcie o tym, co może się złamać i jaki wyjątek może zostać zgłoszony. W takich przypadkach wychwytujesz wyjątek, ponieważ możesz go pozytywnie odzyskać . Oznacza to, że jesteś przygotowany na wyjątek i masz alternatywny plan, którego będziesz przestrzegać w przypadku tego wyjątku.

Na przykład, gdy poprosisz użytkownika o wprowadzenie liczby, możesz przekonwertować dane wejściowe za pomocą, int()który może podnieść liczbę ValueError. Możesz to łatwo odzyskać, po prostu prosząc użytkownika o ponowną próbę, więc złapanie ValueErrori ponowne monitowanie użytkownika byłoby właściwym planem. Innym przykładem może być odczytanie pewnej konfiguracji z pliku, a plik ten nie istnieje. Ponieważ jest to plik konfiguracyjny, możesz mieć domyślną konfigurację zastępczą, więc plik nie jest koniecznie potrzebny. FileNotFoundErrorDobrym pomysłem byłoby więc złapanie i po prostu zastosowanie domyślnej konfiguracji. Teraz w obu tych przypadkach mamy bardzo specyficzny wyjątek, którego się spodziewamy, i mamy równie konkretny plan, aby go naprawić. W związku z tym w każdym przypadku wyraźnie tylko except to pewne wyjątek.

Gdybyśmy jednak złapali wszystko , to - oprócz tych wyjątków, z których jesteśmy gotowi wyzdrowieć - istnieje również szansa, że ​​otrzymamy wyjątki, których się nie spodziewaliśmy i z których w rzeczywistości nie możemy wyzdrowieć; lub nie powinien wyzdrowieć.

Weźmy przykład pliku konfiguracyjnego z góry. W przypadku brakującego pliku zastosowaliśmy naszą domyślną konfigurację i w późniejszym czasie możemy zdecydować o automatycznym zapisaniu konfiguracji (więc następnym razem plik będzie istniał). Teraz wyobraź sobie, że dostaniemy IsADirectoryErroralboPermissionErrorzamiast. W takich przypadkach prawdopodobnie nie chcemy kontynuować; nadal możemy zastosować naszą domyślną konfigurację, ale później nie będziemy mogli zapisać pliku. I prawdopodobne jest, że użytkownik miał również konfigurację niestandardową, więc użycie wartości domyślnych prawdopodobnie nie jest pożądane. Chcielibyśmy więc natychmiast poinformować o tym użytkownika i prawdopodobnie przerwać również wykonywanie programu. Ale nie chcemy tego robić gdzieś głęboko w jakiejś małej części kodu; jest to coś ważnego na poziomie aplikacji, więc powinno się z tym obchodzić na górze - pozwól więc, by wyjątek wybuchł.

Kolejny prosty przykład jest również wspomniany w dokumencie idiomów Python 2 . W kodzie znajduje się prosta literówka, która powoduje jej uszkodzenie. Ponieważ łapiemy każdy wyjątek, łapiemy również NameErrors i SyntaxErrors . Oba są błędami, które przytrafiają się nam wszystkim podczas programowania; i oba są błędami, których absolutnie nie chcemy uwzględniać przy wysyłaniu kodu. Ale ponieważ złapaliśmy je również, nie będziemy nawet wiedzieć, że tam się pojawiły i nie stracimy żadnej pomocy, aby poprawnie debugować.

Ale są też bardziej niebezpieczne wyjątki, na które nie jesteśmy przygotowani. Na przykład SystemError jest zwykle czymś, co zdarza się rzadko i na co tak naprawdę nie możemy zaplanować; oznacza to, że dzieje się coś bardziej skomplikowanego, coś, co prawdopodobnie uniemożliwia nam kontynuowanie bieżącego zadania.

W każdym razie jest bardzo mało prawdopodobne, że jesteś przygotowany na wszystko w małej części kodu, więc tak naprawdę powinieneś wychwycić tylko te wyjątki, na które jesteś przygotowany. Niektórzy sugerują, przynajmniej połowu Exception, ponieważ nie będzie obejmować takie rzeczy jak SystemExiti KeyboardInterruptktóry według projektu mają zakończyć aplikację, ale jestem zdania, że jest to nadal zbyt niespecyficzne. Jest tylko jedno miejsce, w którym osobiście akceptuję łapanie Exceptionlub tylko dowolnewyjątek, który znajduje się w jednym globalnym module obsługi wyjątków na poziomie aplikacji, którego jedynym celem jest zarejestrowanie każdego wyjątku, na który nie byliśmy przygotowani. W ten sposób możemy zachować tyle informacji o nieoczekiwanych wyjątkach, które możemy następnie wykorzystać do rozszerzenia naszego kodu, aby jawnie obsługiwać te wyjątki (jeśli możemy je odzyskać) lub - w przypadku błędu - utworzyć przypadki testowe, aby upewnić się to się więcej nie powtórzy. Ale oczywiście działa to tylko wtedy, gdy wychwycimy tylko te wyjątki, których się już spodziewaliśmy, więc te, których się nie spodziewaliśmy, naturalnie wybuchną.

Staraj się unikać wchodzenia, z wyjątkiem bloków

Gdy wyraźnie wychwytuje się niewielki wybór konkretnych wyjątków, istnieje wiele sytuacji, w których wszystko będzie dobrze, po prostu nic nie robiąc. W takich przypadkach samo posiadanie except SomeSpecificException: passjest w porządku. Jednak przez większość czasu tak nie jest, ponieważ prawdopodobnie potrzebujemy trochę kodu związanego z procesem odzyskiwania (jak wspomniano powyżej). Może to być na przykład coś, co ponownie próbuje wykonać akcję lub zamiast tego ustawić wartość domyślną.

Jeśli tak nie jest, na przykład dlatego, że nasz kod jest już tak skonstruowany, aby powtarzał się, dopóki się nie powiedzie, to wystarczy przekazać. Biorąc nasz przykład z góry, możemy poprosić użytkownika o wprowadzenie numeru. Ponieważ wiemy, że użytkownicy nie chcą robić tego, o co ich proszymy, możemy po prostu umieścić to w pętli, aby wyglądało to tak:

def askForNumber ():
    while True:
        try:
            return int(input('Please enter a number: '))
        except ValueError:
            pass

Ponieważ próbujemy, dopóki nie zostanie zgłoszony żaden wyjątek, nie musimy robić nic specjalnego w bloku oprócz, więc jest w porządku. Ale oczywiście można argumentować, że przynajmniej chcemy pokazać użytkownikowi komunikat o błędzie, aby powiedzieć mu, dlaczego musi powtórzyć dane wejściowe.

Jednak w wielu innych przypadkach samo przekazanie znaku exceptjest znakiem, że tak naprawdę nie byliśmy przygotowani na wyjątek, który łapiemy. O ile te wyjątki nie są proste (jak ValueErrorlub TypeError), a powód, dla którego możemy przejść, jest oczywisty, staraj się unikać po prostu przejścia. Jeśli naprawdę nie ma nic do zrobienia (i jesteś absolutnie pewien), rozważ dodanie komentarza, dlaczego tak jest; w przeciwnym razie rozwiń blok oprócz, aby faktycznie zawierał kod odzyskiwania.

except: pass

Najgorszym sprawcą jest jednak połączenie obu. Oznacza to, że chętnie wychwytujemy każdy błąd, chociaż absolutnie nie jesteśmy na to przygotowani i nie robimy z tym nic. Ci przynajmniej chcą zalogować błąd i prawdopodobnie również przebić go jeszcze zakończyć działanie aplikacji (jest to mało prawdopodobne, można kontynuować jak normalne po MemoryError). Samo przekazanie nie tylko utrzyma działanie aplikacji (oczywiście w zależności od tego, gdzie złapiesz), ale także wyrzuci wszystkie informacje, uniemożliwiając wykrycie błędu - co jest szczególnie prawdziwe, jeśli to nie ty go odkrywasz.


Więc sedno jest następujące: Złap tylko wyjątki, których naprawdę oczekujesz i jesteś przygotowany na powrót do zdrowia; wszystkie inne to albo błędy, które powinieneś naprawić, albo coś, na co i tak nie jesteś przygotowany. Przekazywanie określonych wyjątków jest w porządku, jeśli naprawdę nie musisz nic z nimi robić. We wszystkich innych przypadkach jest to tylko znak domniemania i lenistwa. I na pewno chcesz to naprawić.

szturchać
źródło
1
„Przynajmniej chcesz zarejestrować błąd, a także prawdopodobnie poprawić go, aby nadal kończyć aplikację”. Czy potrafisz zademonstrować, jak „wyhodować” wyjątek, aby mógł nadal bulgotać nawet po jego złapaniu? Wydaje mi się to przydatne, aby dodać niestandardowe komunikaty o błędach, jednocześnie pozwalając wyjątkowi wymusić zamknięcie aplikacji.
Gabriel Staples
1
Pomaga to wyjaśnić: używają koca except, ale następnie dzwonią raisebez argumentów, aby kontynuować tworzenie wyjątku, co powoduje zakończenie aplikacji. Podoba mi się: ianbicking.org/blog/2007/09/re-raising-exceptions.html . Wygląda na solidny wyjątek od reguły nieużywania koca except.
Gabriel Staples
1
@GabrielStaples Tak, złapany wyjątek można odtworzyć za pomocą raise. Zwykle robisz to tylko w kilku lokalizacjach w aplikacji, aby zarejestrować wyjątek.
szturcha
To jest świetne, unikaj przekazywania z wyjątkiem bloków. Powiedziałbym, że rób wszystko, co wydaje się bardziej zrozumiałe, szczególnie dla innych. Uzyskaj drugi zestaw oczu Pythona, aby przejrzeć kod i sprawdzić, czy kwestionują blok. Czytelność jest kluczem.
radtek
262

Główny problem polega na tym, że ignoruje on wszystkie błędy: brak pamięci, procesor się pali, użytkownik chce się zatrzymać, program chce wyjść, Jabberwocky zabija użytkowników.

To zdecydowanie za dużo. W twojej głowie myślisz „Chcę zignorować ten błąd sieci”. Jeśli coś nieoczekiwanego pójdzie nie tak, twój kod po cichu kontynuuje i psuje się w całkowicie nieprzewidywalny sposób, którego nikt nie może debugować.

Dlatego powinieneś ograniczyć się do ignorowania tylko niektórych błędów, a resztę przejść.

Aaron Digulla
źródło
75

Wykonanie pseudo kodu dosłownie nie powoduje żadnego błędu:

try:
    something
except:
    pass

tak jakby to był całkowicie poprawny fragment kodu, zamiast rzucać NameError. Mam nadzieję, że nie tego chcesz.

YS-L
źródło
51

Dlaczego „oprócz: pass” jest złą praktyką programistyczną?

Dlaczego to takie złe?

try:
    something
except:
    pass

Przechwytuje każdy możliwy wyjątek, w tym GeneratorExit, KeyboardInterrupti SystemExit- które są wyjątkami, których prawdopodobnie nie zamierzasz wychwycić. To tak samo jak łapanie BaseException.

try:
    something
except BaseException:
    pass

Starsze wersje dokumentacji mówią :

Ponieważ każdy błąd w Pythonie powoduje wyjątek, użycie except:może sprawić, że wiele błędów programistycznych będzie wyglądać jak problemy w czasie wykonywania, co utrudnia proces debugowania.

Hierarchia wyjątków Python

Jeśli złapiesz nadrzędną klasę wyjątków, złapiesz także wszystkie ich klasy podrzędne. Bardziej eleganckie jest wychwytywanie tylko wyjątków, z którymi jesteś przygotowany.

Oto hierarchia wyjątków Python 3 - czy naprawdę chcesz je wszystkie złapać ?:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

Nie rób tego

Jeśli używasz tej formy obsługi wyjątków:

try:
    something
except: # don't just do a bare except!
    pass

Wtedy nie będziesz w stanie przerwać somethingbloku za pomocą Ctrl-C. Twój program przeoczy każdy możliwy wyjątek w trybloku kodu.

Oto kolejny przykład, który będzie miał takie samo niepożądane zachowanie:

except BaseException as e: # don't do this either - same as bare!
    logging.info(e)

Zamiast tego spróbuj złapać tylko określony wyjątek, którego szukasz. Na przykład, jeśli wiesz, że podczas konwersji może wystąpić błąd wartości:

try:
    foo = operation_that_includes_int(foo)
except ValueError as e:
    if fatal_condition(): # You can raise the exception if it's bad,
        logging.info(e)   # but if it's fatal every time,
        raise             # you probably should just not catch it.
    else:                 # Only catch exceptions you are prepared to handle.
        foo = 0           # Here we simply assign foo to 0 and continue. 

Dalsze objaśnienia z innym przykładem

Być może robisz to, ponieważ robisz przeglądanie stron internetowych i dostajesz odpowiedź UnicodeError, ale ponieważ użyłeś najszerszego wychwytywania wyjątków, Twój kod, który może mieć inne fundamentalne wady, będzie próbował działać do końca, marnując przepustowość , czas przetwarzania, zużycie sprzętu, brak pamięci, zbieranie danych śmieci itp.

Jeśli inne osoby proszą cię o wypełnienie, aby mogły polegać na twoim kodzie, rozumiem, że czuję się zmuszony do obsługi wszystkiego. Ale jeśli zechcesz głośno upaść w miarę rozwoju, będziesz miał możliwość naprawienia problemów, które mogą pojawiać się sporadycznie, ale byłyby to długotrwałe kosztowne błędy.

Dzięki bardziej precyzyjnej obsłudze błędów kod może być bardziej niezawodny.

Aaron Hall
źródło
31
>>> import this

Zen of Python, autor: Tim Peters

Piękne jest lepsze niż brzydkie.
Jawne jest lepsze niż niejawne.
Prosty jest lepszy niż złożony.
Złożony jest lepszy niż skomplikowany.
Mieszkanie jest lepsze niż zagnieżdżone.
Rzadki jest lepszy niż gęsty.
Liczy się czytelność.
Przypadki specjalne nie są wystarczająco wyjątkowe, aby złamać zasady.
Chociaż praktyczność przewyższa czystość.
Błędy nigdy nie powinny przejść bezgłośnie.
Chyba że wyraźnie uciszony.
W obliczu dwuznaczności odmów pokusy zgadywania.
Powinien być jeden - a najlepiej tylko jeden - oczywisty sposób na zrobienie tego.
Chociaż na początku sposób ten może nie być oczywisty, chyba że jesteś Holendrem.
Teraz jest lepiej niż nigdy.
Chociaż nigdy nie jest często lepszy niżwłaśnie teraz.
Jeśli implementacja jest trudna do wyjaśnienia, to zły pomysł.
Jeśli implementacja jest łatwa do wyjaśnienia, może to być dobry pomysł.
Przestrzenie nazw to jeden świetny pomysł - zróbmy ich więcej!

Oto moja opinia. Ilekroć znajdziesz błąd, powinieneś zrobić coś, aby sobie z nim poradzić, tj. Zapisać go w pliku dziennika lub coś innego. Przynajmniej informuje cię, że kiedyś był błąd.

Wzmacniacz
źródło
64
-1 Argument władzy nie wyjaśnia niczego. Autorytet może się mylić.
Izkata
23
co napisał @Izkata ORAZ, jeden wiersz poniżej tego samego autorytetu pisze: „O ile nie zostało to wyraźnie uciszone”, co jest dokładnie tym, co robi: oprócz pass.
Ofri Raviv
13
@OfriRaviv Nie, czy błąd nie jest przekazywany niejawnie ? Jawnie wymagałoby nazwania błędu, który powinien zostać przekazany w trybie cichym, to znaczy jawnego wyrażenia się na ten temat . To nie jest to, z wyjątkiem: pass.
Chelonian
24

Powinieneś użyć przynajmniej, except Exception:aby uniknąć wyłapywania wyjątków systemowych, takich jak SystemExitlub KeyboardInterrupt. Oto link do dokumentów.

Zasadniczo należy wyraźnie zdefiniować wyjątki, które chcesz przechwycić, aby uniknąć przechwytywania niepożądanych wyjątków. Powinieneś wiedzieć, jakie wyjątki ignorujesz .

Tupteq
źródło
13

Po pierwsze, narusza dwie zasady Zen Pythona :

  • Jawne jest lepsze niż niejawne
  • Błędy nigdy nie powinny przejść bezgłośnie

Oznacza to, że umyślnie sprawiasz, że błąd cicho przechodzi. Co więcej, nie wiesz, który błąd dokładnie wystąpił, ponieważ except: passzłapie każdy wyjątek.

Po drugie, jeśli spróbujemy oderwać się od Zen Pythona i mówić w kategoriach czystego rozsądku, powinieneś wiedzieć, że używanie nie except:passpozostawia cię bez wiedzy i kontroli w twoim systemie. Zasadą ogólną jest zgłoszenie wyjątku w przypadku wystąpienia błędu i podjęcie odpowiednich działań. Jeśli nie wiesz z góry, jakie to powinny być działania, przynajmniej zaloguj gdzieś błąd (i lepiej ponownie podnieś wyjątek):

try:
    something
except:
    logger.exception('Something happened')

Ale zwykle, jeśli próbujesz złapać jakiś wyjątek, prawdopodobnie robisz coś złego!

Aleksander Żukow
źródło
2
... O ile nie zostało to wyraźnie uciszone, co ma miejsce w przypadku OP.
Hyperboreus
Chcę poznać twoje rozwiązanie. W rzeczywistości, kiedy naprawdę nic nie trzeba robić, po prostu wymieniam błędy oprócz i robię komentarze i zapisuję logi. Więc po prostu przejdź.
Booster
2
@Hyperboreus, nie sądzę, że wyłapanie wszystkich błędów i jakichkolwiek błędów jest jawnym wyciszeniem ich, tzn. Nawet nie wiesz, co łapiesz.
Alexander Zhukov
13
„Ponieważ ktoś tak mówi” nie jest tak naprawdę odpowiedzią na „dlaczego?” pytanie.
Sebastian Negraszus
12

except:passKonstrukt zasadniczo wycisza wszelkie nadzwyczajne warunki, które pojawią się natomiast kod objęte w try:bloku jest prowadzony.

Co sprawia, że ​​ta zła praktyka jest taka, że ​​zwykle nie jest to, czego naprawdę chcesz. Częściej pojawia się jakiś szczególny warunek, który chcesz wyciszyć, i except:passjest to zbyt tępy instrument. Wykona to zadanie, ale także zamaskuje inne warunki błędu, których prawdopodobnie nie spodziewałeś się, ale z dużym prawdopodobieństwem zechcesz sobie z nimi poradzić w inny sposób.

To, co sprawia, że ​​jest to szczególnie ważne w Pythonie, polega na tym, że według idiomów tego języka wyjątki niekoniecznie są błędami . Oczywiście są one często używane w ten sposób, podobnie jak w większości języków. Ale w szczególności Python od czasu do czasu używał ich do implementacji alternatywnej ścieżki wyjścia z niektórych zadań kodu, która tak naprawdę nie jest częścią normalnego działającego przypadku, ale wciąż wiadomo, że pojawia się od czasu do czasu, a nawet w większości przypadków można się tego spodziewać. SystemExitzostał już wspomniany jako stary przykład, ale najczęstszym przykładem może być obecnie StopIteration. Stosowanie wyjątków w ten sposób wywołało wiele kontrowersji, szczególnie gdy iteratory i generatory zostały po raz pierwszy wprowadzone do Pythona, ale ostatecznie pomysł zwyciężył.

The Spooniest
źródło
12

Powód nr 1 został już podany - ukrywa błędy, których się nie spodziewałeś.

(# 2) - Utrudnia innym czytanie i zrozumienie kodu. Jeśli złapiesz wyjątek FileNotFoundException podczas próby odczytania pliku, to dla innego programisty oczywiste jest, jaką funkcjonalność powinien mieć blok „catch”. Jeśli nie określisz wyjątku, potrzebujesz dodatkowych komentarzy, aby wyjaśnić, co powinien zrobić blok.

(# 3) - Pokazuje leniwe programowanie. Jeśli używasz standardowego try / catch, oznacza to, że albo nie rozumiesz możliwych błędów w czasie wykonywania programu, albo że nie wiesz, jakie wyjątki są możliwe w Pythonie. Złapanie określonego błędu pokazuje, że rozumiesz zarówno swój program, jak i zakres błędów, które generuje Python. Jest to bardziej prawdopodobne, że inni programiści i recenzenci kodu ufają Twojej pracy.

Kevin
źródło
12

Jakie wyniki generuje ten kod?

fruits = [ 'apple', 'pear', 'carrot', 'banana' ]

found = False
try:
     for i in range(len(fruit)):
         if fruits[i] == 'apple':
             found = true
except:
     pass

if found:
    print "Found an apple"
else:
    print "No apples in list"

Teraz wyobraź sobie, że try- exceptblok to setki linii wywołań do złożonej hierarchii obiektów i sam jest wywoływany w środku drzewa wywołań dużego programu. Kiedy program się nie powiedzie, gdzie zaczniesz szukać?

Ian Harvey
źródło
5
Eee, dzięki ludziom, którzy to „poprawili”, ale proszę, nie rób tego - celowo jest źle, w sensie „pytania wywiadu”. Być może jest to bardziej subtelne, niż się wydaje - spróbuj. Chodzi mi o to, że zmiażdżenie wyjątków „wszystkich”, szczególnie w Pythonie, utrudnia debugowanie, nawet w tuzinach tuzinów kodu.
Ian Harvey
11

Zasadniczo można sklasyfikować dowolny błąd / wyjątek w jednej z trzech kategorii :

  • Śmiertelne : to nie twoja wina, nie możesz im zapobiec, nie możesz się od nich odzyskać. Z pewnością nie powinieneś ich ignorować, kontynuować i pozostawić swój program w nieznanym stanie. Pozwól, aby błąd zakończył działanie twojego programu, nic nie możesz zrobić.

  • Boneheaded : Twoja wina, najprawdopodobniej z powodu niedopatrzenia, błędu lub błędu programowania. Powinieneś naprawić błąd. Ponownie, z całą pewnością nie powinieneś ignorować i kontynuować.

  • Egzogeniczny : możesz spodziewać się tych błędów w wyjątkowych sytuacjach, takich jak nie znaleziono pliku lub połączenie zostało przerwane . Powinieneś jawnie obsługiwać te błędy i tylko te.

We wszystkich przypadkach except: passprogram pozostawi tylko nieznany stan, w którym może spowodować więcej szkód.

Daniel AA Pelsmaeker
źródło
6

Mówiąc wprost, jeśli zgłoszony zostanie wyjątek lub błąd, coś jest nie tak. Może nie jest to coś bardzo złego, ale tworzenie, zgłaszanie i wyłapywanie błędów i wyjątków tylko ze względu na używanie instrukcji goto nie jest dobrym pomysłem i rzadko się to robi. W 99% przypadków wystąpił problem.

Problemy muszą być rozwiązywane. Podobnie jak w życiu, w programowaniu, jeśli po prostu zostawiasz problemy w spokoju i próbujesz je zignorować, nie tyle same znikają wiele razy; zamiast tego stają się większe i rozmnażają się. Aby zapobiec narastaniu problemu i ponownemu uderzeniu w dalszą drogę, albo 1) wyeliminuj go i posprzątaj bałagan później, albo 2) powstrzymaj go i posprzątaj bałagan później.

Zignorowanie wyjątków i błędów i pozostawienie ich w ten sposób jest dobrym sposobem na doświadczenie wycieków pamięci, wyjątkowych połączeń z bazą danych, niepotrzebnych blokad uprawnień do plików itp.

W rzadkich przypadkach problem jest tak drobny, trywialny i - oprócz konieczności spróbowania ... złapać blok - samowystarczalny , że tak naprawdę nie ma po prostu bałaganu, który mógłby zostać później oczyszczony. Są to jedyne sytuacje, w których ta najlepsza praktyka niekoniecznie ma zastosowanie. Z mojego doświadczenia wynika, że ​​ogólnie rzecz biorąc oznacza to, że cokolwiek robi kod, jest w zasadzie drobiazgowe i możliwe do uniknięcia, a coś takiego jak próby ponownych prób lub specjalne wiadomości nie są warte ani złożoności, ani podtrzymania wątku.

W mojej firmie regułą jest, aby prawie zawsze robić coś w bloku catch, a jeśli nic nie robisz, musisz zawsze zamieszczać komentarz z bardzo dobrym powodem, dla którego nie. Nigdy nie wolno przechodzić ani wychodzić z pustego bloku catch, gdy jest coś do zrobienia.

Panzercrisis
źródło
6

Moim zdaniem błędy mają swój powód, by wydawać się głupie, ale tak już jest. Dobre programowanie powoduje błędy tylko wtedy, gdy trzeba je obsłużyć. Ponadto, jak czytałem jakiś czas temu, „instrukcja pass jest instrukcją, że kod programu zostanie wstawiony później”, więc jeśli chcesz mieć pustą instrukcję wyjątkiem, możesz to zrobić, ale dla dobrego programu będzie być częścią brakuje. ponieważ nie radzisz sobie z rzeczami, które powinieneś mieć. Pojawiające się wyjątki dają szansę na poprawienie danych wejściowych lub zmianę struktury danych, aby wyjątki te nie występowały ponownie (ale w większości przypadków (wyjątki sieciowe, ogólne wyjątki wejściowe) wyjątki wskazują, że kolejne części programu nie będą działać poprawnie. Na przykład wyjątek NetworkException może wskazywać na przerwane połączenie sieciowe, a program nie może wysłać / odebrać danych w kolejnych krokach programu.

Ale użycie bloku pass tylko dla jednego bloku wykonania jest poprawne, ponieważ nadal rozróżniasz typy wyjątków, więc jeśli umieścisz wszystkie bloki wyjątków w jednym, nie jest ono puste:

try:
    #code here
except Error1:
    #exception handle1

except Error2:
    #exception handle2
#and so on

można w ten sposób przepisać:

try:
    #code here
except BaseException as e:
    if isinstance(e, Error1):
        #exception handle1

    elif isinstance(e, Error2):
        #exception handle2

    ...

    else:
        raise

Tak więc nawet wiele bloków wyjątków z instrukcjami pass może skutkować kodem, którego struktura obsługuje specjalne typy wyjątków.

Sirac
źródło
4

Wszystkie poruszone dotąd komentarze są ważne. Tam, gdzie to możliwe, musisz określić, jaki dokładnie wyjątek chcesz zignorować. Tam, gdzie to możliwe, musisz przeanalizować przyczynę wyjątku i zignorować tylko to, co chciałeś zignorować, a nie resztę. Jeśli wyjątek powoduje, że aplikacja „spektakularnie ulega awarii”, to niech tak będzie, ponieważ ważniejsze jest, aby wiedzieć, że nieoczekiwane zdarzenie miało miejsce, gdy się ono zdarzyło, niż ukrywanie, że problem kiedykolwiek wystąpił.

Biorąc to wszystko pod uwagę, nie bierz żadnej praktyki programowania jako najważniejszej. To jest głupie. Zawsze jest czas i miejsce, aby zrobić blok ignorowania wszystkich wyjątków.

Innym przykładem idiotycznego znaczenia jest użycie gotooperatora. Kiedy byłem w szkole, nasz profesor nauczył nas gotooperatora, aby wspominał, że NIGDY nie będziesz go używał. Nie wierz ludziom, którzy mówią ci, że xyz nigdy nie powinien być używany i nie może być scenariusza, gdy jest on użyteczny. Zawsze jest.

galety
źródło
1
Przypadek „goto” ma charakter stylistyczny i stanowi opinię, podczas gdy „oprócz: pass” jest zwykle błędne pod względem faktycznym. Zakłada, że ​​jeśli ktoś miałby na przykład „zabić -TERM” proces w tym momencie, powinien go zignorować. Przynajmniej takie złe zachowanie.
Score_Under
1
@Score_Under jeszcze istnieją przypadki, w których jest to właściwe do użycia. Na przykład, gdy wywoływana funkcja jest uzupełniająca, niewiadomego pochodzenia / autora, nie wpływa na podstawową funkcjonalność, ale jeśli awarie mogą powodować problemy. Zdaję sobie sprawę, że twierdzisz, że takie połączenia powinny być właściwie badane i analizowane, ale w prawdziwym życiu nie zawsze jest to możliwe.
galets
Jeśli jednak chcę zakończyć proces, zabicie -9 nie powinno być jedyną niezawodną opcją.
Score_Under
2

Obsługa błędów jest bardzo ważna w programowaniu. Musisz pokazać użytkownikowi, co poszło nie tak. W bardzo niewielu przypadkach można zignorować błędy. Jest to bardzo zła praktyka programowania.

fastcodejava
źródło
2

Ponieważ nie zostało to jeszcze wspomniane, lepiej jest użyć stylu contextlib.suppress:

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

Zauważ, że w podanym przykładzie stan programu pozostaje taki sam, niezależnie od tego, czy wystąpi wyjątek. Oznacza to, że somefile.tmpzawsze przestaje istnieć.

Mateen Ulhaq
źródło