Il consenso generale "non usare eccezioni!" Proviene principalmente da altre lingue e anche a volte è obsoleto.
In C ++, generare un'eccezione è molto costoso causa dello "svolgersi dello stack". Ogni dichiarazione di variabile locale è come with
un'istruzione in Python e l'oggetto in quella variabile può eseguire distruttori. Questi distruttori vengono eseguiti quando viene generata un'eccezione, ma anche al ritorno da una funzione. Questo "linguaggio RAII" è una caratteristica del linguaggio integrale ed è estremamente importante per scrivere codice solido e corretto, quindi RAII contro eccezioni economiche era un compromesso che il C ++ ha deciso verso RAII.
All'inizio del C ++, un sacco di codice non veniva scritto in modo sicuro dalle eccezioni: a meno che non si utilizzi effettivamente RAII, è facile perdere la memoria e altre risorse. Quindi, lanciare eccezioni renderebbe quel codice errato. Questo non è più ragionevole poiché anche la libreria standard C ++ usa eccezioni: non si può pretendere che non esistano eccezioni. Tuttavia, le eccezioni sono ancora un problema quando si combina il codice C con C ++.
In Java, ogni eccezione ha una traccia dello stack associata. La traccia dello stack è molto utile quando si eseguono errori di debug, ma è uno sforzo sprecato quando l'eccezione non viene mai stampata, ad esempio perché è stata utilizzata solo per il flusso di controllo.
Quindi in quelle lingue le eccezioni sono "troppo costose" per essere utilizzate come flusso di controllo. In Python questo è meno un problema e le eccezioni sono molto più economiche. Inoltre, il linguaggio Python soffre già di alcune spese generali che rendono impercettibile il costo delle eccezioni rispetto ad altri costrutti del flusso di controllo: ad esempio verificare se esiste una voce dict con un test di appartenenza esplicito if key in the_dict: ...
è in genere esattamente veloce quanto accedere semplicemente alla voce the_dict[key]; ...
e verificare se si ottenere un KeyError. Alcune funzionalità linguistiche integrate (ad es. Generatori) sono progettate in termini di eccezioni.
Pertanto, sebbene non vi siano motivi tecnici per evitare in modo specifico le eccezioni in Python, esiste ancora la questione se si debbano usare al posto dei valori di ritorno. I problemi a livello di progettazione con le eccezioni sono:
non sono affatto ovvi. Non puoi facilmente guardare una funzione e vedere quali eccezioni può generare, quindi non sai sempre cosa catturare. Il valore restituito tende ad essere più ben definito.
le eccezioni sono il flusso di controllo non locale che complica il tuo codice. Quando si genera un'eccezione, non si sa dove riprenderà il flusso di controllo. Per errori che non possono essere gestiti immediatamente, questa è probabilmente una buona idea, quando notificare al chiamante una condizione è del tutto superflua.
La cultura Python è generalmente inclinata a favore delle eccezioni, ma è facile esagerare. Immagina una list_contains(the_list, item)
funzione che controlla se l'elenco contiene un oggetto uguale a quell'elemento. Se il risultato viene comunicato tramite eccezioni che è assolutamente fastidioso, perché dobbiamo chiamarlo così:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Restituire un bool sarebbe molto più chiaro:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Se la funzione dovrebbe già restituire un valore, quindi restituire un valore speciale per condizioni speciali è piuttosto soggetto a errori, perché le persone dimenticheranno di controllare questo valore (questa è probabilmente la causa di 1/3 dei problemi in C). Un'eccezione è generalmente più corretta.
Un buon esempio è una pos = find_string(haystack, needle)
funzione che cerca la prima occorrenza della needle
stringa nella stringa `pagliaio e restituisce la posizione iniziale. E se la corda del pagliaio non contiene la corda dell'ago?
La soluzione di C e imitata da Python è di restituire un valore speciale. In C questo è un puntatore nullo, in Python lo è -1
. Ciò porterà a risultati sorprendenti quando la posizione viene utilizzata come indice di stringa senza controllo, soprattutto perché -1
è un indice valido in Python. In C, il puntatore NULL ti darà almeno un segfault.
In PHP, viene restituito un valore speciale di un tipo diverso: il valore booleano FALSE
anziché un numero intero. A quanto pare questo non è in realtà migliore a causa delle implicite regole di conversione del linguaggio (ma nota che in Python anche i booleani possono essere usati come ints!). Le funzioni che non restituiscono un tipo coerente sono generalmente considerate molto confuse.
Una variante più solida sarebbe stata quella di lanciare un'eccezione quando non è possibile trovare la stringa, il che assicura che durante il normale flusso di controllo sia impossibile utilizzare accidentalmente il valore speciale al posto di un valore ordinario:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
In alternativa, è possibile utilizzare sempre la restituzione di un tipo che non può essere utilizzato direttamente ma che deve essere prima scartato, ad esempio una tupla booleana di risultati in cui il booleano indica se si è verificata un'eccezione o se il risultato è utilizzabile. Poi:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Questo ti costringe a gestire immediatamente i problemi, ma diventa fastidioso molto rapidamente. Ti impedisce anche di concatenare facilmente la funzione. Ogni chiamata di funzione ora richiede tre righe di codice. Golang è un linguaggio che pensa che questo disturbo sia degno della sicurezza.
Quindi, per riassumere, le eccezioni non sono del tutto prive di problemi e possono essere definitivamente abusate, soprattutto quando sostituiscono un valore di ritorno "normale". Ma quando vengono utilizzati per segnalare condizioni speciali (non necessariamente solo errori), le eccezioni possono aiutarti a sviluppare API pulite, intuitive, facili da usare e difficili da usare in modo improprio.