Perché le eccezioni sono considerate migliori del test di errore esplicito? [duplicare]

43

Spesso mi imbatto in post di blog riscaldati in cui l'autore utilizza l'argomento: "eccezioni vs controllo degli errori espliciti" per sostenere la sua lingua preferita su un'altra lingua. Il consenso generale sembra essere che i linguaggi che fanno uso di eccezioni sono intrinsecamente migliori / più puliti delle lingue che si basano in gran parte sul controllo degli errori attraverso chiamate di funzioni esplicite.

L'uso delle eccezioni è considerato una prassi di programmazione migliore rispetto al controllo esplicito degli errori, e in tal caso, perché?

    
posta Richard Keller 25.09.2012 - 01:46
fonte

10 risposte

62

Nella mia mente, l'argomento più importante è la differenza in ciò che accade quando il programmatore commette un errore. Dimenticare di gestire un errore è un errore molto comune e facile da fare.

Se si restituiscono codici di errore, è possibile ignorare silenziosamente un errore. Ad esempio, se malloc fallisce, restituisce NULL e imposta il errno globale. Quindi il codice corretto dovrebbe fare

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

Ma è molto semplice e opportuno scrivere invece:

void* myptr = malloc(1024);
doSomethingWith(myptr);

che inaspettatamente supererà NULL nella tua altra procedura e probabilmente scarterà errno che è stata accuratamente impostata. Non c'è nulla di visibilmente sbagliato nel codice per indicare che questo è possibile.

In una lingua che usa le eccezioni, dovresti scrivere

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

In questo esempio (Java), l'operatore new restituisce un oggetto inizializzato valido o genera OutOfMemoryError . Se un programmatore deve gestire questo, possono prenderlo. Nel solito (e convenientemente, anche il pigro) caso in cui si tratta di un errore fatale, la propagazione delle eccezioni termina il programma in modo relativamente pulito ed esplicito.

Questo è uno dei motivi per cui le eccezioni, se utilizzate correttamente, possono rendere molto più semplice la scrittura di codice chiaro e sicuro. Questo schema si applica a molte, molte cose che possono andare storte, non solo allocare memoria.

    
risposta data 25.09.2012 - 02:03
fonte
57

Mentre la risposta di Steven fornisce una buona spiegazione, c'è un altro punto che trovo piuttosto importante. A volte, quando controlli un codice di errore, non puoi gestire immediatamente il caso di errore. È necessario propagare l'errore in modo esplicito attraverso lo stack di chiamate. Quando si rifatta una funzione di grandi dimensioni, potrebbe essere necessario aggiungere tutti i codici di codice di controllo degli errori alla sottofunzione.

Con le eccezioni, devi solo occuparti del tuo flusso principale. Se una parte di codice genera un InvalidOperationError, puoi comunque spostare quel codice in una funzione secondaria e la logica di gestione degli errori verrà mantenuta.

Quindi eccezioni ti permettono di effettuare il refactoring più velocemente e di evitare il piatto della caldaia.

    
risposta data 25.09.2012 - 03:09
fonte
17

Un punto di vista da una diversa angolazione: la gestione degli errori riguarda esclusivamente la sicurezza. Un errore non controllato interrompe tutte le ipotesi e le precondizioni su cui si basava il codice seguente. Ciò potrebbe aprire molti vettori di attacco esterni: da un semplice DoS a dati non autorizzati, accesso al danneggiamento dei dati e completa infiltrazione di sistema.

Certo, dipende dall'applicazione specifica, ma quando le ipotesi e le condizioni preliminari sono interrotte, tutte le scommesse sono disattivate. All'interno di un software complesso, non puoi semplicemente dire con certezza cosa è possibile da quel momento in poi, e cosa può e non può essere usato da extern.

Dato questo, c'è un'osservazione fondamentale: quando la sicurezza viene trattata come un componente aggiuntivo facoltativo che può essere allegato in un secondo momento, fallisce il più delle volte. Funziona meglio quando è già stato preso in considerazione nelle primissime fasi di progettazione di base e integrato sin dall'inizio.

Questo è ciò che fondamentalmente ottieni con le eccezioni: un'infrastruttura di gestione degli errori già integrata che è attiva anche se non ti interessa. Con test espliciti, devi costruirlo da solo. Devi costruirlo come primo passo. Basta iniziare a scrivere le funzioni che restituiscono i codici di errore, senza pensare al quadro generale fino a quando non si è nelle fasi finali dello sviluppo dell'applicazione, è effettivamente una gestione degli errori del componente aggiuntivo, destinata a fallire.

Non che un sistema integrato aiuti in alcun modo. La maggior parte dei programmatori non sa come gestire gli errori. È semplicemente troppo complessa la maggior parte del tempo.

  • Ci sono così tanti errori che possono accadere e ogni errore richiede la sua stessa gestione e le sue azioni e reazioni.

  • Anche lo stesso errore può richiedere diverse azioni, in base al contesto. Che ne dici di un file non trovato o di una memoria insufficiente?

    • FNF - Una libreria di terze parti necessaria per l'esecuzione della tua app? Termina.
    • FNF - Il file di configurazione iniziale della tua applicazione? Inizia con le impostazioni predefinite.
    • FNF - Un file di dati su una rete condivide l'utente che vuole che la tua app si apra? La scomparsa dei file può avvenire in qualsiasi momento, anche nei microsecondi tra Exists () e Open (). Non è nemmeno una "eccezione" nel significato letterale della parola.
    • OOM - Per un'allocazione di 2 GB? Nessuna sorpresa.
    • OOM - Per un'assegnazione di 100 byte? Sei nei guai seri.
  • Errore nel gestire le perdite attraverso tutti i livelli di astrazione accuratamente separati. Un errore nel livello più basso potrebbe richiedere di notificare all'utente un messaggio della GUI. Potrebbe richiedere una decisione da parte dell'utente per cosa fare ora. Potrebbe essere necessario il log. Potrebbe essere necessario eseguire operazioni di ripristino in un'altra parte, ad es. aprire database o connessioni di rete. Ecc.

  • E lo stato? Quando chiami un metodo su un oggetto che richiama le modifiche di stato e genera un errore:

    • L'oggetto è incoerente e necessita di una ricostruzione?
    • Lo stato dell'oggetto è coerente ma (parzialmente) modificato e richiede un ulteriore rollback o una ricostruzione?
    • È stato eseguito un rollback all'interno dell'oggetto e rimane invariato per il chiamante?
    • Dove è anche ragionevole fare cosa?

Mostrami semplicemente il libro di uno studente in cui la gestione degli errori è rigorosamente progettata sin dall'inizio e di conseguenza utilizzata attraverso tutti gli esempi, senza essere tralasciata per brevità e leggibilità e come esercitazione per il lettore. Se questo è applicabile da un POV educativo è un'altra domanda, ma non sorprende che la gestione degli errori sia spesso un secondo o terzo pensiero quando dovrebbe essere il primo.

    
risposta data 25.09.2012 - 10:05
fonte
14

Mi piacerebbe pensare alle eccezioni come ...

Ti permettono di scrivere codice nel modo in cui tende naturalmente a essere scritto

Quando scrivi il codice, c'è una serie di cose che il cervello umano ha bisogno di destreggiarsi:

  • Qual è la logica che sto tentando di esprimere qui?
  • Come faccio a farlo nella lingua XXXX?
  • In che modo questo codice si adatta al resto dell'applicazione?
  • Perché continuo a ricevere questi errori di compilazione?
  • Sto dimenticando le condizioni al contorno?
  • Questo codice è leggibile e sufficientemente gestibile?
  • Che succede se qualcosa in questo codice fallisce?
  • Sto producendo codice in uno stile coerente e utilizzo le giuste convenzioni di codifica?

Ciascuno di questi proiettili richiede una certa quantità di concentrazione e la concentrazione è una risorsa limitata. Questo è il motivo per cui le persone che sono nuove in un progetto spesso dimenticano le cose verso il fondo della lista. Non sono persone cattive, ma perché così tante cose potrebbero essere nuove per loro (linguaggio, progetto, strani errori, ecc ...), semplicemente non hanno la capacità di gestire altri proiettili.

Ho fatto la mia parte di recensioni di codice e questa tendenza è molto evidente. Una persona con meno esperienza dimenticherà più cose. Queste cose includono lo stile e una gestione degli errori abbastanza frequente. Quando le persone hanno difficoltà a far funzionare il codice, la gestione degli errori tende ad essere nella parte posteriore della loro mente. A volte tornano indietro e aggiungono alcune dichiarazioni if qui e là, ma state tranquilli, dimenticheranno un sacco di cose.

La gestione delle eccezioni ti consente di scrivere codice in cui la carne della tua logica è scritta come se non ci fossero errori. Si effettua una chiamata di funzione e la riga successiva può semplicemente assumere che la linea precedente sia riuscita. Questo ha un ulteriore vantaggio nel produrre codice che è molto più facile da leggere. Hai mai visto un riferimento API con esempi di codice con un commento "// Gestione degli errori omessa per chiarezza"? Se rimuovono la gestione degli errori nella documentazione per rendere l'esempio più facile da leggere, non sarebbe bello se si potesse fare la stessa cosa nel codice di produzione e renderlo più facile da leggere? Questo è esattamente ciò che le eccezioni ti permettono di fare.

Ora almeno una persona che legge questo post dirà: "pffttt un po 'noob non sa come codificare. Non dimentico mai la gestione degli errori". a) Buono per te, ma soprattutto b) come ho detto sopra, la codifica richiede che il tuo cervello si concentri e che ci sia solo così tanto cervello da girare; questo vale per TUTTI. Se il tuo codice è facile da leggere, è meno concentrato. Se riesci a mettere alla prova / catturare un grosso frammento e poi semplicemente codificare la logica dell'applicazione, c'è meno concentrazione. Ora puoi usare quella capacità extra per cose più importanti, come ottenere il lavoro effettivo molto più velocemente.

    
risposta data 25.09.2012 - 05:40
fonte
6

Vantaggi dell'eccezione di lancio del codice di errore di ritorno:

  • Il valore di ritorno di una funzione può essere utilizzato per ciò che è stato progettato per fare : restituisce il risultato della chiamata di funzione, non un codice che rappresenta il successo o uno dei molti errori all'interno della funzione. Questo crea un codice più pulito e più elegante; un minor numero di parametri di output / riferimento, le assegnazioni vengono assegnate alle cose che realmente ti interessano e non ai codici di status throwaway, ecc.
  • Le eccezioni sono orientate agli oggetti, quindi i tipi di eccezione hanno un significato concettuale riutilizzabile . Cosa significa un codice di ritorno di -2? Bisogna guardare in su nella documentazione (che era meglio essere buono o si è costretti a rintracciare il codice, e se non si dispone di codice sorgente si sta davvero avvitata). Che cosa significa NullPointerException? Che hai provato a chiamare un metodo su una variabile che non fa riferimento a un'istanza. Inoltre, le eccezioni incapsulano i dati, in modo che possano dirti esattamente come e dove il tuo programma si è incasinato in mezzi facilmente leggibili. Un codice di ritorno può solo dirti che il metodo è fallito.
  • Per impostazione predefinita, i codici di ritorno vengono ignorati; le eccezioni non sono . Se non si memorizzano e controllare il codice di ritorno, il programma prosegue allegramente lungo, corrompendo i dati, strizzando puntatori e, in generale si accartocciando in spazzatura. Se non si rileva un'eccezione, viene lanciata al sistema operativo che interrompe il programma. "Fail fast".
  • Le eccezioni sono più facilmente standardizzate . Poiché le eccezioni creare più code di auto-documentazione, e perché la maggior parte di noi non scrivere tutti i nostri programmi al 100% da zero, tipi di eccezione che coprono una vasta gamma di casi generali sono già disponibili. La soluzione più economica è quella che già possiedi, quindi questi tipi di eccezione incorporati vengono utilizzati in situazioni simili a quelle per cui sono stati originariamente creati. Ora, una InvalidOperationException significa che hai provato a fare qualcosa di incoerente con lo stato corrente di un oggetto (esattamente quello che sarebbe stato dettagliato nel messaggio di errore), indipendentemente dalla funzione dalla quale la libreria di terze parti stavi cercando di chiamare.

    Contrasto che con i codici di ritorno; per un metodo, -1 potrebbe essere un "errore puntatore nullo", mentre -2 potrebbe essere un "errore operazione non valida". Nel metodo successivo, la condizione di "operazione non valida" è stata verificata per prima e quindi l'errore ha ottenuto il codice di ritorno -1, mentre "il puntatore nullo" ha ottenuto -2. Un numero è un numero, e si è liberi di creare qualsiasi standard per assegnare i numeri a errori, e così è tutti gli altri, che porta a un sacco di norme concorrenti.

  • Le eccezioni ti consentono di essere più ottimista . Invece di controllare pessimisticamente tutto ciò che potrebbe sbagliare con un'istruzione prima di eseguire effettivamente l'istruzione, puoi semplicemente provare eseguire l'istruzione e catturare qualsiasi eccezioni che sono state generate da esso. Se è possibile risolvere la causa dell'errore e riprovare, ottimo, in caso contrario, bene; probabilmente non avresti potuto fare nulla a riguardo se lo avessi saputo prima.

    La gestione degli errori basata su eccezioni crea quindi un codice più efficiente assumendo che funzioni finché non lo fa. Dichiarazioni di guardia che restituiscono il tempo di elaborazione del costo iniziale; dovrebbero quindi essere usati principalmente quando i benefici del check-up superano i costi; se in alcuni orologi è possibile determinare che l'input non produrrà mai un output valido, ma occorreranno alcuni secondi prima che il corpo principale della funzione giunga alla stessa conclusione, quindi con tutti i mezzi inserire una clausola di guardia. Tuttavia, se ci sono una dozzina di cose che potrebbero andare storte, nessuna delle quali è particolarmente probabile e la maggior parte delle quali richiede costose esecuzioni, il "percorso felice" della normale esecuzione del programma sarà più volte più veloce se si tenta semplicemente di catturare. p>

risposta data 25.09.2012 - 17:55
fonte
5

La differenza fondamentale per me è la lettura e la scrittura:

La gestione dei codici di errore tende a ingombrare l'intento : il codice basato su eccezioni è solitamente più facile da leggere poiché l'origine si concentra sulle cose che dovrebbero accadere, piuttosto che su < em> potrebbe .

OTOH, Un sacco di codice basato su eccezioni è più difficile da scrivere, perché per un'analisi di correttezza devi discutere su dettagli "non correlati", come ordine di costruzione / distruzione, oggetti parzialmente costruiti, garbage collection, ecc.

Quanto è difficile? Questo difficile.

La generazione di eccezioni significative può ingombrare la sorgente, ho basi di codice in cui virtualmente ogni riga di codice è seguita da due o tre linee che creano un'eccezione letterale.

Per trasferire le eccezioni significative al livello principale, spesso devono essere riconfezionati. Quando faccio clic su "celle di rebicker", un messaggio di errore come "Condivisione di violazione su% tmp% \ foo \ file536821" non è molto più utile di "Errore 77" .

In realtà non ho una preferenza, entrambe le opzioni sono ugualmente brutte. Quel che è peggio: quale è meglio sembra dipendere molto dal progetto, in modi difficili da prevedere. Nella mia esperienza, l'interazione hardware di basso livello è più agevole con i codici di errore (potrebbero essere i client ...), l'accesso ai dati di basso livello è migliore con le eccezioni.

    
risposta data 25.09.2012 - 08:51
fonte
3

Mettiamo da parte i problemi di implementazione di molte lingue. Le eccezioni di beneficio sono che possono essere centralizzate per gestire tutti i tipi (in teoria) di stati eccezionali del programma. Il problema che affrontano è che le cose eccezionali accadono e difficilmente possono essere previste. Ciò che è eccezionale (in teoria) con le eccezioni è che sei al sicuro finché:

  • Scrivi il codice di sicurezza eccezionale al massimo grado possibile
  • Cattura gli stati eccezionali tramite le eccezioni
  • Gestisci le eccezioni di conseguenza
risposta data 25.09.2012 - 02:50
fonte
2

Il codice basato su eccezioni sposta la gestione degli errori fuori dal flusso del programma principale. Invece, la gestione degli errori viene spostata sul distruttore dell'oggetto o sul costrutto catch.

Il più grande vantaggio e anche il più grande svantaggio con eccezione è che non ti costringe a pensare sulla gestione degli errori. Con l'eccezione, spesso scrivi semplicemente un codice e lasci che il distruttore faccia il suo lavoro, e dimentichi quel piccolo scenario che il distruttore non gestisce e deve essere fatto manualmente (che è chiaramente documentato e quindi è totalmente colpa tua per dimenticare). Lo stesso scenario potrebbe accadere con il codice di errore, ma quando si scrive codice con codice di errore, si è costretti a controllare il manuale / documentazione / sorgente tutto il tempo ed è meno probabile che si dimentichi di quella piccola condizione.

Sì, questo significa che a volte le eccezioni rendono più difficile scrivere codice corretto.

Scrivere un codice errato con il codice di errore è facile, scrivere un buon codice con il codice di errore è difficile. Scrivere un codice decente in eccezioni è facile (perché ogni errore termina il programma o viene catturato da un gestore di livello molto alto che potrebbe avvisare dell'imminente sventura, invece di essere passato in silenzio), scrivere un buon codice con eccezione è davvero difficile (perché stai gestendo gli errori a un braccio dal luogo in cui si verifica l'errore).

    
risposta data 25.09.2012 - 11:03
fonte
1
  • Facile dimenticare i codici di uscita
  • Codice duplicato. Controllare il codice di errore e restituire il codice di errore quando c'è un errore causerebbe un sacco di codice duplicato da gestire e molto da cambiare se cambia il tipo di ritorno. Inoltre cosa fare quando tipi diversi possono causare un errore?
  • Le eccezioni vengono eseguite al di fuori del percorso del codice, il che significa che non c'è lavoro che controlla il valore di ritorno. Ma c'è del lavoro nel binario da qualche parte quando viene eseguito il codice di eccezione.
  • Facile vedere quali funzioni gestiscono il tipo di errori. Non devi guardare un sacco di codice, basta scorrere il catch
  • Maggiori dettagli (in alcune lingue). Ha incorporato informazioni quali la linea, la funzione, ecc. Metterlo in una classe di errore sarebbe noioso. Probabilmente si scriverà solo il nome della funzione e si spera che il tipo o la causa dell'errore.
  • I debugger comprendono che le eccezioni sono errori e potrebbero interrompersi quando ne viene colpito uno mentre si esegue il debug. Strumenti di aiuto:)
risposta data 25.09.2012 - 03:25
fonte
-1

Se entrambi vengono implementati perfettamente le differenze non sono poi così tanto, di solito, nulla è perfetto. Se manca la gestione di alcuni errori, in caso di eccezioni il programma si ferma con un errore visibile, mentre il programma con test degli errori continua a funzionare con un comportamento indefinito.

Se sviluppi un programma e hai un errore che non hai notato, cosa preferiresti avere?

  1. Il programma si blocca in modo da iniziare a cercare e trovare il bug (o il programma si arresta in modo anomalo ai beta tester o forse all'utente in modo che possano inviare un bug report) (gestione delle eccezioni)
  2. Il programma continua a funzionare, nel frattempo corrompendo silenziosamente alcune variabili senza causare effetti visibili sul tuo computer. Tuttavia, eliminerà tutti i dati degli utenti quando vengono eseguiti, senza che questi se ne accorgano quando ciò accade. ( test degli errori )
risposta data 25.09.2012 - 10:13
fonte

Leggi altre domande sui tag