c # gestione delle eccezioni, esempio pratico. Come lo faresti?

Sto cercando di migliorare la gestione delle eccezioni, ma sento che il mio codice diventa molto brutto, illeggibile e disordinato quando faccio del mio meglio per catturarli. Mi piacerebbe vedere come le altre persone si avvicinano a questo dando un esempio pratico e confrontando le soluzioni.

Il mio metodo di esempio scarica i dati da un URL e tenta di serializzarli in un determinato tipo, quindi restituisce un’istanza popolata con i dati.

Innanzitutto, senza alcuna gestione delle eccezioni:

private static T LoadAndSerialize(string url) { var uri = new Uri(url); var request = WebRequest.Create(uri); var response = request.GetResponse(); var stream = response.GetResponseStream(); var result = Activator.CreateInstance(); var serializer = new DataContractJsonSerializer(result.GetType()); return (T)serializer.ReadObject(stream); } 

Mi sento come se il metodo fosse abbastanza leggibile come questo. So che ci sono alcuni passaggi non necessari nel metodo (come WebRequest.Create () può prendere una stringa, e potrei concatenare i metodi senza dare loro variabili), ma li lascerò in questo modo per confrontarli meglio con la versione eccezionalmente la manipolazione.

Questo è un primo tentativo di gestire tutto ciò che potrebbe andare storto:

  private static T LoadAndSerialize(string url) { Uri uri; WebRequest request; WebResponse response; Stream stream; T instance; DataContractJsonSerializer serializer; try { uri = new Uri(url); } catch (Exception e) { throw new Exception("LoadAndSerialize : Parameter 'url' is malformsd or missing.", e); } try { request = WebRequest.Create(uri); } catch (Exception e) { throw new Exception("LoadAndSerialize : Unable to create WebRequest.", e); } try { response = request.GetResponse(); } catch (Exception e) { throw new Exception(string.Format("LoadAndSerialize : Error while getting response from host '{0}'.", uri.Host), e); } if (response == null) throw new Exception(string.Format("LoadAndSerialize : No response from host '{0}'.", uri.Host)); try { stream = response.GetResponseStream(); } catch (Exception e) { throw new Exception("LoadAndSerialize : Unable to get stream from response.", e); } if (stream == null) throw new Exception("LoadAndSerialize : Unable to get a stream from response."); try { instance = Activator.CreateInstance(); } catch (Exception e) { throw new Exception(string.Format("LoadAndSerialize : Unable to create and instance of '{0}' (no parameterless constructor?).", typeof(T).Name), e); } try { serializer = new DataContractJsonSerializer(instance.GetType()); } catch (Exception e) { throw new Exception(string.Format("LoadAndSerialize : Unable to create serializer for '{0}' (databinding issues?).", typeof(T).Name), e); } try { instance = (T)serializer.ReadObject(stream); } catch (Exception e) { throw new Exception(string.Format("LoadAndSerialize : Unable to serialize stream into '{0}'.", typeof(T).Name), e); } return instance; } 

Il problema qui è che mentre tutto ciò che potrebbe andare storto sarà catturato e dato un’eccezione un po ‘significativa, si tratta di un clutter-fest di proporzioni significative.

Quindi, cosa succede se cancello la cattura invece. Il mio prossimo tentativo è questo:

  private static T LoadAndSerialize(string url) { try { var uri = new Uri(url); var request = WebRequest.Create(uri); var response = request.GetResponse(); var stream = response.GetResponseStream(); var serializer = new DataContractJsonSerializer(typeof(T)); return (T)serializer.ReadObject(stream); } catch (ArgumentNullException e) { throw new Exception("LoadAndSerialize : Parameter 'url' cannot be null.", e); } catch (UriFormatException e) { throw new Exception("LoadAndSerialize : Parameter 'url' is malformsd.", e); } catch (NotSupportedException e) { throw new Exception("LoadAndSerialize : Unable to create WebRequest or get response stream, operation not supported.", e); } catch (System.Security.SecurityException e) { throw new Exception("LoadAndSerialize : Unable to create WebRequest, operation was prohibited.", e); } catch (NotImplementedException e) { throw new Exception("LoadAndSerialize : Unable to get response from WebRequest, method not implemented?!.", e); } catch(NullReferenceException e) { throw new Exception("LoadAndSerialize : Response or stream was empty.", e); } } 

Mentre è certamente più facile per gli occhi, sto inclinando pesantemente l’intellisense qui per fornire tutte le eccezioni che possono essere gettate da un metodo o una class. Non sono sicuro che questa documentazione sia accurata al 100% e sarebbe ancora più scettico se alcuni metodi provenissero da un assembly al di fuori del framework .net. Ad esempio, DataContractJsonSerializer non mostra eccezioni su intellisense. Questo significa che il costruttore non fallirà mai? Posso esserne sicuro?

Altri problemi con questo sono che alcuni metodi lanciano la stessa eccezione, il che rende l’errore più difficile da descrivere (questo o questo o questo è andato storto) e quindi è meno utile per l’utente / debugger.

Una terza opzione sarebbe ignorare tutte le eccezioni a parte quelle che mi permetterebbero di intraprendere un’azione come riprovare la connessione. Se l’url è nullo, allora l’url è nullo, l’unico vantaggio derivante dalla cattura è un messaggio di errore un po ‘più dettagliato.

Mi piacerebbe vedere i tuoi pensieri e / o implementazioni!

Regola uno della gestione delle eccezioni: non rilevare le eccezioni che non sai come gestire.

Catturare le eccezioni solo per fornire dei buoni messaggi di errore è discutibile. Il tipo di eccezione e il messaggio contengono già informazioni sufficienti per uno sviluppatore : i messaggi che hai fornito non aggiungono alcun valore.

DataContractJsonSerializer non mostra eccezioni su intellisense. Questo significa che il costruttore non fallirà mai? Posso esserne sicuro?

No, non puoi esserne sicuro. C # e .NET in generale non sono come Java dove devi dichiarare quali eccezioni possono essere lanciate.

Una terza opzione sarebbe ignorare tutte le eccezioni a parte quelle che mi permetterebbero di intraprendere un’azione come riprovare la connessione.

Questa è davvero l’opzione migliore.

È inoltre ansible aggiungere un gestore di eccezioni generali nella parte superiore dell’applicazione che acquisirà tutte le eccezioni non gestite e registrarle.

Per prima cosa, leggi il mio articolo sulla gestione delle eccezioni:

http://ericlippert.com/2008/09/10/vexing-exceptions/

Il mio consiglio è: devi gestire le “eccezioni fastidiose” e le “eccezioni esogene” che possono essere generate dal tuo codice. Le eccezioni Vexing sono eccezioni “non eccezionali” e quindi è necessario gestirle. Eccezioni esogene possono verificarsi a causa di considerazioni che sfuggono al tuo controllo e quindi devi gestirle.

Non devi gestire le eccezioni fatali e dalla testa ossuta. Le eccezioni boneheaded che non devi gestire perché non farai mai nulla che le faccia gettare . Se vengono lanciati, si verifica un errore e la soluzione risolve il problema . Non gestire l’eccezione; questo è hide il bug E non puoi gestire in modo significativo le eccezioni fatali perché sono fatali . Il processo sta per scendere. Potresti considerare di registrare l’eccezione fatale, ma tieni presente che il sottosistema di registrazione potrebbe essere la cosa che ha innescato l’eccezione fatale in primo luogo.

In breve: gestisci solo le eccezioni che possono eventualmente accadere che sai come gestire. Se non sai come gestirlo, lascialo al tuo chiamante; il chiamante potrebbe sapere meglio di te.

Nel tuo caso particolare: non gestisci alcuna eccezione in questo metodo. Lascia che il chiamante gestisca le eccezioni. Se il chiamante ti passa un url che non può essere risolto, mandalo in crash . Se l’url cattivo è un bug, il chiamante ha un bug da correggere e gli stai facendo un favore portandolo alla sua attenzione. Se l’URL non è un bug, ad esempio perché la connessione Internet dell’utente è incasinata, allora il chiamante deve scoprire perché il recupero non è riuscito interrogando la vera eccezione. Il chiamante potrebbe sapere come recuperare, quindi aiutali.

Prima di tutto, dovresti, per tutti gli scopi pratici, non lanciare mai il tipo Exception . Butta sempre qualcosa di più specifico. Anche ApplicationException sarebbe migliore, marginalmente. In secondo luogo, utilizzare istruzioni di cattura separate per le diverse operazioni quando, e solo quando, il chiamante avrà motivo di preoccuparsi dell’operazione non riuscita. Se una InvalidOperationException che si verifica in un punto del programma implicherà qualcosa di diverso sullo stato del tuo object rispetto a quello che si verifica in un altro momento, e se il tuo interlocutore si preoccuperà della distinzione, allora dovresti concludere la prima parte di il tuo programma in un blocco ‘try / catch’ che avvolgerà InvalidOperationException in qualche altra (forse personalizzata) class di eccezioni.

La nozione di “solo eccezioni di cattura che sai come gestire” è buona in teoria, ma sfortunatamente molti tipi di eccezioni sono così vaghi sullo stato degli oggetti sottostanti che è quasi imansible sapere se è ansible “gestire” un’eccezione o meno. Ad esempio, si potrebbe avere una routine TryLoadDocument che deve utilizzare internamente metodi che possono generare eccezioni se non è ansible caricare parti del documento. Nel 99% dei casi in cui si verifica tale eccezione, il modo corretto per “gestire” tale eccezione sarà semplicemente abbandonare il documento parzialmente caricato e tornare senza esporlo al chiamante. Sfortunatamente, è molto difficile identificare l’1% dei casi in cui questo è insufficiente. Dovresti sforzarti di introdurre diverse eccezioni nei casi in cui la tua routine ha fallito senza fare nulla, rispetto a quelli in cui potrebbe aver avuto altri effetti collaterali imprevedibili; sfortunatamente, probabilmente sarai bloccato a indovinare l’interpretazione della maggior parte delle eccezioni dalle routine che chiami.

Eccezione e.message dovrebbe avere più di un numero sufficiente di dati del messaggio di errore per poter eseguire correttamente il debug. Quando eseguo la gestione delle eccezioni, in genere mi limito a registrarlo con alcune brevi informazioni su dove si è verificato e sull’eccezione effettiva.

Non lo dividerei in quel modo, questo fa solo casino. Le eccezioni sono per lo più per VOI. Idealmente se l’utente sta causando eccezioni, le avresti catturato prima.

Non consiglierei di lanciare eccezioni con nomi diversi a meno che non siano realmente delle eccezioni (ad esempio a volte in alcune chiamate API la risposta diventa nullo. Di solito la controllerei e genererei un’eccezione utile per me).

Guarda l’ Unity Intercettazione . All’interno di questo framework, puoi usare qualcosa chiamato ICallHandler , che ti consente di intercettare le chiamate e fare tutto ciò che ti serve / vuoi fare con la chiamata intercettata.

Per esempio:

 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { var methodReturn = getNext().Invoke(input, getNext); if (methodReturn.Exception != null) { // exception was encountered... var interceptedException = methodReturn.Exception // ... do whatever you need to do, for instance: if (interceptedException is ArgumentNullException) { // ... and so on... } } } 

Esistono altri quadri di intercettazione, ovviamente.

Prendi in considerazione di suddividere il metodo in metodi più piccoli, in modo che la gestione degli errori possa essere eseguita per gli errori correlati.

Esistono più cose semi-non correlate nello stesso metodo, poiché la gestione degli errori dei risultati deve essere più o meno per riga di codice.

Cioè per il tuo caso puoi dividere il metodo in: CreateRequest (gestisci errori di argomenti non validi qui), GetResponse (gestisce gli errori di rete), ParseRespone (gestisce gli errori di contenuto).

Non sono d’accordo con @oded quando dice:

“Regola uno di gestione delle eccezioni – non rilevare le eccezioni che non sai come gestire”.

Può andare bene per scopi accademici, ma nella vita reale i tuoi clienti non vogliono che gli errori non informativi compaiano sui loro volti.

Penso che puoi e dovresti prendere delle eccezioni e generare qualche eccezione informativa per l’utente. Quando un simpatico errore, ben informato, viene mostrato all’utente, può avere più informazioni su cosa dovrebbe fare per risolvere il problema.

Inoltre, l’individuazione di tutte le eccezioni può essere utile quando si decide di registrare gli errori o, ancora meglio, di inviarli automaticamente.

Tutti i miei progetti hanno una class Error e prendo sempre tutte le eccezioni che la utilizzano. Anche se non faccio molto in questa class, è lì e può essere usato per un sacco di cose.