Domanda

Sono bloccato a decidere come gestire le eccezioni nella mia applicazione.

Molto se i miei problemi con le eccezioni derivano da 1) accedere ai dati tramite un servizio remoto o 2) deserializzare un oggetto JSON. Sfortunatamente non posso garantire il successo di nessuna di queste attività (tagliare la connessione di rete, oggetto JSON non valido che è fuori dal mio controllo).

Di conseguenza, se riscontro un'eccezione, la catturo semplicemente all'interno della funzione e restituisco FALSO al chiamante. La mia logica è che tutto ciò che interessa al chiamante è se l'attività ha avuto successo, non perché non abbia avuto successo.

Ecco un codice di esempio (in JAVA) di un metodo tipico)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

Penso che questo approccio vada bene, ma sono davvero curioso di sapere quali sono le migliori pratiche per la gestione delle eccezioni (dovrei davvero creare un'eccezione su uno stack di chiamate?).

In sintesi delle domande chiave:

  1. Va bene solo catturare le eccezioni ma non cancellarle o avvisare formalmente il sistema (tramite un registro o una notifica all'utente)? ??
  2. Quali sono le migliori pratiche per le eccezioni che non comportano tutto ciò che richiede un blocco try / catch?

Follow-up / Modifica

Grazie per tutto il feedback, ho trovato alcune eccellenti fonti sulla gestione delle eccezioni online:

Sembra che la gestione delle eccezioni sia una di quelle cose che variano in base al contesto. Ma soprattutto, si dovrebbe essere coerenti nel modo in cui gestiscono le eccezioni all'interno di un sistema.

Inoltre, fai attenzione alla rotazione del codice tramite tentativi / catture eccessive o il mancato rispetto di un'eccezione (un'eccezione sta avvertendo il sistema, cos'altro deve essere avvertito?).

Inoltre, questo è un bel commento di m3rLinEz .

  

Tendo a concordare con Anders Hejlsberg e con te che solo il maggior numero di chiamanti   attenzione se l'operazione ha esito positivo o meno.

Da questo commento vengono sollevate alcune domande a cui pensare quando si tratta di eccezioni:

  • Qual è il punto in cui viene generata questa eccezione?
  • Come ha senso gestirlo?
  • Al chiamante interessa davvero l'eccezione o si preoccupano solo se la chiamata è andata a buon fine?
  • È forzato un chiamante a gestire un'eccezione potenziale?
  • Sei rispettoso degli idomi della lingua?
    • Hai davvero bisogno di restituire una bandiera di successo come booleana? Restituire un valore booleano (o un int) è più una mentalità C che una Java (in Java gestiresti solo l'eccezione).
    • Segui i costrutti di gestione degli errori associati alla lingua :)!
È stato utile?

Soluzione

Mi sembra strano che tu voglia catturare le eccezioni e trasformarle in codici di errore. Perché pensi che il chiamante preferirebbe i codici di errore rispetto alle eccezioni quando quest'ultimo è il valore predefinito sia in Java che in C #?

Per quanto riguarda le tue domande:

  1. Dovresti rilevare solo le eccezioni che puoi effettivamente gestire. Appena catturare le eccezioni non è la cosa giusta da fare nella maggior parte dei casi. Esistono alcune eccezioni (ad esempio eccezioni di registrazione e di smistamento tra le discussioni) ma anche per quei casi dovresti generalmente riproporre le eccezioni.
  2. Non dovresti assolutamente avere molte dichiarazioni try / catch nel tuo codice. Ancora una volta, l'idea è di catturare solo le eccezioni che puoi gestire. È possibile includere un gestore di eccezioni più in alto per trasformare qualsiasi non gestito eccezioni in qualcosa di utile per l'utente finale ma altrimenti non dovresti provare a cogliere ogni singola eccezione ogni posto possibile.

Altri suggerimenti

Questo dipende dall'applicazione e dalla situazione. Se stai costruendo un componente di libreria, dovresti creare delle eccezioni, sebbene debbano essere racchiuse in modo da essere contestuali al tuo componente. Ad esempio, se stai costruendo un database Xml e supponiamo che tu stia utilizzando il file system per archiviare i tuoi dati e che tu stia utilizzando le autorizzazioni del file system per proteggere i dati. Non si vorrebbe eliminare un'eccezione FileIOAccessDenied che perde l'implementazione. Dovresti invece racimolare l'eccezione e generare un errore AccessDenied. Ciò è particolarmente vero se si distribuisce il componente a terzi.

Come se fosse giusto ingoiare eccezioni. Dipende dal tuo sistema. Se l'applicazione è in grado di gestire i casi di errore e non vi è alcun vantaggio nel notificare all'utente il motivo dell'errore, procedere, anche se si consiglia vivamente di registrare l'errore. Ho sempre trovato la frustata chiamata per aiutare a risolvere un problema e scoprire che stavano inghiottendo l'eccezione (o sostituendolo e lanciandone uno nuovo invece di impostare l'eccezione interna).

In generale utilizzo le seguenti regole:

  1. Nei miei componenti & amp; librerie Prendo un'eccezione solo se ho intenzione di gestirlo o fare qualcosa basato su di esso. O se desidero fornire ulteriori informazioni contestuali in un'eccezione.
  2. Uso un tentativo generale di cattura nel punto di ingresso dell'applicazione o il livello più alto possibile. Se arriva un'eccezione, la registro e la lascio fallire. Idealmente, le eccezioni non dovrebbero mai arrivare qui.

Trovo che il seguente codice sia un odore:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

Codice come questo non serve a nulla e non dovrebbe essere incluso.

Vorrei raccomandare un'altra buona fonte sull'argomento. È un'intervista con gli inventori di C # e Java, rispettivamente Anders Hejlsberg e James Gosling, sul tema dell'eccezione controllata di Java.

Fallimenti ed eccezioni

Ci sono anche grandi risorse nella parte inferiore della pagina.

Tendo a concordare con Anders Hejlsberg e con te sul fatto che alla maggior parte dei chiamanti interessa solo se l'operazione ha esito positivo o meno.

  

Bill Venners : hai menzionato   problemi di scalabilità e versioning   rispetto alle eccezioni verificate.   Potresti chiarire con cosa intendi   quei due problemi?

     

Anders Hejlsberg : iniziamo con   controllo delle versioni, perché i problemi sono   abbastanza facile da vedere lì. Diciamo io   creare un metodo foo che lo dichiari   genera eccezioni A, B e C. In   versione due di foo, voglio aggiungere a   un sacco di funzioni, e ora potrebbe essere   gettare l'eccezione D. È una rottura   cambia per me per aggiungere D ai tiri   clausola di tale metodo, perché   chiamante esistente di quel metodo sarà   quasi certamente non lo gestisco   fa eccezione.

     

Aggiunta di una nuova eccezione a un tiro   la clausola in una nuova versione rompe il client   codice. È come aggiungere un metodo a un   interfaccia. Dopo aver pubblicato un   interfaccia, è per tutti pratici   scopi immutabili, perché qualsiasi   la sua implementazione potrebbe avere il   metodi che si desidera aggiungere in   prossima versione. Quindi devi creare   una nuova interfaccia invece. allo stesso modo   con le eccezioni, avresti entrambi   per creare un metodo completamente nuovo chiamato   foo2 che genera più eccezioni, o   dovresti prendere l'eccezione D in   il nuovo foo e trasforma la D in   una A, B o C.

     

Bill Venners : Ma non stai rompendo   il loro codice in quel caso comunque, anche   in una lingua senza controllo   eccezioni? Se la nuova versione di foo   lancerà una nuova eccezione   i clienti dovrebbero pensare alla gestione,   non è il loro codice rotto solo dal   fatto che non se l'aspettavano   eccezione quando hanno scritto il codice?

     

Anders Hejlsberg : No, perché in molti   dei casi, alla gente non importa. Sono   non gestirà nessuno di questi   eccezioni. C'è un livello inferiore   gestore di eccezioni attorno al loro messaggio   ciclo continuo. Quel gestore lo farà   aprire una finestra di dialogo che dice cosa è successo   sbagliato e continua. I programmatori   proteggere il loro codice scrivendo try   finalmente è ovunque, quindi torneranno   correttamente se si verifica un'eccezione,   ma in realtà non sono interessati   gestire le eccezioni.

     

La clausola di lancio, almeno la strada   è implementato in Java, no   necessariamente forzarti a gestire il   eccezioni, ma se non gestisci   loro, ti costringe a riconoscere   esattamente quali eccezioni potrebbero passare   attraverso. Richiede a entrambi   catturare le eccezioni dichiarate o metterle   nella tua clausola dei tiri. Lavorare   attorno a questo requisito, la gente lo fa   cose ridicole. Ad esempio, loro   decorare ogni metodo con, " tiri   Eccezione. & Quot; Questo completamente   sconfigge la funzionalità e hai appena creato   il programmatore scrive più ingobbito   gunk. Questo non aiuta nessuno.

EDIT: aggiunti ulteriori dettagli sulla conversione

Le eccezioni verificate sono un problema controverso in generale e in Java in particolare (in seguito cercherò di trovare alcuni esempi per coloro che sono a favore e si oppongono a loro).

Come regole empiriche, la gestione delle eccezioni dovrebbe essere qualcosa attorno a queste linee guida, in nessun ordine particolare:

  • Per motivi di manutenibilità, registra sempre le eccezioni in modo che quando inizi a vedere i bug, il registro ti aiuti a indicarti il ??punto in cui il tuo bug è probabilmente iniziato. Non lasciare mai printStackTrace () o simili, è probabile che uno dei tuoi utenti alla fine ottenga una di quelle tracce dello stack e abbia conoscenza esattamente zero su cosa fare con esso.
  • Cattura le eccezioni che puoi gestire e solo quelle, e gestiscile , non le gettano solo in pila.
  • Prendi sempre una classe di eccezione specifica e in genere non dovresti mai prendere il tipo Exception , molto probabilmente inghiottirai eccezioni altrimenti importanti.
  • Non intercettare mai (mai) Errore s !! , il che significa: Non prendere mai gettabile s come Errore sono sottoclassi di quest'ultimo. Errore sono problemi che molto probabilmente non sarai mai in grado di gestire (ad esempio OutOfMemory o altri problemi di JVM)

Per quanto riguarda il tuo caso specifico, assicurati che qualsiasi client che chiami il tuo metodo riceverà il valore di ritorno corretto. Se qualcosa non riesce, un metodo di ritorno booleano potrebbe restituire false, ma assicurati che i posti che chiami quel metodo siano in grado di gestirlo.

Dovresti solo cogliere le eccezioni che puoi affrontare. Ad esempio, se hai a che fare con la lettura su una rete e la connessione scade e ottieni un'eccezione, puoi riprovare. Tuttavia, se stai leggendo su una rete e ottieni un'eccezione IndexOutOfBounds, non puoi davvero gestirlo perché non (bene, in questo caso non lo saprai) cosa lo ha causato. Se hai intenzione di restituire false o -1 o null, assicurati che si tratti di eccezioni specifiche. Non voglio che una libreria che sto usando restituisca un falso su una rete letto quando l'eccezione generata è che l'heap è esaurito.

Le eccezioni sono errori che non fanno parte della normale esecuzione del programma. A seconda di ciò che fa il tuo programma e dei suoi usi (cioè un elaboratore di testi rispetto a un monitor cardiaco) vorrai fare diverse cose quando riscontri un'eccezione. Ho lavorato con il codice che utilizza le eccezioni come parte della normale esecuzione ed è sicuramente un odore di codice.

Ex.

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

Questo codice mi rende barf. IMO non dovresti recuperare da eccezioni a meno che non sia un programma critico. Se si generano eccezioni, allora stanno accadendo cose brutte.

Tutto quanto sopra sembra ragionevole e spesso il tuo posto di lavoro può avere una politica. Al nostro posto abbiamo definito i tipi di eccezione: SystemException (non selezionata) e ApplicationException (selezionata).

Abbiamo concordato che è improbabile che SystemException sia recuperabile e che verrà gestito una volta in alto. Per fornire ulteriore contesto, i nostri SystemException sono estesi per indicare dove si sono verificati, ad es. RepositoryException , ServiceEception , ecc.

ApplicationException potrebbe avere un significato commerciale come InsufficientFundsException e dovrebbe essere gestito dal codice client.

Senza un esempio concreto, è difficile commentare la tua implementazione, ma non userei mai i codici di ritorno, sono un problema di manutenzione. È possibile ingoiare un'eccezione, ma è necessario decidere il perché e registrare sempre l'evento e lo stacktrace. Infine, poiché il tuo metodo non ha altre elaborazioni è abbastanza ridondante (tranne l'incapsulamento?), Quindi doactualStuffOnObject (p_jsonObject); potrebbe restituire un valore booleano!

Dopo aver riflettuto e esaminato il tuo codice, mi sembra che tu stia semplicemente ridisegnando l'eccezione come booleana. Potresti semplicemente lasciare che il metodo passi questa eccezione (non devi nemmeno catturarlo) e gestirlo nel chiamante, poiché quello è il luogo in cui conta. Se l'eccezione farà sì che il chiamante riprovi questa funzione, il chiamante dovrebbe essere quello che rileva l'eccezione.

A volte può succedere che l'eccezione che stai riscontrando non abbia senso per il chiamante (ovvero è un'eccezione di rete), nel qual caso dovresti racchiuderla in un'eccezione specifica del dominio.

Se d'altra parte, l'eccezione segnala un errore irrecuperabile nel programma (ovvero il risultato finale di questa eccezione sarà la chiusura del programma) Personalmente mi piace renderlo esplicito catturandolo e generando un'eccezione di runtime.

Se utilizzerai il modello di codice nell'esempio, chiamalo TryDoSomething e ottieni solo eccezioni specifiche.

Inoltre considera l'utilizzo di un Filtro eccezioni quando si registrano eccezioni a fini diagnostici. VB ha il supporto linguistico per i filtri di eccezione. Il collegamento al blog di Greggm ha un'implementazione che può essere utilizzata da C #. I filtri di eccezione hanno proprietà migliori per il debuggability rispetto a catch e rethrow. In particolare, è possibile registrare il problema nel filtro e consentire all'eccezione di continuare a propagarsi. Tale metodo consente a un debugger JIT (Just in Time) di allegare lo stack originale completo. Un rilancio interrompe lo stack nel punto in cui è stato riproposto.

I casi in cui TryXXXX ha senso sono quando si esegue il wrapping di una funzione di terze parti che genera casi che non sono veramente eccezionali o che sono difficili da testare senza chiamare la funzione. Un esempio potrebbe essere qualcosa del tipo:

// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse); 

bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
     try
     {
         parsedInt = ParseHexidecimal(numberToParse);
         return true;
     }
     catch(NumberNotHexidecimalException ex)
     {
         parsedInt = 0;
         return false;
     }
     catch(Exception ex)
     {
         // Implement the error policy for unexpected exceptions:
         // log a callstack, assert if a debugger is attached etc.
         LogRetailAssert(ex);
         // rethrow the exception
         // The downside is that a JIT debugger will have the next
         // line as the place that threw the exception, rather than
         // the original location further down the stack.
         throw;
         // A better practice is to use an exception filter here.
         // see the link to Exception Filter Inject above
         // http://code.msdn.microsoft.com/ExceptionFilterInjct
     }
}

Se usi uno schema come TryXXX o meno è più una domanda di stile. La questione di catturare tutte le eccezioni e di ingoiarle non è un problema di stile. Assicurati che si possano propagare eccezioni impreviste!

Suggerisco di prendere i tuoi spunti dalla libreria standard per la lingua che stai usando. Non posso parlare per C #, ma diamo un'occhiata a Java.

Ad esempio java.lang.reflect.Array ha un metodo set statico:

static void set(Object array, int index, Object value);

Il modo C sarebbe

static int set(Object array, int index, Object value);

... con il valore restituito come indicatore di successo. Ma non sei più nel mondo C.

Una volta accettate le eccezioni, dovresti scoprire che rende il tuo codice più semplice e chiaro, allontanando il tuo codice di gestione degli errori dalla tua logica principale. Cerca di avere molte affermazioni in un singolo blocco try .

Come altri hanno notato, dovresti essere il più specifico possibile nel tipo di eccezione che ricevi.

Se prendi un'eccezione e restituisci false, dovrebbe trattarsi di un'eccezione molto specifica. Non lo stai facendo, li stai prendendo tutti e stai tornando falso. Se ricevo un'eccezione MyCarIsOnFireException ne voglio sapere subito! Il resto delle eccezioni non mi interessa. Quindi dovresti avere una pila di gestori delle eccezioni che dicono "whoa whoa qualcosa qui non va" " per alcune eccezioni (ripensare o catturare e ricodificare una nuova eccezione che spiega meglio cosa è successo) e restituire false per altri.

Se questo è un prodotto che lancerai, dovresti registrare quelle eccezioni da qualche parte, ti aiuterà a mettere a punto le cose in futuro.

Modifica: per quanto riguarda la questione di avvolgere tutto in un tentativo / cattura, penso che la risposta sia sì. Le eccezioni dovrebbero essere così rare nel tuo codice che il codice nel blocco catch viene eseguito così raramente da non compromettere affatto le prestazioni. Un'eccezione dovrebbe essere uno stato in cui la tua macchina a stati si è rotta e non sa cosa fare. Almeno ripensare un'eccezione che spiega cosa stava succedendo in quel momento e ha l'eccezione catturata al suo interno. " Eccezione nel metodo doSomeStuff () " non è molto utile per chiunque debba capire perché si è rotto durante una vacanza (o per un nuovo lavoro).

La mia strategia:

Se la funzione originale ha restituito void la cambio per restituire bool . Se si è verificata un'eccezione / errore, restituire false , se tutto andava bene restituire vero .

Se la funzione deve restituire qualcosa, quando si verifica un'eccezione / errore restituire null , altrimenti l'elemento restituibile.

Invece di bool potrebbe essere restituita una stringa contenente la descrizione dell'errore.

In ogni caso prima di restituire qualcosa, registra l'errore.

Alcune risposte eccellenti qui. Vorrei aggiungere che se finisci con qualcosa come quello che hai pubblicato, almeno stampa più della traccia dello stack. Di 'quello che stavi facendo in quel momento, e Ex.getMessage (), per dare allo sviluppatore una possibilità di combattimento.

I blocchi try / catch formano un secondo set di logica incorporato nel primo set (principale), in quanto tali sono un ottimo modo per estrarre il codice spaghetti illeggibile, difficile da eseguire il debug.

Tuttavia, usati ragionevolmente fanno miracoli nella leggibilità, ma dovresti semplicemente seguire due semplici regole:

  • usali (con parsimonia) a basso livello per rilevare i problemi di gestione delle librerie e ripercorrerli nel flusso logico principale. La maggior parte della gestione degli errori che vogliamo dovrebbe provenire dal codice stesso, come parte dei dati stessi. Perché creare condizioni speciali, se i dati di ritorno non sono speciali?

  • usa un grande gestore di livello superiore per gestire una o tutte le strane condizioni che si presentano nel codice che non sono rilevate a un livello basso. Fai qualcosa di utile con gli errori (registri, riavvii, ripristini, ecc.).

Oltre a questi due tipi di gestione degli errori, tutto il resto del codice nel mezzo dovrebbe essere libero e privo di codice try / catch e oggetti di errore. In questo modo, funziona semplicemente e come previsto, indipendentemente da dove lo usi o da cosa ci fai.

Paul.

Potrei essere un po 'in ritardo con la risposta, ma la gestione degli errori è qualcosa che possiamo sempre cambiare ed evolvere nel tempo. Se vuoi leggere qualcosa in più su questo argomento, ho scritto un post nel mio nuovo blog a riguardo. http://taoofdevelopment.wordpress.com

Buona codifica.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top