2012-01-19 16 views
6

Ho cercato, ma non sono riuscito a trovare una risposta definitiva per alcune delle mie domande sulle eccezioni, in particolare per quanto riguarda le best practice C#.Errori di mixaggio ed eccezioni in C#

Forse sarò sempre confuso sul modo migliore per utilizzare le eccezioni, ho trovato questo articolo che dice in pratica 'usa sempre le eccezioni, non usare mai codici di errore o proprietà' http://www.eggheadcafe.com/articles/20060612.asp. Lo comprerò sicuramente, ma ecco il mio dilemma:

Ho una funzione 'chiamante' che chiama 'callee'. 'callee' esegue alcune cose diverse, ognuna delle quali potrebbe generare lo stesso tipo di eccezione. Come faccio a ritrasmettere informazioni significative a 'chiamante' su cosa stava facendo 'callee' al momento dell'eccezione?

ho potuto lanciare una nuova eccezione come qui di seguito, ma io sono preoccupato io rovinare lo stacktrace che è male:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    throw new SomeException("Login failed", ex); 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    throw new SomeException("Sending file failed", ex): 
} 

Grazie, Mark

+2

Le eccezioni riempiono automaticamente lo stack di chiamate. Non devi ricrearli. –

+0

@CodyGray Il motivo del rilancio è di aggiungere informazioni in modo che il chiamante possa distinguerle. Se remoto il try/catch dal callee, il chiamante non saprà quale operazione di più operazioni ha causato l'eccezione. – MStodd

+1

Non capisco perché il destinatario non abbia lanciato un'eccezione contenente informazioni su ciò che stava facendo. Il chiamante non dovrebbe sapere cosa sta facendo il callee comunque! –

risposta

3

In scenari come questo di solito iniettare un registratore e registrare l'errore specifico e rigenerare l'eccezione originale:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    logger.LogError("Login failed"); 
    throw; 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    logger.LogError("Sending file failed"); 
    throw; 
} 

Il throw; non mancherà di tenere la pila originale traccia vivo (al contrario di throw ex; che creerà una nuova stacktrace).

Modifica

A seconda di ciò che il codice vero e sta facendo si potrebbe dare un senso a rompere in realtà callee in parecchi chiamate (Login(), SendFile(), ecc), in modo da caller sta eseguendo le singole fasi invece di chiamare un grande metodo che sta facendo tutto. Poi il chiamante saprà dove non è riuscito e può agire su di essa (forse chiedere all'utente per le diverse credenziali di accesso)

+0

+1 questa è una buona idea se devi lanciare un'eccezione e in passato ho fatto molto di questo quando si tratta di file io eccezioni. – JonH

+0

Questo è in realtà quello che stavo facendo.Potrei andare sulla strada di far sì che il callee non gestisca NESSUNA eccezione, e chiedi al chiamante di provare/catturarli e di ottenere ulteriori informazioni dal registro, se necessario. – MStodd

+0

@MStodd: Potrebbe essere sensato interrompere il problema se è importante che il chiamante sappia in quale fase si è verificato il problema. – ChrisWue

5

Il chiamante non ha bisogno di sapere cosa stava facendo il callee al momento dell'eccezione. Non dovrebbe sapere nulla su come il callee è implementato.

+5

Come tutte le affermazioni generali, anche questo è fuorviante (oh l'ironia). Se fosse vero, non ci sarebbe un bazillion diversi tipi di eccezione in .NET. – Jon

+0

In realtà non ci sono tipi di eccezioni di bazillion. Inoltre, questi tipi non indicano "cosa stava facendo il fattorino"; indicano il risultato del callee che lo fa, che è abbastanza diverso. –

+0

Mi permetto di cliccare [qui] (http://msdn.microsoft.com/en-us/library/system.exception.aspx#inheritanceContinued) (e considera che quelli sono solo i figli immediati di 'System.Exception' - il divertimento continua ad es. [qui] (http://msdn.microsoft.com/en-us/library/system.systemexception.aspx#inheritanceContinued)). Per quanto riguarda la distinzione tra azione e risultato, IMHO è una falsa pista; nessuno lancia eccezioni a meno che * non * sia un risultato da riportare (uno indesiderato). – Jon

7

Non esiste un'unica best practice per coprire tutti gli scenari in .Net. Chiunque dice "usa sempre le eccezioni" o "usa sempre i codici di errore" è semplicemente sbagliato. I programmi scritti in .Net sono ampi e complessi da generalizzare in una regola dura e veloce come quella.

Ci sono molti casi in cui le eccezioni semplicemente non sono appropriate. Ad esempio, se ho un ciclo molto stretto che cerca valori in un Dictionary<TKey, TValue>, preferirei assolutamente TryGetValue su un indicizzatore di lancio. Eppure in altri casi preferirei il lancio se i dati forniti a me fossero già stati contratti sulla mappa.

Il modo migliore per affrontare questa decisione è caso per caso. In generale, utilizzo Eccezioni solo se la situazione è davvero eccezionale. Uno essenzialmente che non poteva essere previsto dal chiamante o dai risultati del chiamante che violava esplicitamente un contratto. Esempi

  • non possono essere previsti: file non trovato
  • contratto Violazione: Passare un indice negativo in un indicizzatore
2

'callee' esegue un paio di cose diverse, ognuna delle quali potrebbe lanciare lo stesso tipo di eccezione.

Credo che questo sia il vero colpevole: callee dovrebbe essere gettando o un diverso tipo di eccezione in base a tipo di guasto è avvenuto, o le eccezioni dello stesso tipo dovrebbe trasportare abbastanza informazioni aggiuntive per far la figura chiamante fuori come gestire questa particolare eccezione. Naturalmente se il chiamante non è destinato a gestire l'eccezione affatto, non dovrebbe essere nel business di prenderlo in primo luogo.

0

Se veramente avere uno scenario in cui un chiamante ha bisogno per gestire le eccezioni in modo diverso, quindi la strategia tipica è per "catturare e avvolgere" l'eccezione di fondo nel callee.

È possibile

  • avvolgere tutti catturati sottostante eccezioni in un unico tipo di eccezione personalizzato, con un codice di stato che indica quale eccezione sottostante è stato gettato. Questo è comune quando si utilizza il modello di progettazione del modello di provider. Ad esempio, Membership.CreateUser può generare uno MembershipCreateUserException che ha un codice di stato per distinguere i motivi comuni dell'errore. Si prevede che i responsabili dell'implementazione seguano questo schema in modo tale che la gestione delle eccezioni del consumatore sia indipendente dall'implementazione del provider.

  • o avvolgere ogni tipo di eccezione sottostante in un'eccezione personalizzata separata. Ciò potrebbe essere appropriato se si prevede che alcuni chiamanti potrebbero voler gestire una delle eccezioni (ad esempio, inviare file non riuscito) ma non altri (ad esempio, il log in non è riuscito). Utilizzando diversi tipi di eccezioni, il chiamante deve solo catturare quelli che sono di interesse.

2

Perché la cura caller metodo di ciò callee stava facendo? Ci vorrà un'azione diversa se il problema si è verificato durante l'invio del file?

try 
{ 
    callee(..); 
} 
catch (SomeException e) 
{ 
    if (e.Message == "Sending file failed") 
    { 
     // what would you do here? 
    } 
} 

Come regola generale, eccezioni dovrebbero essere eccezionale. Il tuo flusso logico tipico non dovrebbe richiedere che vengano lanciati e catturati. Quando vengono lanciati, è di solito dovuto a un errore nel codice *. Quindi lo scopo principale di un eccezione è:

  1. per fermare il flusso della logica prima che il bug provoca più danni, e
  2. per fornire informazioni sullo stato del sistema quando si è verificato l'errore, in modo da poter individuare la fonte del bug.

Con questo in mente, si dovrebbe in genere solo prendere eccezioni se:

  1. si prevede di avvolgere l'eccezione con dati aggiuntivi (come nel tuo esempio) e lanciare una nuova eccezione, o
  2. tu sai che non ci saranno ulteriori effetti negativi dal continuare con il tuo flusso logico: ti rendi conto che la cosa che hai tentato di fare fallisce, e indipendentemente da come ha fallito, sei in pace con quello. In questi casi, dovresti sempre cogliere l'opportunità per registrare l'errore.

Se c'è un caso abbastanza comune di errore (ad esempio, le credenziali di accesso errate fornite dall'utente), questo non deve essere gestito come eccezione. Piuttosto, dovresti strutturare la tua firma del metodo callee in modo che il risultato restituito fornisca tutti i dettagli che lo caller ha bisogno di sapere su cosa è andato storto.

* L'eccezione notevole si verifica quando accade qualcosa di veramente imprevedibile, come se qualcuno scollegasse il cavo di rete dal computer nel mezzo della transazione.

1

La classe Exception fornisce un dizionario per l'aggiunta di dati aggiuntivi a un'eccezione, accessibile tramite la proprietà Exception.Data.

Se tutto ciò che si vuole fare è inoltrare ulteriori bit di informazioni al chiamante, non c'è motivo di avvolgere l'eccezione originale e lanciarne una nuova. Invece puoi semplicemente fare:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    ex.Data.Add("action", "Login"); 
    throw; 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    ex.Data.Add("action", "Sending of file"); 
    throw; 
} 

... 

// somewhere further up the call stack 
catch(SomeException ex) 
{ 
    var action = ex.Data["action"] ?? "Unknown action"; 
    Console.WriteLine("{0} failed.", action); // ... or whatever. 
} 

In questo modo sarai in grado di preservare lo stacktrace originale mentre puoi ancora presentare informazioni extra all'utente.