Catturare le eccezioni generali è davvero una brutta cosa?


57

In genere sono d'accordo con la maggior parte degli avvisi di analisi del codice e provo ad aderirli. Tuttavia, sto avendo un momento più difficile con questo:

CA1031: Non rilevare i tipi di eccezione generali

Capisco la logica di questa regola. Ma, in pratica, se voglio intraprendere la stessa azione indipendentemente dall'eccezione generata, perché dovrei gestirle in modo specifico? Inoltre, se gestisco eccezioni specifiche, cosa succede se il codice che sto chiamando cambia per generare una nuova eccezione in futuro? Ora devo cambiare il mio codice per gestire quella nuova eccezione. Considerando che se ho semplicemente catturato il Exceptionmio codice non deve cambiare.

Ad esempio, se Foo chiama Bar e Foo deve interrompere l'elaborazione indipendentemente dal tipo di eccezione generata da Bar, c'è qualche vantaggio nell'essere specifico sul tipo di eccezione che sto rilevando?

Forse un esempio migliore:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Se non si rileva un'eccezione generale qui, è necessario catturare ogni tipo di eccezione possibile. Patrick ha un buon punto che OutOfMemoryExceptionnon dovrebbe essere affrontato in questo modo. E se volessi ignorare ogni eccezione ma OutOfMemoryException?


12
Che dire OutOfMemoryException? Stesso codice di gestione di tutto il resto?
Patrick,

@Patrick Un buon punto. Ho aggiunto un nuovo esempio alla mia domanda per coprire la tua domanda.
Bob Horn,

1
Catturare le eccezioni generali è tanto negativo quanto fare dichiarazioni generali su ciò che tutti dovrebbero fare. In genere non esiste un approccio unico per tutti.

4
@Patrick sarebbe OutOfMemoryError, che è separato dall'albero Exceptiondell'eredità proprio per questo motivo
Darkhogg

Che dire delle eccezioni da tastiera, come Ctrl-Alt-Canc?
Mawg,

Risposte:


33

Queste regole sono generalmente una buona idea e quindi dovrebbero essere seguite.

Ma ricorda che queste sono regole generiche. Non coprono tutte le situazioni. Coprono le situazioni più comuni. Se hai una situazione specifica e puoi argomentare che la tua tecnica è migliore (e dovresti essere in grado di scrivere un commento nel codice per articolare l'argomento per farlo), allora fallo (e poi fallo rivedere alla pari).

Sul lato opposto dell'argomento.

Non vedo il tuo esempio sopra come una buona situazione per farlo. Se il sistema di registrazione non funziona (presumibilmente registrando qualche altra eccezione), probabilmente non voglio che l'applicazione continui. Esci e stampa l'eccezione sull'output in modo che l'utente possa vedere cosa è successo.


2
Concur. Per lo meno , ora che la registrazione regolare non è in linea, effettuare una sorta di disposizione che fornirà un qualche tipo di output di debug, a STDERR se non altro.
Shadur,

6
Ho votato entrambi, ma penso che tu stia pensando come sviluppatori, non utenti. A un utente in genere non importa se la registrazione sta avvenendo, a condizione che l'applicazione sia utilizzabile.
Mawg,

1
Interessante, poiché log4net non vuole esplicitamente interrompere l'app solo perché la registrazione non riesce. E penso che dipende da cosa stai registrando; i controlli di sicurezza, sicuramente, interrompono il processo. Ma registri di debug? Non così importante.
Andy,

1
@Andy: Sì, tutto dipende da cosa stai facendo.
Martin York,

2
Perché non li capisci? E non dovresti sforzarti di farlo?
Mawg,

31

Sì, catturare le eccezioni generali è una cosa negativa. Un'eccezione di solito significa che il programma non può fare ciò che gli è stato chiesto di fare.

Esistono alcuni tipi di eccezioni che è possibile gestire:

  • Eccezioni fatali : memoria insufficiente, overflow dello stack, ecc. Alcune forze soprannaturali hanno appena incasinato il tuo universo e il processo sta già morendo. Non puoi migliorare la situazione, quindi arrenditi
  • Eccezione generata da bug nel codice che si sta utilizzando : non tentare di gestirli, ma piuttosto correggere l'origine del problema. Non utilizzare eccezioni per il controllo del flusso
  • Situazioni eccezionali : gestisci solo le eccezioni in questi casi. Qui possiamo includere: cavo di rete scollegato, connessione Internet interrotta, autorizzazioni mancanti, ecc.

Oh, e come regola generale: se non sai cosa fare con un'eccezione se la prendi, è meglio fallire velocemente (passa l'eccezione al chiamante e lascia che lo gestisca)


1
Che dire del caso specifico nella domanda? Se c'è un bug nel codice di registrazione, probabilmente va bene ignorare l'eccezione, perché il codice che conta davvero non dovrebbe esserne influenzato.
svick,

4
Perché non correggere invece il bug nel codice di registrazione?
Victor Hurdugaci,

3
Victor, probabilmente non è un bug. Se il disco su cui si trova il registro si esaurisce, è presente un bug nel codice di registrazione?
Carson63000,

4
@ Carson63000 - beh, sì. I log non vengono scritti, quindi: bug. Implementare tronchi rotanti di dimensioni fisse.
detenere il

5
Questa è la migliore risposta imo, ma manca un aspetto cruciale: la sicurezza . Ci sono alcune eccezioni che si verificano a causa dei cattivi che cercano di riempire il tuo programma. Se viene generata un'eccezione che non è possibile gestire, non è più possibile fidarsi di quel codice. A nessuno piace sentire che hanno scritto il codice che ha permesso ai cattivi di entrare.
riwalk

15

Il ciclo esterno più in alto dovrebbe avere uno di questi per stampare tutto ciò che può, e quindi morire in modo orribile, violento e NOISY (poiché ciò non dovrebbe accadere e qualcuno deve sentire).

Altrimenti dovresti generalmente stare molto attento poiché molto probabilmente non hai previsto tutto ciò che potrebbe accadere in questa posizione, e quindi molto probabilmente non lo tratterai correttamente. Sii il più specifico possibile in modo da catturare solo quelli che sai che accadranno e lasciare che quelli che non si vedono prima arrivino alla morte rumorosa sopra menzionata.


1
Sono d'accordo con questo in teoria. Ma per quanto riguarda il mio esempio di registrazione sopra? Cosa succede se si desidera semplicemente ignorare le eccezioni di registrazione e continuare? Dovresti avere bisogno di cogliere ogni eccezione che potrebbe essere lanciata e gestirle ognuna lasciando che l'eccezione più seria si risolva?
Bob Horn,

6
@BobHorn: due modi di vederlo. Sì, puoi dire che la registrazione non è particolarmente importante e se fallisce, non dovrebbe interrompere la tua app. E questo è un punto abbastanza giusto. Ma cosa succede se la tua applicazione non riesce ad accedere per cinque mesi e non ne hai idea? L'ho visto succedere. E poi abbiamo avuto bisogno dei registri. Disastro. Suggerirei di fare tutto ciò che si può pensare per fermare il fallimento della registrazione è meglio che ignorarlo quando lo fa. (Ma non avrai molto consenso su questo.)
pdr

@pdr Nel mio esempio di registrazione, in genere invio un avviso e-mail. In alternativa, disponiamo di sistemi per monitorare i log e, se un log non viene popolato attivamente, il nostro team di supporto riceverà un avviso. Quindi verremmo a conoscenza del problema di registrazione in altri modi.
Bob Horn,

@BobHorn il tuo esempio di registrazione è piuttosto elaborato - la maggior parte dei framework di registrazione che conosco, fanno di tutto per non generare eccezioni e non verrebbero invocati in questo modo (poiché renderebbe inutile qualsiasi "istruzione di registro avvenuta alla riga X nel file Y" )

@pdr Ho sollevato la domanda nella lista di logback (Java) su come far sì che il framework di logging fermi l'applicazione se la configurazione di logging fallisce, semplicemente perché è COSÌ importante che abbiamo i log, e qualsiasi errore silenzioso è inaccettabile. Una buona soluzione è ancora in sospeso.

9

Non è male, è solo che le catture specifiche sono migliori. Quando sei specifico, significa che in realtà capisci, più concretamente, cosa sta facendo la tua applicazione e hai un maggiore controllo su di essa. In generale, se ti imbatti in una situazione in cui hai appena preso un'eccezione, registrala e continua, probabilmente ci sono alcune cose brutte che stanno accadendo comunque. Se stai rilevando in modo specifico le eccezioni che sai che un blocco di codice o un metodo possono generare, allora c'è una probabilità maggiore che puoi effettivamente recuperare invece di limitarti a registrare e sperare per il meglio.


5

Le due possibilità non si escludono a vicenda.

In una situazione ideale, dovresti rilevare tutti i possibili tipi di eccezione che il tuo metodo potrebbe generare, gestirli su una base per eccezione e alla fine aggiungere una catchclausola generale per rilevare eventuali eccezioni future o sconosciute. In questo modo ottieni il meglio da entrambi i mondi.

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

Tieni presente che per cogliere le eccezioni più specifiche, devi prima metterle.

Modifica: per i motivi indicati nei commenti, aggiunto un nuovo tentativo nell'ultima cattura.


2
Aspettare. Perché mai vorresti registrare un'eccezione sconosciuta per console e andare avanti? Se è un'eccezione che non sapevi nemmeno che potrebbe essere generata dal codice, probabilmente stai parlando di un errore di sistema. Andare avanti in questo scenario è probabilmente una pessima idea.
pdr

1
@pdr Sicuramente ti rendi conto che è un esempio inventato. Il punto era dimostrare la cattura di eccezioni sia specifiche che generali, non come gestirle.
Rotem,

@Rotem Penso che tu abbia una buona risposta qui. Ma il pdr ha ragione. Il codice, così com'è, fa sembrare che catturare e continuare vada bene. Alcuni principianti, vedendo questo esempio, potrebbero pensare che sia un buon approccio.
Bob Horn,

Concepito o no, rende la tua risposta sbagliata. Non appena si iniziano a rilevare eccezioni come Memoria esaurita e Disco pieno e solo a ingerirle, si sta dimostrando il motivo per cui catturare le eccezioni generali è effettivamente negativo.
pdr

1
Se si rileva un'eccezione generale solo per registrarla, è necessario ripeterla dopo la registrazione.
Victor Hurdugaci,

5

Ho riflettuto sulla stessa cosa di recente, e la mia conclusione provvisoria è che la semplice domanda sorge dal momento che la gerarchia di eccezioni .NET è gravemente incasinata.

Prendi, ad esempio, il minimo ArgumentNullExceptionche potrebbe essere un candidato ragionevole per un'eccezione che non vuoi catturare, perché tende a indicare un bug nel codice piuttosto che un errore di runtime legittimo. Ah sì. Lo stesso NullReferenceExceptionvale, tranne per il fatto che NullReferenceExceptionderiva SystemExceptiondirettamente, quindi non esiste un bucket in cui sia possibile catturare (o non catturare) tutti gli "errori logici".

Poi c'è l'IMNSHO, il problema principale di avere SEHExceptionderivato (via ExternalException) SystemExceptione quindi renderlo un "normale"SystemException Quando si ottiene unSEHException, si desidera scrivere un dump e terminare il più velocemente possibile - e iniziando con .NET 4 almeno alcuni Le eccezioni SEH sono considerate eccezioni di stato danneggiate che non verranno rilevate. Una cosa buona e un fatto che rende laCA1031regola ancora più inutile, perché ora i tuoi pigricatch (Exception)non cattureranno comunque questi peggiori tipi.

Quindi, sembra che le altre cose del Framework derivino piuttosto incoerentemente Exceptiondirettamente o tramite SystemException, facendo qualsiasi tentativo di raggruppare clausole di cattura da qualcosa come il moot di gravità.

C'è un pezzo di C#fama del signor Lippert , chiamato Vexing Exception , in cui espone un'utile categorizzazione delle eccezioni: si potrebbe obiettare che si desidera catturare solo quelli "esogeni", tranne ... C#il linguaggio e il design di .NET le eccezioni quadro rendono impossibile "catturare solo quelli esogeni" in modo succinto. (e, ad esempio, un OutOfMemoryExceptionpuò benissimo essere un errore recuperabile totalmente normale per un'API che deve allocare i buffer che sono in qualche modo grande)

La linea di fondo per me è che il modo in cui C#funzionano i blocchi di cattura e il modo in cui è progettata la gerarchia delle eccezioni di Framework, la regola CA1031è al di là del tutto inutile . Si finge di aiuto per il problema radice di "non ingerire eccezioni", ma ingoiare eccezioni ha zero a che fare con quello che si cattura, ma con quello che poi fai:

Esistono almeno 4 modi per gestire legittimamente un pescato Exceptione CA1031sembra gestirne solo superficialmente uno (vale a dire il caso di rilancio).


Come nota a margine, c'è una funzione C # 6 chiamata Filtri eccezioni che renderà di nuovo CA1031leggermente più valida da allora sarai in grado di filtrare correttamente, correttamente, le eccezioni che vuoi catturare, e ci sono meno motivi per scrivere un filtro non filtrato catch (Exception).


1
Un grave problema OutOfMemoryExceptionè che non esiste un modo efficace per garantire che il codice "solo" indichi il fallimento di una determinata allocazione per cui si è preparati a fallire. È possibile che sia stata generata un'altra eccezione più seria e si è OutOfMemoryExceptionverificato un errore durante lo svolgimento dello stack dall'altra eccezione. Java potrebbe essere in ritardo alla festa con il suo "prova con le risorse", ma gestisce le eccezioni durante lo svolgimento dello stack un po 'meglio di .NET.
supercat,

1
@supercat - abbastanza vero, ma è praticamente vero per qualsiasi altra eccezione di sistema generale, quando le cose vanno male in qualsiasi blocco finalmente.
Martin Ba,

1
È vero, ma si può (e generalmente si dovrebbe) proteggere i finallyblocchi dalle eccezioni che si può realisticamente aspettarsi che si verifichino in modo tale che entrambe le eccezioni originali e nuove vengano registrate. Sfortunatamente, ciò richiederà spesso un'allocazione di memoria, che potrebbe causare guasti propri.
supercat,

4

La gestione delle eccezioni di Pokemon (devo catturarli tutti!) Non è certamente sempre male. Quando si espone un metodo a un client, in particolare a un utente finale, è spesso meglio catturare qualsiasi cosa piuttosto che far arrestare e bruciare l'applicazione.

Generalmente però dovrebbero essere evitati dove è possibile. A meno che non sia possibile intraprendere azioni specifiche in base al tipo di eccezione, è meglio non gestirla e consentire all'eccezione di eseguire il bubble up anziché ingoiare l'eccezione o gestirla in modo errato.

Dai un'occhiata a questa risposta SO per ulteriori letture.


1
Questa situazione (quando uno sviluppatore deve essere un Pokemon Trainer) appare quando si crea da soli un intero software: sono stati creati i livelli, le connessioni al database e la GUI dell'utente. L'app deve ripristinarsi da situazioni come perdita di connettività, eliminazione della cartella dell'utente, dati danneggiati, ecc. Gli utenti finali non amano le app che si arrestano in modo anomalo e che si masterizzano. A loro piacciono le app che possono funzionare su un computer in crash che esplode e che brucia!
Broken_Window

Non ci avevo pensato fino ad ora, ma in Pokemon, si presume generalmente che "catturandoli tutti" si desideri esattamente uno di ciascuno, e si debba gestire la cattura in modo specifico, che è l'opposto dell'uso comune di questa frase tra i programmatori.
Magus,

2
@Magus: con un metodo simile LoadDocument(), è sostanzialmente impossibile identificare tutte le cose che potrebbero andare storte, ma il 99% delle eccezioni che potrebbero essere lanciate significherà semplicemente "Non è stato possibile interpretare il contenuto di un file con il nome dato come un documento; affrontalo ". Se qualcuno tenta di aprire qualcosa che non è un file di documento valido, ciò non dovrebbe arrestare in modo anomalo l'applicazione e uccidere qualsiasi altro documento aperto. La gestione degli errori di Pokemon in questi casi è brutta, ma non conosco buone alternative.
supercat

@supercat: stavo solo facendo un punto linguistico. Tuttavia, non credo che i contenuti di file non validi suonino come qualcosa che dovrebbe generare più di un tipo di eccezione.
Magus,

1
@Magus: può generare ogni sorta di eccezione - questo è il problema. Spesso è molto difficile prevedere tutti i tipi di eccezioni che potrebbero essere generati a seguito di dati non validi in un file. Se non si utilizza la gestione di PokeMon, si rischia che un'applicazione muoia perché, ad esempio, il file che stava caricando conteneva una stringa di cifre eccezionalmente lunga in un posto che richiedeva un numero intero a 32 bit in formato decimale e si è verificato un overflow numerico in la logica di analisi.
supercat

1

La cattura dell'eccezione generale è negativa perché lascia il programma in uno stato indefinito. Non sai dove le cose sono andate male, quindi non sai cosa il tuo programma ha effettivamente fatto o non ha fatto.

Dove consentirei di catturare tutto è quando si chiude un programma. Finché puoi pulirlo bene. Niente di così fastidioso come un programma che chiudi che genera solo una finestra di dialogo di errore che non fa altro che stare lì, non andare via e impedire al tuo computer di chiudersi.

In un ambiente distribuito il tuo metodo di registro potrebbe fallire: la cattura di un'eccezione generale potrebbe significare che il tuo programma mantiene ancora un blocco sul file di registro impedendo ad altri utenti di creare registri.


0

Capisco la logica di questa regola. Ma, in pratica, se voglio intraprendere la stessa azione indipendentemente dall'eccezione generata, perché dovrei gestirle in modo specifico?

Come altri hanno già detto, è davvero difficile (se non impossibile) immaginare qualche azione che vorresti intraprendere indipendentemente dall'eccezione generata. Solo un esempio sono le situazioni in cui lo stato del programma è stato danneggiato e qualsiasi ulteriore elaborazione può portare a problemi (questa è la logica alla base Environment.FailFast).

Inoltre, se gestisco eccezioni specifiche, cosa succede se il codice che sto chiamando cambia per generare una nuova eccezione in futuro? Ora devo cambiare il mio codice per gestire quella nuova eccezione. Considerando che se ho semplicemente preso l'eccezione il mio codice non deve cambiare.

Per il codice hobby va bene catturare Exception, ma per il codice di livello professionale l'introduzione di un nuovo tipo di eccezione dovrebbe essere trattato con lo stesso rispetto di una modifica della firma del metodo, vale a dire essere considerato un cambiamento di rottura. Se ti iscrivi a questo punto di vista, è immediatamente ovvio che tornare indietro per cambiare (o verificare) il codice client è l'unica linea di azione corretta.

Ad esempio, se Foo chiama Bar e Foo deve interrompere l'elaborazione indipendentemente dal tipo di eccezione generata da Bar, c'è qualche vantaggio nell'essere specifico sul tipo di eccezione che sto rilevando?

Certo, perché non ti accorgerai solo delle eccezioni generate Bar. Ci saranno anche delle eccezioni che i client di Bar o persino il runtime potrebbero generare durante il tempo che si Bartrova nello stack di chiamate. Una persona ben scritta Bardovrebbe definire il proprio tipo di eccezione, se necessario, in modo che i chiamanti possano cogliere in modo specifico le eccezioni emesse da sé.

E se volessi ignorare ogni eccezione tranne OutOfMemoryException?

IMHO questo è il modo sbagliato di pensare alla gestione delle eccezioni. Dovresti operare sulle whitelist (prendi i tipi di eccezione A e B), non sulle blacklist (prendi tutte le eccezioni tranne X).


È spesso possibile specificare un'azione che dovrebbe essere eseguita per tutte le eccezioni che indicano che un'operazione tentata non è riuscita senza effetti collaterali e / o implicazioni avverse sullo stato del sistema. Ad esempio, se un utente seleziona un file di documento da caricare, un programma passa il suo nome a un metodo "crea oggetto documento con dati da un file su disco" e tale metodo non riesce per qualche motivo particolare che l'applicazione non aveva previsto (ma che soddisfa i criteri di cui sopra), il comportamento corretto dovrebbe essere quello di visualizzare un messaggio "Impossibile caricare il documento" anziché arrestare in modo anomalo l'applicazione.
supercat,

Sfortunatamente, non esiste un mezzo standard con cui un'eccezione può indicare la cosa più importante che il codice client vorrebbe sapere: se implica qualcosa di negativo sullo stato del sistema oltre a ciò che sarebbe implicito dall'impossibilità di eseguire l'operazione richiesta. Pertanto, anche se le situazioni in cui tutte le eccezioni dovrebbero essere gestite sono rare, ci sono quindi molte situazioni in cui molte eccezioni dovrebbero essere gestite allo stesso modo, e non vi è alcun criterio che sia soddisfatto da tutte queste eccezioni sarebbe soddisfatto anche da alcune rare dovrebbe essere gestito in modo diverso.
supercat,

0

Forse . Ci sono eccezioni ad ogni regola e nessuna regola dovrebbe essere seguita acriticamente. Il tuo esempio potrebbe essere presumibilmente uno dei casi in cui ha senso ingoiare tutte le eccezioni. Ad esempio, se si desidera aggiungere la traccia a un sistema di produzione critico e si desidera assicurarsi che le modifiche non interrompano l'attività principale dell'app.

Tuttavia , dovresti riflettere attentamente sui possibili motivi del fallimento prima di decidere di ignorarli silenziosamente. Ad esempio, cosa succede se il motivo dell'eccezione è:

  • un errore di programmazione nel metodo di registrazione che provoca sempre un'eccezione particolare
  • un percorso non valido per il file di registrazione nella configurazione
  • il disco è pieno

Non vuoi essere avvisato immediatamente che questo problema è presente, quindi puoi risolverlo? Ingoiare l'eccezione significa che non sai mai che qualcosa è andato storto.

Alcuni problemi (come il disco è pieno) potrebbero anche causare il fallimento di altre parti dell'applicazione, ma questo errore non è registrato ora, quindi non lo saprai mai!


0

Vorrei affrontarlo da una prospettiva logica piuttosto che tecnica,

Ora devo cambiare il mio codice per gestire quella nuova eccezione.

Bene, qualcuno dovrebbe gestirlo. Questa è l'idea. Gli autori del codice della libreria sarebbero prudenti nell'aggiungere nuovi tipi di eccezione, tuttavia, poiché è probabile che si rompano i client, non è necessario riscontrarlo molto spesso.

La tua domanda è fondamentalmente "Cosa succede se non mi interessa cosa è andato storto? Devo davvero passare attraverso la seccatura di scoprire di cosa si trattava?"

Ecco la parte della bellezza: no, non lo fai.

"Quindi, posso solo guardare dall'altra parte e far apparire automaticamente qualsiasi cosa cattiva saltata sotto il tappeto e farla finita?"

No, non funziona neanche così.

Il punto è che la raccolta di possibili eccezioni è sempre più ampia della raccolta che ti aspetti e ti interessa nel contesto del tuo piccolo problema locale. Gestisci quelli che prevedi e, se succede qualcosa di inaspettato, lo lasci ai gestori di livello superiore anziché ingoiarlo. Se non ti interessa un'eccezione che non ti aspettavi, scommetti che lo farà lo stack di chiamate e sarebbe un sabotaggio uccidere un'eccezione prima che raggiunga il suo gestore.

"Ma ... ma ... allora una di quelle altre eccezioni che non mi interessano potrebbe far fallire il mio compito!"

Sì. Ma saranno sempre più importanti di quelli gestiti localmente. Come un allarme antincendio o il capo che ti dice di fermare quello che stai facendo e raccogliere un compito più urgente che è saltato fuori.


-3

È necessario rilevare eccezioni generali al livello più alto di ogni processo e gestirlo segnalando il bug nel miglior modo possibile e quindi terminando il processo.

Non dovresti prendere eccezioni generali e provare a continuare l'esecuzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.