Come usare try catch per la gestione delle eccezioni è la migliore pratica

pur mantenendo il codice del mio collega anche da qualcuno che afferma di essere uno sviluppatore senior, vedo spesso il seguente codice:

try { //do something } catch { //Do nothing } 

o a volte scrivono le informazioni di registrazione per registrare i file come il seguente blocco di try catch

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception); } 

Mi chiedo solo se ciò che hanno fatto è la migliore pratica? Mi fa confondere perché nel mio modo di pensare gli utenti dovrebbero sapere cosa succede con il sistema.

Per favore, dammi un consiglio

La mia strategia di gestione delle eccezioni è:

  • Per catturare tutte le eccezioni non gestite agganciando l’ Application.ThreadException event , quindi decidere:

    • Per un’applicazione di interfaccia utente: per farla apparire all’utente con un messaggio di scuse (winforms)
    • Per un servizio o un’applicazione della console: collegarlo a un file (servizio o console)

Quindi racchiudo sempre ogni parte di codice eseguita esternamente in try/catch :

  • Tutti gli eventi triggersti ​​dall’infrastruttura di Winforms (carica, clicca, SelectedChanged …)
  • Tutti gli eventi triggersti ​​da componenti di terze parti

Quindi accludo in ‘try / catch’

  • Tutte le operazioni che conosco potrebbero non funzionare sempre (operazioni IO, calcoli con una potenziale zero divisione …). In tal caso, lancio una nuova ApplicationException("custom message", innerException) per tenere traccia di ciò che è realmente accaduto

Inoltre, faccio del mio meglio per ordinare correttamente le eccezioni . Ci sono delle eccezioni che:

  • devono essere mostrati all’utente immediatamente
  • richiedono un po ‘di elaborazione extra per mettere insieme le cose quando accadono per evitare problemi a cascata (es .: mettere .EndUpdate nella sezione finally durante un riempimento TreeView )
  • l’utente non si cura, ma è importante sapere cosa è successo. Quindi li registro sempre:

    • Nel registro eventi
    • o in un file .log sul disco

È buona norma progettare alcuni metodi statici per gestire le eccezioni nei gestori di errori di livello superiore dell’applicazione.

Mi sforzo anche di provare a:

  • Ricorda TUTTE le eccezioni sono bollite fino al livello più alto . Non è necessario mettere i gestori di eccezioni ovunque.
  • Le funzioni riutilizzabili o chiamate in profondità non hanno bisogno di visualizzare o registrare eccezioni: esse sono ribollite automaticamente o riconvertite con alcuni messaggi personalizzati nei miei gestori di eccezioni.

Quindi finalmente:

Cattivo:

 // DON'T DO THIS, ITS BAD try { ... } catch { // only air... } 

Inutili:

 // DONT'T DO THIS, ITS USELESS try { ... } catch(Exception ex) { throw ex; } 

Avere una prova finalmente senza una presa è perfettamente valida:

 try { listView1.BeginUpdate(); // If an exception occurs in the following code, then the finally will be executed // and the exception will be thrown ... } finally { // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT listView1.EndUpdate(); } 

Cosa faccio al massimo livello:

 // ie When the user clicks on a button try { ... } catch(Exception ex) { ex.Log(); // Log exception -- OR -- ex.Log().Display(); // Log exception, then show it to the user with apologies... } 

Cosa faccio in alcune funzioni chiamate:

 // Calculation module try { ... } catch(Exception ex) { // Add useful information to the exception throw new ApplicationException("Something wrong happened in the calculation module :", ex); } // IO module try { ... } catch(Exception ex) { throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex); } 

C’è molto da fare con la gestione delle eccezioni (eccezioni personalizzate) ma le regole che cerco di tenere a mente sono sufficienti per le semplici applicazioni che faccio.

Ecco un esempio di metodi di estensioni per gestire le eccezioni scoperte in modo comodo. Sono implementati in un modo in cui possono essere concatenati, ed è molto facile aggiungere l’elaborazione delle eccezioni rilevate.

 // Usage: try { // boom } catch(Exception ex) { // Only log exception ex.Log(); -- OR -- // Only display exception ex.Display(); -- OR -- // Log, then display exception ex.Log().Display(); -- OR -- // Add some user-friendly message to an exception new ApplicationException("Unable to calculate !", ex).Log().Display(); } // Extension methods internal static Exception Log(this Exception ex) { File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n"); return ex; } internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error) { MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img); return ex; } 

La migliore pratica è che la gestione delle eccezioni non dovrebbe mai hide problemi . Ciò significa che try-catch blocchi try-catch dovrebbero essere estremamente rari.

Ci sono 3 circostanze in cui l’uso di un try-catch ha senso.

  1. Gestisci sempre le eccezioni conosciute più in basso che puoi. Tuttavia, se ti aspetti un’eccezione, di solito è meglio provare prima. Ad esempio, le eccezioni di parsing, formattazione e aritmetica sono quasi sempre meglio gestite dai controlli logici prima, piuttosto che da uno specifico try-catch .

  2. Se è necessario eseguire un’operazione su un’eccezione (ad esempio, registrare o eseguire il rollback di una transazione), quindi ripetere l’eccezione.

  3. Affronta sempre le eccezioni sconosciute in alto come puoi: l’ unico codice che dovrebbe utilizzare un’eccezione e non il re-throw dovrebbe essere l’interfaccia utente o l’API pubblica.

Supponiamo che ti stia connettendo a un’API remota, qui sai di aspettarti determinati errori (e avere cose da fare in quelle circostanze), quindi questo è il caso 1:

 try { remoteApi.Connect() } catch(ApiConnectionSecurityException ex) { // User's security details have expired return false; } return true; 

Si noti che non vengono rilevate altre eccezioni, in quanto non sono previste.

Supponiamo ora che tu stia cercando di salvare qualcosa nel database. Dobbiamo ripristinarlo se fallisce, quindi abbiamo il caso 2:

 try { DBConnection.Save(); } catch { // Roll back the DB changes so they aren't corrupted on ANY exception DBConnection.Rollback(); // Re-throw the exception, it's critical that the user knows that it failed to save throw; } 

Nota che ripetiamo l’eccezione: il codice più in alto deve ancora sapere che qualcosa non ha funzionato.

Finalmente abbiamo l’interfaccia utente – qui non vogliamo avere eccezioni completamente non gestite, ma non vogliamo neanche nasconderle. Qui abbiamo un esempio del caso 3:

 try { // Do something } catch(Exception ex) { // Log exception for developers WriteException2LogFile(ex); // Display message to users DisplayWarningBox("An error has occurred, please contact support!"); } 

Tuttavia, la maggior parte dei framework API o UI ha modi generici di fare il caso 3. Ad esempio, ASP.Net ha una schermata di errore gialla che scarica i dettagli delle eccezioni, ma che può essere sostituita con un messaggio più generico nell’ambiente di produzione. Seguirle è una buona pratica perché consente di risparmiare molto codice, ma anche perché la registrazione degli errori e la visualizzazione dovrebbero essere decisioni di configurazione piuttosto che codificate.

Tutto questo significa che il caso 1 (eccezioni conosciute) e il caso 3 (gestione dell’interfaccia utente una tantum) hanno entrambi schemi migliori (evita l’errore previsto o la gestione dell’errore della mano nell’interfaccia utente).

Anche il caso 2 può essere sostituito da modelli migliori, ad esempio gli ambiti di transazione ( using blocchi che eseguono il rollback di qualsiasi transazione non impegnata durante il blocco) rendono più difficile agli sviluppatori ottenere il modello di best practice sbagliato.

Ad esempio, supponiamo di avere un’applicazione ASP.Net su larga scala. La registrazione degli errori può avvenire tramite ELMAH , la visualizzazione degli errori può essere un YSoD informativo a livello locale e un buon messaggio localizzato in produzione. Le connessioni al database possono essere tutte tramite gli ambiti di transazione e using blocchi. Non hai bisogno di un singolo blocco try-catch .

TL; DR: Le migliori pratiche in realtà non sono affatto l’uso try-catch blocchi try-catch .

Un’eccezione è un errore di blocco .

Prima di tutto, la migliore pratica dovrebbe essere non gettare eccezioni per qualsiasi tipo di errore, a meno che non si tratti di un errore di blocco .

Se l’errore sta bloccando , lancia l’eccezione. Una volta che l’eccezione è già stata lanciata, non è necessario nasconderlo perché è eccezionale; lascia che l’utente lo sappia (dovresti riformattare l’intera eccezione in qualcosa di utile per l’utente nell’interfaccia utente).

Il tuo compito come sviluppatore di software è quello di cercare di prevenire un caso eccezionale in cui alcuni parametri o situazioni di runtime possono finire in un’eccezione. Cioè, le eccezioni non devono essere distriggerste, ma queste devono essere evitate .

Ad esempio, se sai che alcuni valori interi potrebbero avere un formato non valido, usa int.TryParse invece di int.Parse . Ci sono molti casi in cui puoi farlo invece di dire semplicemente “se fallisce, lancia semplicemente un’eccezione”.

Le eccezioni di lancio sono costose.

Se, dopo tutto, viene lanciata un’eccezione, invece di scrivere l’eccezione al log una volta che è stata lanciata, una delle migliori pratiche è quella di catturarla in un gestore di eccezioni di prima scelta . Per esempio:

  • ASP.NET: Global.asax Application_Error
  • Altri: evento AppDomain.FirstChanceException .

La mia posizione è che i try / catch locali sono più adatti per gestire casi speciali in cui è ansible tradurre un’eccezione in un’altra, o quando si desidera “silenziarla” per un caso molto, molto, molto, molto, molto speciale (un bug di libreria lanciare un’eccezione non correlata che è necessario distriggersre per risolvere l’errore intero).

Per il resto dei casi:

  • Cerca di evitare le eccezioni.
  • Se questo non è ansible: gestori di eccezioni di prima scelta.
  • O utilizzare un aspetto PostSharp (AOP).

Rispondere a @thewhiteambit su qualche commento …

@thewhiteambit ha detto:

Le eccezioni non sono errori irreversibili, sono eccezioni! A volte non sono nemmeno errori, ma considerarli errori fatali è una comprensione completamente falsa di ciò che sono le eccezioni.

Prima di tutto, come un’eccezione non può essere nemmeno un errore?

  • Nessuna connessione al database => eccezione.
  • Formato di stringa non valido per analizzare qualche tipo => eccezione
  • Cercando di analizzare JSON e mentre l’input non è in realtà JSON => eccezione
  • Argomento null mentre l’object era previsto => eccezione
  • Alcune librerie ha un bug => genera un’eccezione imprevista
  • C’è una connessione socket e viene disconnessa. Quindi si tenta di inviare un messaggio => eccezione

Potremmo elencare i casi 1k di quando viene lanciata un’eccezione e, dopo tutto, uno dei possibili casi sarà un errore .

Un’eccezione è un errore, perché alla fine è un object che raccoglie informazioni diagnostiche – ha un messaggio e succede quando qualcosa va storto.

Nessuno farebbe un’eccezione quando non ci sono casi eccezionali. Le eccezioni dovrebbero essere errori di blocco perché una volta lanciati, se non provi a cadere nell’uso try / catch ed eccezioni per implementare il stream di controllo , significa che la tua applicazione / servizio interromperà l’operazione che è entrata in un caso eccezionale .

Inoltre, suggerisco a tutti di verificare il paradigma fail-fast pubblicato da Martin Fowler (e scritto da Jim Shore) . È così che ho sempre capito come gestire le eccezioni, anche prima di arrivare a questo documento qualche tempo fa.

[…] considerarli Errori fatali è completamente falsa comprensione di quali eccezioni sono.

Solitamente le eccezioni riducono il stream operativo e vengono gestite per convertirli in errori comprensibili all’uomo. Pertanto, sembra che un’eccezione sia effettivamente un paradigma migliore per gestire i casi di errore e lavorare su di essi per evitare un arresto completo di applicazioni / servizi e informare l’utente / consumatore che qualcosa è andato storto.

Altre risposte alle preoccupazioni di @thewhiteambit

Ad esempio, in caso di mancata connessione al database, il programma potrebbe continuare eccezionalmente con la scrittura su un file locale e inviare le modifiche al database una volta che è nuovamente disponibile. Il tuo casting String-To-Number non valido può essere tentato di analizzare nuovamente con l’interpretazione locale della lingua su Exception, come se tu provassi l’inglese predefinito su Parse (“1,5”) fallendo e tu provi di nuovo con l’interpretazione tedesca che è completamente bene perché usiamo virgola anziché punto come separatore. Vedete queste eccezioni non devono nemmeno essere bloccanti, hanno solo bisogno di una gestione delle eccezioni.

  1. Se la tua app potrebbe funzionare offline senza dati persistenti nel database, non dovresti usare eccezioni , poiché l’implementazione del stream di controllo usando try/catch è considerata come anti-pattern. Il lavoro offline è un ansible caso d’uso, quindi implementa il stream di controllo per verificare se il database è accessibile o meno, non aspetti finché non è irraggiungibile .

  2. Anche la parsing è un caso previsto ( non CASE ECCEZIONALE ). Se ti aspetti questo, non usi le eccezioni per fare il controllo del stream! . Ottieni alcuni metadati dall’utente per sapere qual è la sua / la sua cultura e usi i formattatori per questo! .NET supporta anche questo e altri ambienti e un’eccezione perché la formattazione del numero deve essere evitata se si prevede un utilizzo specifico della propria applicazione / servizio .

Un’eccezione non gestita di solito diventa un errore, ma le eccezioni in sé non sono codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

Questo articolo è solo un’opinione o un punto di vista dell’autore.

Dal momento che Wikipedia può essere anche solo l’opinione dell’autore (i) articolato (s), non direi che è il dogma , ma controlla cosa dice Coding by exception article da qualche parte in qualche paragrafo:

[…] L’utilizzo di queste eccezioni per gestire errori specifici che si presentano per continuare il programma è chiamato codifica per eccezione. Questo anti-pattern può rapidamente degradare il software in termini di prestazioni e manutenibilità.

Dice anche da qualche parte:

Uso delle eccezioni errato

Spesso la codifica in base all’eccezione può causare ulteriori problemi nel software con un utilizzo non corretto delle eccezioni. Oltre a utilizzare la gestione delle eccezioni per un problema univoco, l’utilizzo non corretto delle eccezioni lo prende ulteriormente eseguendo il codice anche dopo che l’eccezione è stata sollevata. Questo povero metodo di programmazione assomiglia al metodo goto in molti linguaggi software, ma si verifica solo dopo che è stato rilevato un problema nel software.

Onestamente, credo che il software non possa essere sviluppato senza prendere sul serio i casi d’uso. Se lo sai …

  • Il tuo database può andare offline …
  • Alcuni file possono essere bloccati …
  • Alcune formattazioni potrebbero non essere supportate …
  • Qualche convalida del dominio potrebbe fallire …
  • La tua app dovrebbe funzionare in modalità offline …
  • qualunque sia il caso d’uso

non userete eccezioni per questo . Supporterai questi casi d’uso usando un stream di controllo regolare.

E se un caso d’uso inaspettato non è coperto, il tuo codice fallirà velocemente, perché genererà un’eccezione . Giusto, perché un’eccezione è un caso eccezionale .

D’altra parte, e infine, a volte si coprono casi eccezionali che generano eccezioni previste , ma non li si butta per implementare il stream di controllo. Lo fai perché vuoi notificare ai livelli superiori che non supporti alcuni casi d’uso o che il tuo codice non funziona con alcuni argomenti o dati / proprietà dell’ambiente dati.

L’unica volta in cui dovresti preoccuparti per gli utenti di qualcosa che è accaduto nel codice è se c’è qualcosa che possono o devono fare per evitare il problema. Se possono modificare i dati su un modulo, premere un pulsante o modificare l’impostazione di un’applicazione per evitare il problema, quindi informarli. Ma gli avvisi o gli errori che l’utente non ha la capacità di evitare li fa perdere la fiducia nel prodotto.

Le eccezioni e i log sono per te, lo sviluppatore, non il tuo utente finale. Capire la cosa giusta da fare quando si cattura ogni eccezione è molto meglio che applicare una regola d’oro o fare affidamento su una rete di sicurezza a livello di applicazione.

La codifica senza cervello è l’UNICO tipo di codifica errata. Il fatto che tu senta che c’è qualcosa di meglio che può essere fatto in quelle situazioni dimostra che sei investito in una buona codifica, ma evita di provare a timbrare alcune regole generiche in queste situazioni e capire il motivo per cui qualcosa deve essere buttato in primo piano e che cosa puoi fare per recuperare da esso.

So che questa è una vecchia domanda, ma nessuno ha menzionato l’articolo MSDN, ed è stato il documento che in realtà lo ha chiarito per me, MSDN ha un ottimo documento su questo, dovresti prendere delle eccezioni quando sono vere le seguenti condizioni:

  • Hai una buona comprensione del motivo per cui l’eccezione potrebbe essere generata e puoi implementare un recupero specifico, ad esempio chiedendo all’utente di immettere un nuovo nome file quando si cattura un object FileNotFoundException.

  • È ansible creare e lanciare una nuova eccezione più specifica.

 int GetInt(int[] array, int index) { try { return array[index]; } catch(System.IndexOutOfRangeException e) { throw new System.ArgumentOutOfRangeException( "Parameter index is out of range."); } } 
  • Si desidera gestire parzialmente un’eccezione prima di passarla per una gestione aggiuntiva. Nell’esempio seguente, un blocco catch viene utilizzato per aggiungere una voce a un log degli errori prima di rilanciare l’eccezione.
  try { // Try to access a resource. } catch (System.UnauthorizedAccessException e) { // Call a custom error logging procedure. LogError(e); // Re-throw the error. throw; } 

Suggerirei di leggere l’intera sezione ” Gestione eccezioni ed eccezioni ” e anche Best practice per le eccezioni .

L’approccio migliore è il secondo (quello in cui si specifica il tipo di eccezione). Il vantaggio di questo è che sai che questo tipo di eccezione può verificarsi nel tuo codice. Stai gestendo questo tipo di eccezione e puoi riprendere. Se si presenta qualche altra eccezione, significa che qualcosa non va che ti aiuterà a trovare bug nel tuo codice. L’applicazione finirà per bloccarsi, ma verrai a sapere che c’è qualcosa che ti sei perso (bug) che deve essere risolto.

Il secondo approccio è buono.

Se non si desidera mostrare l’errore e confondere l’utente dell’applicazione mostrando un’eccezione di runtime (ad es. Errore) che non è correlata ad essi, è sufficiente registrare l’errore e il team tecnico può cercare il problema e risolverlo.

 try { //do some work } catch(Exception exception) { WriteException2LogFile(exception);//it will write the or log the error in a text file } 

Vi raccomando di andare al secondo approccio per l’intera applicazione.

Lasciare il blocco catch vuoto è la cosa peggiore da fare. Se c’è un errore, il modo migliore per gestirlo è:

  1. Registralo in file \ database ecc.
  2. Prova a risolverlo al volo (magari cercando un modo alternativo di fare quell’operazione)
  3. Se non riusciamo a risolverlo, comunica all’utente che c’è qualche errore e, naturalmente, interrompi l’operazione

Per me, gestire l’eccezione può essere vista come regola aziendale. Ovviamente, il primo approccio è inaccettabile. Il secondo è migliore e potrebbe essere corretto al 100% se il contesto lo dice. Ora, ad esempio, stai sviluppando un componente aggiuntivo di Outlook. Se aggiungete un’eccezione non gestita, l’utente di Outlook potrebbe ora saperlo poiché l’outlook non si distruggerà da solo a causa di un plug-in non riuscito. E hai difficoltà a capire cosa è andato storto. Pertanto, il secondo approccio in questo caso, per me, è corretto. Accanto alla registrazione dell’eccezione, potresti decidere di visualizzare un messaggio di errore all’utente – lo considero una regola aziendale.

È buona pratica lanciare un’eccezione quando si verifica l’errore. Perché si è verificato un errore e non dovrebbe essere nascosto.

Ma nella vita reale puoi avere diverse situazioni quando vuoi nasconderlo

  1. Si fa affidamento su componenti di terze parti e si desidera continuare il programma in caso di errore.
  2. Hai un business case che devi continuare in caso di errore

Dovresti considerare queste Linee guida di progettazione per le eccezioni

  • Lancio di eccezione
  • Utilizzo dei tipi di eccezione standard
  • Eccezioni e prestazioni

https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions

La catch senza argomenti è semplicemente mangiare l’eccezione ed è inutile. Cosa succede se si verifica un errore fatale? Non c’è modo di sapere cosa è successo se usi la cattura senza argomenti.

Una dichiarazione di cattura dovrebbe catturare eccezioni più specifiche come FileNotFoundException e quindi alla fine dovresti catturare Exception che catturerebbe qualsiasi altra eccezione e le registrerebbe.

A volte è necessario trattare le eccezioni che non dicono nulla agli utenti.

Il mio modo è:

  • Per rilevare le eccezioni non risolte a livello di applicazione (ad esempio in global.asax) per eccezioni critiche (l’applicazione non può essere utile). Queste esecuzioni non sto prendendo sul posto. Basta registrarli a livello di app e lasciare che il sistema faccia il suo lavoro.
  • Catch “on place” e mostra alcune informazioni utili all’utente (inserito il numero sbagliato, imansible analizzare).
  • Catch on place e non fare nulla su problemi marginali come “controllerò le informazioni di aggiornamento sullo sfondo, ma il servizio non è in esecuzione”.

Sicuramente non deve essere la migliore pratica. 😉

Con Eccezioni, provo il seguente:

Innanzitutto, rilevo tipi speciali di eccezioni come la divisione per zero, le operazioni IO e così via e scrivo il codice in base a ciò. Ad esempio, una divisione per zero, a seconda della provenienza dei valori che potevo avvisare l’utente (ad esempio un semplice calcolatore in quanto in un calcolo centrale (non gli argomenti) arriva in una divisione per zero) o per trattare silenziosamente quell’eccezione, la registrazione e continua l’elaborazione.

Quindi provo a catturare le restanti eccezioni e le registro. Se ansible, consentire l’esecuzione del codice, altrimenti avvisare l’utente che si è verificato un errore e chiedergli di inviare un messaggio di errore.

Nel codice, qualcosa del genere:

 try{ //Some code here } catch(DivideByZeroException dz){ AlerUserDivideByZerohappened(); } catch(Exception e){ treatGeneralException(e); } finally{ //if a IO operation here i close the hanging handlers for example }