Trarre eccezioni generali è davvero una brutta cosa?

55

In genere sono d'accordo con la maggior parte degli avvisi di analisi del codice, e cerco di aderire a loro. Tuttavia, sto passando un momento difficile con questo:

CA1031: Do not catch general exception types

Capisco la logica di questa regola. Ma, in pratica, se voglio prendere la stessa azione indipendentemente dall'eccezione lanciata, perché dovrei gestirli singolarmente? 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 questa nuova eccezione. Considerando che se ho catturato semplicemente Exception il mio 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 trovi un'eccezione generale qui, devi cogliere ogni tipo di eccezione possibile. Patrick ha un buon punto che OutOfMemoryException non dovrebbe essere trattato in questo modo. Quindi, cosa succede se voglio ignorare ogni eccezione tranne OutOfMemoryException ?

    
posta Bob Horn 09.09.2012 - 17:18
fonte

12 risposte

32

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

Ma ricorda che si tratta di 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 il tuo argomento per farlo), fallo (e poi fallo revisionare 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 si registra 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.

    
risposta data 09.09.2012 - 19:23
fonte
30

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

Ci sono alcuni tipi di eccezioni che potresti gestire:

  • Eccezioni fatali : esaurimento della memoria, sovraccarico dello stack, ecc. Alcune forze soprannaturali hanno solo incasinato il tuo universo e il processo sta già morendo. Non puoi migliorare la situazione, quindi rinuncia a
  • Eccezione generata a causa di bug nel codice che si sta utilizzando : non tentare di gestirli, ma piuttosto risolvere l'origine del problema. Non utilizzare le eccezioni per il controllo del flusso
  • Situazioni eccezionali : gestiscono solo eccezioni in questi casi. Qui possiamo includere: cavo di rete scollegato, connessione a Internet smesso di funzionare, permessi 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 sia gestita)

    
risposta data 09.09.2012 - 17:58
fonte
15

Il ciclo esterno più in alto dovrebbe avere uno di questi per stampare tutto ciò che può, e poi morire una morte orribile, violenta e rumorosa (perché questo non dovrebbe accadere e qualcuno ha bisogno di sentire).

Altrimenti dovresti in genere stare molto attento perché molto probabilmente hai non anticipato tutto ciò che potrebbe accadere in questa posizione, e quindi molto probabilmente non lo tratterà correttamente. Sii il più specifico possibile in modo da catturare solo quelli che conosci accadrà, e lasciare che quelli che non si vedono prima possano esplodere fino alla morte rumorosa sopra menzionata.

    
risposta data 09.09.2012 - 18:18
fonte
9

Non è che sia cattivo, è solo che le catture specifiche sono migliori. Quando sei specifico, significa che in realtà comprendi concretamente ciò che sta facendo la tua applicazione e ne hai un maggiore controllo. In generale, se ti imbatti in una situazione in cui prendi semplicemente un Eccezione, registralo e continua, probabilmente ci sono alcune cose brutte che stanno succedendo comunque. Se stai rilevando in modo specifico le eccezioni che sai che un blocco di codice o un metodo possono generare, allora c'è una maggiore probabilità che tu possa effettivamente recuperare invece di limitarti a loggarti e sperare per il meglio.

    
risposta data 09.09.2012 - 18:02
fonte
5

Le due possibilità non si escludono a vicenda.

In una situazione ideale, cattureresti tutti i possibili tipi di eccezioni che il tuo metodo potrebbe generare, gestirli su base per eccezione e infine aggiungere una clausola generale catch per intercettare 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;
}

Ricorda che per catturare le eccezioni più specifiche, devi prima metterle.

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

    
risposta data 09.09.2012 - 17:24
fonte
5

Ho riflettuto sullo stesso di recente, e la mia conclusione provvisoria è che la semplice domanda sorge perché . La gerarchia delle eccezioni NET è gravemente incasinata.

Prendi, ad esempio, l'umile ArgumentNullException quale potrebbe essere un candidato ragionevole per un'eccezione che non vuole intercettare, perché tende per indicare un bug nel codice piuttosto che un legittimo errore di runtime. Ah sì. Così fa NullReferenceException , tranne che NullReferenceException deriva direttamente da SystemException , quindi non esiste un bucket in cui inserire tutti gli "errori logici" per catturare (o non catturare).

Poi c'è il botch IMNSHO major di avere SEHException ricava (via ExternalException ) SystemException e quindi rendi un%" normale "co_de% Quando si ottiene un SystemException , si desidera scrivere un dump e terminare il più velocemente possibile e iniziare con .NET 4 almeno alcune SEH eccezioni sono considerate Eccezioni di stato corrotte che non verranno catturate. Una buona cosa, e un fatto che rende la regola SEHException ancora più inutile, perché ora il tuo pigro CA1031 non catturerà comunque questi tipi peggiori.

Quindi, sembra che le altre cose del Framework derivino incoerentemente o catch (Exception) direttamente o tramite Exception , facendo qualsiasi tentativo di raggruppare le clausole catch con qualcosa come il moot di severità.

C'è un pezzo di Mr. Lippert di SystemException fame, chiamato Vexing Exception , dove espone alcune utili categorie di eccezioni: potresti sostenere che vuoi solo prendere "esogeni", eccetto ... C# della lingua e il design delle eccezioni di .NET framework impossibile "catturare solo quelli esogeni" in modo succinto. (e, ad esempio, un C# potrebbe molto bene essere un errore completamente recuperabile normale per un'API che deve allocare buffer che sono in qualche modo grandi)

La linea di fondo per me è che il modo in cui OutOfMemoryException cattura i blocchi e il modo in cui è progettata la gerarchia delle eccezioni Framework, la regola C# è oltre totalmente inutile . fa finta per aiutare con il problema di root di "non ingoiare le eccezioni" ma ingoiare le eccezioni non ha nulla a che fare con ciò che si cattura, ma con ciò che allora fa:

Ci sono almeno 4 modi per gestire legittimamente un CA1031 catturato e Exception sembra gestire solo superficialmente uno di essi (vale a dire il caso di rigetto).

Come nota a margine, c'è una funzione C # 6 chiamata Filtri di eccezione che renderà nuovamente% valid di CA1031 nuovamente valido da allora sarai in grado di filtrare correttamente, correttamente, correttamente le eccezioni che vuoi catturare, e ci sono meno motivi per scrivere CA1031 non filtrato.

    
risposta data 09.12.2014 - 15:37
fonte
4

La gestione delle eccezioni Pokemon (devi prenderle tutte!) non è certamente sempre male. Quando esponi un metodo a un cliente, in particolare a un utente finale, spesso è meglio catturare qualsiasi cosa e tutto piuttosto che bloccare e masterizzare l'applicazione.

Generalmente però dovrebbero essere evitati dove puoi. A meno che tu non possa intraprendere azioni specifiche in base al tipo di eccezione, è meglio non gestirlo e consentire all'eccezione di esplodere piuttosto che ingoiare l'eccezione o gestirla in modo errato.

Dai un'occhiata a questo SO risposta per più lettura.

    
risposta data 09.09.2012 - 17:37
fonte
1

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

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

In un ambiente distribuito il tuo metodo di registrazione potrebbe ritorcersi contro: rilevando un'eccezione generale potrebbe significare che il tuo programma mantiene ancora un blocco sul file di registro che impedisce ad altri utenti di creare registri.

    
risposta data 09.09.2012 - 19:54
fonte
0

I understand the rationale for this rule. But, in practice, if I want to take the same action regardless of the exception thrown, why would I handle each one specifically?

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

Furthermore, if I handle specific exceptions, what if the code I'm calling changes to throw a new exception in the future? Now I have to change my code to handle that new exception. Whereas if I simply caught Exception my code doesn't have to change.

Per il codice hobbistico va benissimo prendere Exception , ma per il codice di livello professionale che introduce un nuovo tipo di eccezione dovrebbe essere trattato con lo stesso rispetto di un cambiamento nella firma del metodo, cioè essere considerato un cambiamento di rottura. Se ti iscrivi a questo punto di vista, allora è immediatamente ovvio che tornare a cambiare (o verificare) il codice client è l'unica linea di condotta corretta.

For example, if Foo calls Bar, and Foo needs to stop processing regardless of the type of exception thrown by Bar, is there any advantage in being specific about the type of exception I'm catching?

Certo, perché non catturerai solo le eccezioni generate da Bar . Ci saranno anche eccezioni che i client di Bar o anche il runtime potrebbero generare durante il tempo in cui Bar è nello stack di chiamate. Un Bar ben scritto dovrebbe definire il proprio tipo di eccezione, se necessario, in modo che i chiamanti possano catturare in modo specifico le eccezioni emesse da solo.

So what if I want to ignore every exception but OutOfMemoryException?

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

    
risposta data 10.09.2012 - 10:59
fonte
0

Forse . Ci sono eccezioni ad ogni regola e nessuna regola dovrebbe essere seguita acriticamente. L'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 essere assolutamente sicuri che le modifiche apportate non interrompano l'attività principale dell'app.

Tuttavia , dovresti riflettere attentamente sui possibili motivi di errore prima di decidere di ignorarli tutti in silenzio. Ad esempio, cosa succede se il motivo dell'eccezione è:

  • un errore di programmazione nel metodo di registrazione che fa sì che generino sempre una particolare eccezione
  • 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 ripararlo? Ingoiare l'eccezione significa che non si sa 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 viene registrato ora, quindi non si sa mai!

    
risposta data 26.08.2015 - 18:02
fonte
0

Vorrei affrontare questo da una prospettiva logica piuttosto che tecnica,

Now I have to change my code to handle that new exception.

Bene, qualcuno dovrebbe gestirlo. Questa è l'idea. Gli scrittori del codice della libreria dovrebbero essere prudenti nell'aggiungere nuovi tipi di eccezioni, anche perché è probabile che si possano rompere i client, non dovresti incontrarli molto spesso.

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

Ecco la parte di bellezza: no non lo fai.

"Quindi, posso semplicemente guardare dall'altra parte e fare in modo che la cosa brutta che si apre scenda automaticamente sotto il tappeto e sia fatta con esso?"

No, non è così che funziona.

Il punto è che la raccolta di possibili eccezioni è sempre più ampia della collezione 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 piuttosto che inghiottirlo. Se non ti interessa un'eccezione che non ti aspetti, scommetti qualcuno su uno stack di chiamate e sarebbe sabotaggio uccidere un'eccezione prima che raggiunga il gestore.

"Ma ... ma ... allora una di quelle altre eccezioni di cui non mi importa 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 interrompere ciò che stai facendo e raccogliere un compito più urgente che è saltato fuori.

    
risposta data 11.12.2016 - 10:25
fonte
-3

Dovresti prendere le eccezioni generali al livello più alto di ogni processo e gestirle segnalando il bug nel miglior modo possibile e quindi terminando il processo.

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

    
risposta data 09.09.2012 - 23:54
fonte

Leggi altre domande sui tag