it-swarm.dev

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
}

oppure a volte scrivono informazioni di registrazione per registrare file come il seguente blocco try catch 

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

Mi chiedo solo se quello 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.

186
Toan Nguyen

La mia strategia di gestione delle eccezioni è:

  • Per catturare tutte le eccezioni non gestite agganciando il codice 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)

Poi racchiudo sempre ogni pezzo di codice che viene eseguito esternamente in try/catch:

  • Tutti gli eventi attivati ​​dall'infrastruttura di Winforms (carica, clicca, SelectedChanged ...)
  • Tutti gli eventi attivati ​​da componenti di terze parti

Quindi accludo in 'try/catch'

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

Inoltre, faccio del mio meglio per ordinare le eccezioni correttamente. 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 che TUTTE le eccezioni sono riportate 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: vengono ribaltate automaticamente o riconvertite con alcuni messaggi personalizzati nei miei gestori di eccezioni.

Quindi finalmente:

Male:

// 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:

// i.e 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 semplice 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;
}
280
Larry

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

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

  1. Gestisci sempre le eccezioni known più in basso che puoi. Tuttavia, se ti aspetti un'eccezione, di solito è meglio provare prima. Ad esempio, le eccezioni di analisi, 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 la registrazione o il rollback di una transazione), quindi ripetere l'eccezione.

  3. Affronta sempre le eccezioni unknown in alto come puoi: il codice only che dovrebbe consumare un'eccezione e non rilanciarlo 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, poiché 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 è fallito.

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. Seguirli è 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.

Ciò significa che il caso 1 (eccezioni conosciute) e il caso 3 (gestione dell'interfaccia utente una tantum) presentano entrambi schemi migliori (evita l'errore atteso o la gestione degli errori della mano nell'interfaccia utente).

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

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 locale e un messaggio localizzato Nice in produzione. Le connessioni al database possono essere tutte tramite gli ambiti di transazione e i blocchi using. Non hai bisogno di un unico blocco try-catch.

TL; DR: La pratica migliore è in realtà non utilizzare i blocchi try-catch.

55
Keith

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

  • 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 oggetto FileNotFoundException.

  • È possibile 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 " Exception and Exception Handling " e anche Best Practices for Exceptions .

5
Hamid Mosalla

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 qualche regola generica in queste situazioni e capire il motivo per cui qualcosa deve essere buttato in primo piano e che cosa puoi fare per riprenderci.

5
ChrisCW

L'approccio migliore è il secondo (quello in cui si specifica il tipo di eccezione). Il vantaggio di questo è che tu sai che questo tipo di eccezione può verificarsi nel tuo codice. Stai gestendo questo tipo di eccezione e puoi riprendere. Se si presenta un'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.

1
fhnaseer

Il secondo approccio è buono.

Se non si vuole mostrare l'errore e confondere l'utente dell'applicazione mostrando l'eccezione di runtime (cioè l'errore) che non è correlato a loro, basta 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 per il secondo approccio per l'intera applicazione.

1
Pranay Rana

È 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 caso aziendale che devi continuare in caso di errore
0
Gregory Nozik

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
0
Stasel

Posso dirti qualcosa:

Lo snippet n. 1 non è accettabile perché ignora l'eccezione. (lo sta inghiottendo come se nulla fosse accaduto).

Quindi non aggiungere blocchi catch che non fanno nulla o solo retrocedono.

Il blocco di cattura dovrebbe aggiungere un certo valore. Ad esempio, messaggio di output per l'utente finale o errore di registro.

Non utilizzare l'eccezione per la normale logica del programma di flusso. Per esempio:

per esempio convalida dell'input. <- Questa non è una situazione eccezionale valida, piuttosto dovresti scrivere il metodo IsValid(myInput); per verificare se l'elemento in input è valido o meno.

Codice di progettazione per evitare eccezioni. Per esempio:

int Parse(string input);

Se passiamo un valore che non può essere analizzato in int, questo metodo genererebbe ed eccezione, invece di scrivere qualcosa del genere:

bool TryParse(string input,out int result); <- questo metodo restituirebbe booleano indicando se parse ha avuto successo.

Forse questo è un po 'fuori dalla portata di questa domanda, ma spero che questo ti aiuterà a prendere le giuste decisioni quando si tratta di try {} catch(){} ed eccezioni.

0
Roxy'Pro

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, non può 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. ;-)

0
Fanda

Con Eccezioni, provo il seguente:

Innanzitutto, rilevo tipi speciali di eccezioni come divisione per zero, IO operazioni e così via e scrivere codice in base a ciò. Ad esempio, una divisione per zero, in base alla provenienza dei valori che potevo avvisare l'utente (ad esempio una semplice calcolatrice 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 possibile, consentire l'esecuzione del codice, altrimenti avvisare l'utente che si è verificato un errore e chiedere 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
}
0
Sorcerer86pt

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 aggiungi un'eccezione non gestita, l'utente di Outlook potrebbe ora saperlo poiché 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. Oltre a registrare l'eccezione, potresti decidere di visualizzare un messaggio di errore all'utente - lo considero una regola aziendale.

0
Thai Anh Duc

Dovresti prendere in considerazione 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

0
Jaider

Il catch senza argomenti è semplicemente eating 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 catch dovrebbe catturare più specifiche Eccezioni come FileNotFoundException e quindi alla fine end dovresti prendere Exception che catturerebbe ogni altra eccezione e le registrerebbe.

0
Anirudha