Il modo moderno di eseguire la gestione degli errori ...

116

Sto riflettendo su questo problema da un po 'di tempo e mi trovo continuamente alla ricerca di caveat e contraddizioni, quindi spero che qualcuno possa dare una conclusione a quanto segue:

Favorisci le eccezioni rispetto ai codici di errore

Per quanto ne so, dal lavoro nel settore per quattro anni, dalla lettura di libri e blog, ecc., l'attuale best practice per la gestione degli errori consiste nel generare eccezioni piuttosto che restituire codici di errore (non necessariamente un codice di errore , ma un tipo che rappresenta un errore).

Ma - a me sembra contraddire ...

Codifica su interfacce, non implementazioni

Codifichiamo le interfacce o le astrazioni per ridurre l'accoppiamento. Non sappiamo, o vogliamo sapere, il tipo specifico e l'implementazione di un'interfaccia. Quindi, come possiamo sapere quali eccezioni dovremmo cercare di prendere? L'implementazione potrebbe generare 10 eccezioni diverse, oppure non generarne nessuna. Quando rileviamo un'eccezione, stiamo facendo delle ipotesi sull'implementazione?

A meno che l'interfaccia non abbia ...

Specifiche delle eccezioni

Alcune lingue consentono agli sviluppatori di affermare che determinati metodi generano alcune eccezioni (Java ad esempio, usa la parola chiave throws .) Dal punto di vista del codice chiamante ciò sembra corretto - sappiamo esplicitamente quali eccezioni avremmo bisogno di catturare.

Ma - questo sembra suggerire un ...

Estrazione di perdite

Perché un'interfaccia dovrebbe specificare quali eccezioni possono essere lanciate? Cosa succede se l'implementazione non ha bisogno di lanciare un'eccezione, o ha bisogno di lanciare altre eccezioni? Non c'è modo, a livello di interfaccia, di sapere quali eccezioni si può desiderare di lanciare.

...

Per concludere

Perché le eccezioni sono preferite quando sembrano (ai miei occhi) contraddire le migliori pratiche del software? E se i codici di errore sono così cattivi (e non ho bisogno di essere venduto sui vizi dei codici di errore), c'è un'altra alternativa? Qual è lo stato attuale (o presto) dello stato dell'arte per la gestione degli errori che soddisfa i requisiti delle best practice come sopra descritto, ma non si basa sul codice di chiamata che controlla il valore restituito dai codici di errore?

    
posta RichK 03.05.2012 - 12:35
fonte

11 risposte

31

Innanzitutto, non sarei d'accordo con questa affermazione:

Favour exceptions over error codes

Questo non è sempre il caso: per esempio, date un'occhiata a Objective-C (con il framework Foundation). Lì il NSError è il modo migliore per gestire gli errori, nonostante l'esistenza di ciò che uno sviluppatore Java chiamerebbe vere eccezioni: @try, @catch, @throw, NSException class, ecc.

Tuttavia è vero che molte interfacce perdono le loro astrazioni con le eccezioni generate. Credo che non sia colpa dello stile di "eccezione" di propagazione / gestione degli errori. In generale, credo che il miglior consiglio sulla gestione degli errori sia questo:

Gestisci l'errore / l'eccezione al livello più basso possibile, periodo

Penso che se ci si attiene a questa regola empirica, la quantità di "fuga" dalle astrazioni può essere molto limitata e contenuta.

Se le eccezioni generate da un metodo dovrebbero far parte della sua dichiarazione, credo che dovrebbero: fanno parte del contratto definito da questa interfaccia: Questo metodo fa A, o fallisce con B o C.

Ad esempio, se una classe è un parser XML, una parte del suo design dovrebbe essere per indicare che il file XML fornito è semplicemente sbagliato. In Java, lo fai normalmente dichiarando le eccezioni che ti aspetti di incontrare e aggiungendole alla parte throws della dichiarazione del metodo. D'altra parte, se uno degli algoritmi di analisi non è riuscito, non c'è motivo di passare quell'eccezione sopra non gestita.

Tutto si riduce a una cosa: Buona progettazione dell'interfaccia. Se si progetta la tua interfaccia abbastanza bene, nessuna quantità di eccezioni dovrebbe tormentarti. Altrimenti, non sono solo le eccezioni che ti infastidiscono.

Inoltre, penso che i creatori di Java avessero motivi di sicurezza molto forti per includere eccezioni a una dichiarazione / definizione del metodo.

Un'ultima cosa: alcune lingue, ad esempio, Eiffel hanno altri meccanismi per la gestione degli errori e semplicemente non includono le funzionalità di lancio. Lì, un'eccezione di ordinamento viene automaticamente sollevata quando una postcondizione per una routine non è soddisfatta.

    
risposta data 03.05.2012 - 13:23
fonte
27

Vorrei solo ricordare che le eccezioni e i codici di errore non sono l'unico modo per gestire errori e percorsi di codice alternativi.

Al di fuori della mente, puoi avere un approccio come quello di Haskell, in cui gli errori possono essere segnalati tramite tipi di dati astratti con più costruttori (pensi enumerazioni discriminate, o puntatori nulli, ma typesafe e con la possibilità di aggiungere zucchero sintetico o funzioni di supporto per rendere il flusso del codice un bell'aspetto).

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail è una funzione che restituisce un valore racchiuso in un Maybe. Funziona come un puntatore nullable, ma la notazione garantisce che l'intera cosa restituisce null se qualcuno di a, b o c fallisce. (e il compilatore ti protegge dal fare una NullPointerException accidentale)

Un'altra possibilità è passare un oggetto gestore di errori come argomento extra per ogni funzione chiamata. Questo gestore di errori ha un metodo per ogni possibile "eccezione" che può essere segnalato dalla funzione a cui lo si passa e può essere utilizzato da quella funzione per trattare le eccezioni dove si verificano, senza dover necessariamente riavvolgere lo stack tramite eccezioni.

Il Common LISP fa questo e lo rende fattibile avendo supporto sintattico (argomenti impliciti) e avendo le funzioni integrate che seguono questo protocollo.

    
risposta data 03.05.2012 - 17:53
fonte
8

Sì, le eccezioni possono causare astrazioni che perdono. Ma i codici di errore non sono nemmeno peggiori in questo senso?

Un modo per affrontare questo problema è fare in modo che l'interfaccia specifichi esattamente quali eccezioni possono essere generate in quali circostanze e dichiari che le implementazioni devono mappare il loro modello di eccezione interno a questa specifica, catturando, convertendo e rilanciando le eccezioni se necessario . Se vuoi un'interfaccia "prefetto", questa è la strada da percorrere.

In pratica, di solito è sufficiente specificare eccezioni che sono logicamente parte dell'interfaccia e che un client potrebbe voler catturare e fare qualcosa. È generalmente inteso che possono esserci altre eccezioni quando si verificano errori di basso livello o si manifesta un bug e che un client può gestire solo in genere mostrando un messaggio di errore e / o chiudendo l'applicazione. Almeno l'eccezione può ancora contenere informazioni che aiutano a diagnosticare il problema.

In effetti, con i codici di errore, quasi la stessa cosa finisce per accadere, solo in un modo più implicito, e con molta più probabilità che le informazioni si perdano e l'app finisca in uno stato incoerente.

    
risposta data 03.05.2012 - 12:50
fonte
5

Un sacco di cose buone qui, vorrei solo aggiungere che dovremmo essere diffidenti nei confronti del codice che usa le eccezioni come parte del normale flusso di controllo. A volte le persone entrano in quella trappola dove tutto ciò che non è il solito caso diventa un'eccezione. Ho persino visto un'eccezione utilizzata come condizione di terminazione del ciclo.

Eccezioni significano che "qualcosa che non riesco a gestire qui è successo, ho bisogno di andare da qualcun altro per capire cosa fare". Un utente che digita un input non valido non è un'eccezione (che dovrebbe essere gestita localmente dall'input chiedendo di nuovo, ecc.).

Un altro caso degenerato di utilizzo delle eccezioni che ho visto sono le persone la cui prima risposta è "lanciare un'eccezione". Questo è quasi sempre fatto senza scrivere la presa (regola empirica: scrivi prima la presa, poi la frase di lancio). Nelle grandi applicazioni questo diventa problematico quando un'eccezione non rilevata esplode dalle regioni inferiori e fa esplodere il programma.

Non sono anti-eccezioni, ma sembrano dei singleton di qualche anno fa: usati troppo spesso e in modo inappropriato. Sono perfetti per l'uso previsto, ma il caso non è così ampio come alcuni pensano.

    
risposta data 05.05.2012 - 16:51
fonte
4

Leaky abstraction

Why should an interface specify which exceptions can be thrown? What if the implementation doesn't need to throw an exception, or needs to throw other exceptions? There's no way, at an interface level, to know which exceptions an implementation may want to throw.

No. Le specifiche di eccezione si trovano nello stesso bucket dei tipi return e argument: fanno parte dell'interfaccia. Se non è possibile conformarsi a tale specifica, non implementare l'interfaccia. Se non butti mai, allora va bene. Non c'è niente di leale nel specificare le eccezioni in un'interfaccia.

I codici di errore sono oltremodo negativi. Sono terribili. Devi ricordarti manualmente di controllarli e propagarli, ogni volta, per ogni chiamata. Questo viola DRY, per cominciare, e fa esplodere in maniera massiccia il codice di gestione degli errori. Questa ripetizione è un problema molto più grande di ogni altra affrontata dalle eccezioni. Non puoi mai ignorare silenziosamente un'eccezione, ma le persone possono ignorare silenziosamente i codici di ritorno, decisamente una brutta cosa.

    
risposta data 03.05.2012 - 18:37
fonte
2

Bene La gestione delle eccezioni può avere la propria implementazione dell'interfaccia. A seconda del tipo di eccezione generata, eseguire i passaggi desiderati.

La soluzione al tuo problema di progettazione è avere due implementazioni di interfaccia / astrazione. Uno per la funzionalità e l'altro per la gestione delle eccezioni. E in base al tipo di Eccezione rilevata, chiama la classe del tipo di eccezione appropriata.

L'implementazione dei codici di errore è un modo ortodosso di gestire l'eccezione. È come l'utilizzo del costruttore di stringhe e stringhe.

    
risposta data 02.05.2012 - 14:35
fonte
2

Le eccezioni IM-very-HO devono essere giudicate caso per caso, perché interrompendo il flusso di controllo aumenteranno la complessità effettiva e percepita del codice, in molti casi inutilmente. Mettendo da parte la discussione relativa al lancio di eccezioni all'interno delle tue funzioni - che potrebbero effettivamente migliorare il tuo flusso di controllo, se si guardano le eccezioni di lancio attraverso i limiti delle chiamate, considera quanto segue:

Permettere a un callee di interrompere il flusso di controllo potrebbe non apportare alcun beneficio reale e potrebbe non esserci alcun modo significativo per gestire l'eccezione. Per un esempio diretto, se si sta implementando il pattern Observable (in un linguaggio come C # in cui si hanno eventi ovunque e nessun throws esplicito nella definizione), non vi è alcun motivo reale per consentire all'Osservatore di interrompere il flusso di controllo se esso si blocca e non ha un modo significativo di gestire le proprie cose (ovviamente, un buon vicino non dovrebbe lanciare quando osserva, ma nessuno è perfetto).

L'osservazione sopra può essere estesa a qualsiasi interfaccia liberamente accoppiata (come hai sottolineato); Penso che sia in realtà una norma che dopo aver insinuato 3-6 frame di stack, è probabile che un'eccezione non rilevata finisca in una sezione di codice che:

  • è troppo astratto per gestire l'eccezione in modo significativo, anche se l'eccezione stessa viene aggiornata,
  • sta eseguendo una funzione generica (non potrebbe importare di meno del motivo per cui non sei riuscito, ad esempio un messaggio pump o l'osservabile);
  • è specifico, ma con una responsabilità diversa, e in realtà non dovrebbe preoccupare;

Considerando quanto sopra, la decorazione delle interfacce con la semantica di throws è solo un guadagno marginale funzionale, perché molti chiamanti attraverso i contratti di interfaccia si preoccuperebbero solo se tu fallissi, non perché.

Direi che diventa una questione di gusto e convenienza: il tuo obiettivo primario è recuperare con garbo il tuo stato sia nel chiamante che nel chiamato dopo un'eccezione, quindi, se hai molta esperienza nello spostare i codici di errore intorno (proveniente da uno sfondo C), o se stai lavorando in un ambiente in cui le eccezioni possono trasformarsi in malvagità (C ++), non credo che buttare roba in giro sia così importante per un OOP bello e pulito che non puoi contare sui tuoi vecchi schemi se ti senti a disagio. Soprattutto se porta a rompere SoC.

Da una prospettiva teorica, penso che un modo SoC-kosher di gestire le eccezioni possa essere derivato direttamente dall'osservazione che la maggior parte delle volte il chiamante diretto si preoccupa solo del fatto che tu abbia fallito, non perché. Il callee lancia, qualcuno molto vicino sopra (2-3 fotogrammi) cattura una versione upcasted, e l'eccezione effettiva viene sempre affondata a un gestore di errori specializzato (anche se solo la traccia) - questo è dove AOP sarebbe utile, perché questi gestori sono probabilmente orizzontali.

    
risposta data 03.05.2012 - 13:28
fonte
1

Favour exceptions over error codes

  • Entrambi dovrebbero coesistere.

  • Restituisci il codice di errore quando anticipi determinati comportamenti.

  • Restituisce un'eccezione quando non hai previsto un comportamento.

  • I codici di errore sono normalmente associati a un singolo messaggio, quando il tipo di eccezione rimane, ma un messaggio può variare

  • L'eccezione ha una traccia dello stack, quando il codice di errore no. Non uso i codici di errore per il debug del sistema rotto.

Coding to interfaces not implementations

Questo potrebbe essere specifico per JAVA, ma quando dichiaro le mie interfacce non specificherò quali eccezioni potrebbero essere generate da un'implementazione di tale interfaccia, semplicemente non ha senso.

When we catch an exception surely we're making assumptions about the implementation?

Questo dipende interamente da te. Puoi provare a catturare un tipo di eccezione molto specifico e poi prendere un Exception più generico. Perché non lasciare che l'eccezione si propaghi nello stack e poi gestirlo? In alternativa è possibile guardare la programmazione degli aspetti in cui la gestione delle eccezioni diventa un aspetto "collegabile".

What if the implementation doesn't need to throw an exception, or needs to throw other exceptions?

Non capisco perché è un problema per te. Sì, potresti avere un'implementazione che non fallisce mai o genera eccezioni e potresti avere un'implementazione diversa che fallisce costantemente e genera un'eccezione. In tal caso, non specificare alcuna eccezione sull'interfaccia e il problema è risolto.

cambierebbe qualcosa se invece l'eccezione la tua implementazione restituisse un oggetto risultato? Questo oggetto conterrebbe l'esito della tua azione insieme a eventuali errori / errori, se presenti. Puoi quindi interrogare quell'oggetto.

    
risposta data 04.05.2012 - 00:40
fonte
1

Leaky abstraction

Why should an interface specify which exceptions can be thrown? What if the implementation doesn't need to throw an exception, or needs to throw other exceptions? There's no way, at an interface level, to know which exceptions an implementation may want to throw.

Nella mia esperienza, il codice che riceve l'errore (sia tramite eccezione, codice di errore o qualsiasi altra cosa) normalmente non si preoccupa della causa esatta dell'errore - reagirebbe allo stesso modo a qualsiasi errore, tranne che per il possibile segnalazione dell'errore (che si tratti di una finestra di errore o di qualche tipo di registro); e questa segnalazione verrebbe eseguita ortogonalmente al codice che ha chiamato la procedura in errore. Ad esempio, questo codice potrebbe passare l'errore ad un altro pezzo di codice che sa come segnalare errori specifici (ad esempio, formattare una stringa di messaggi), eventualmente allegando alcune informazioni di contesto.

Naturalmente, in alcuni casi è necessario per collegare semantica specifica agli errori e reagire in modo diverso in base a quale errore si è verificato. Tali casi dovrebbero essere documentati nelle specifiche dell'interfaccia. Tuttavia, l'interfaccia potrebbe comunque riservarsi il diritto di lanciare altre eccezioni senza alcun significato specifico.

    
risposta data 04.05.2012 - 02:35
fonte
1

Trovo che le eccezioni consentano di scrivere un codice più strutturato e conciso per la segnalazione e la gestione degli errori: l'uso di codici di errore richiede il controllo del reso valori dopo ogni chiamata e decidere cosa fare in caso di risultato imprevisto.

D'altra parte sono d'accordo sul fatto che le eccezioni rivelano dettagli di implementazione che dovrebbero essere nascosti al codice che invoca un'interfaccia. Dato che non è possibile sapere a priori quale parte di codice può lanciare quali eccezioni (a meno che non siano dichiarate nella firma del metodo come in Java), usando le eccezioni stiamo introducendo dipendenze implicite molto complesse tra diverse parti del codice, che è contro il principio di minimizzare le dipendenze.

Riassumendo:

  • Penso che le eccezioni consentano un codice più pulito e un approccio più aggressivo al testing e al debug perché le eccezioni non rilevate sono molto più visibili e difficili da ignorare rispetto ai codici di errore (fallire presto).
  • D'altro canto, i bug di eccezione non rilevati che non vengono scoperti durante il test possono apparire in un ambiente di produzione sotto forma di arresto anomalo. In determinate circostanze questo comportamento non è accettabile e in questo caso penso che l'utilizzo di codici di errore sia un approccio più robusto.
risposta data 05.05.2012 - 00:23
fonte
-1

In entrambi i lati.

Non può essere ignorato, deve essere gestito, è completamente trasparente. E SE si utilizza il tipo corretto di errore mancini trasmette tutte le stesse informazioni di un'eccezione java.

lato negativo? Il codice con la corretta gestione degli errori sembra disgustoso (vero di tutti i meccanismi).

    
risposta data 22.12.2014 - 02:53
fonte

Leggi altre domande sui tag