it-swarm.it

Come scrivere un buon messaggio di eccezione

Attualmente sto facendo una revisione del codice e una delle cose che noto è il numero di eccezioni in cui il messaggio di eccezione sembra reiterare dove si è verificata l'eccezione. per esempio.

throw new Exception("BulletListControl: CreateChildControls failed.");

Tutti e tre gli elementi di questo messaggio posso capire dal resto dell'eccezione. Conosco la classe e il metodo dalla traccia dello stack e so che non è riuscito (perché ho un'eccezione).

Mi ha fatto pensare a quale messaggio ho inserito nei messaggi di eccezione. Per prima cosa creo una classe di eccezione, se non esiste già, per il motivo generale (ad esempio PropertyNotFoundException - il why), e poi quando lo lancio il messaggio indica cosa è andato storto (ad es. "Impossibile trovare la proprietà 'IDontExist' su Node 1234" - il cosa). Il punto in cui si trova il StackTrace. Il - quando potrebbe finire nel registro (se applicabile). how è per lo sviluppatore (e risolvere)

Hai altri suggerimenti per lanciare eccezioni? In particolare per quanto riguarda la creazione di nuovi tipi e il messaggio di eccezione.

102
Colin Mackay

Dirò di più la mia risposta a ciò che viene dopo un'eccezione: a cosa serve e come dovrebbe comportarsi il software, cosa dovrebbero fare gli utenti con l'eccezione? Una grande tecnica che ho riscontrato all'inizio della mia carriera è stata quella di segnalare sempre problemi ed errori in 3 parti: contesto, problema e soluzione. L'uso di questa dicipline modifica enormemente la gestione degli errori e rende il software notevolmente migliore per gli operatori.

Ecco alcuni esempi.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

In questo caso, l'operatore sa esattamente cosa fare e su quale file deve essere interessato. Sanno anche che le modifiche al pool di connessioni non hanno avuto e dovrebbero essere ripetute.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Scrivo sistemi lato server e i miei operatori sono generalmente esperti di tecnologia di prima linea. Scriverei i messaggi in modo diverso per i software desktop che hanno un pubblico diverso ma includono le stesse informazioni.

Diverse cose meravigliose accadono se si usa questa tecnica. Lo sviluppatore di software è spesso nella posizione migliore per sapere come risolvere i problemi nel proprio codice, quindi codificare le soluzioni in questo modo mentre si scrive il codice è di enorme beneficio per gli utenti finali che sono in svantaggio nel trovare soluzioni poiché spesso mancano informazioni su cosa stava facendo esattamente il software. Chiunque abbia mai letto un messaggio di errore Oracle saprà cosa intendo.

La seconda cosa meravigliosa che viene in mente è quando ti ritrovi a provare a descrivere una soluzione nella tua eccezione e stai scrivendo "Controlla X e se A allora B altro C". Questo è un segno molto chiaro ed evidente che la tua eccezione è stata controllata nel posto sbagliato. Il programmatore ha la capacità di confrontare le cose nel codice, quindi "if" le istruzioni dovrebbero essere eseguite nel codice, perché coinvolgere l'utente in qualcosa che può essere automatizzato? È probabile che sia più profondo nel codice e qualcuno ha fatto la cosa pigra e ha generato IOException da un numero qualsiasi di metodi e ha rilevato potenziali errori da tutti loro in un blocco di codice chiamante che non può descrivere adeguatamente cosa è andato storto, cosa il contesto specifico e come risolverlo. Questo ti incoraggia a scrivere errori di grana più fine, a catturarli e gestirli nel posto giusto nel tuo codice in modo da poter articolare correttamente i passi che l'operatore dovrebbe prendere.

In una società avevamo operatori di prim'ordine che conoscevano molto bene il software e mantenevano il loro "libro di istruzioni" che aumentava la segnalazione degli errori e suggeriva soluzioni. Per riconoscerlo, il software ha iniziato a includere collegamenti wiki al runbook in eccezioni, in modo che fosse disponibile una spiegazione di base, nonché collegamenti a discussioni e osservazioni più avanzate da parte degli operatori nel tempo.

Se hai avuto la dicipline per provare questa tecnica, diventa molto più ovvio come dovresti nominare le tue eccezioni nel codice quando crei la tua. NonRecoverableConfigurationReadFailedException diventa un po 'una scorciatoia per ciò che stai per descrivere in modo più completo all'operatore. Mi piace essere prolisso e penso che sarà più facile da interpretare per il prossimo sviluppatore che tocca il mio codice.

72
Sir Wobin

In questa domanda più recente ho sottolineato che le eccezioni non dovrebbero contenere affatto un messaggio. Secondo me, il fatto che lo facciano è un grande equivoco. Quello che sto proponendo è quello

Il "messaggio" dell'eccezione è il nome della classe (completo) dell'eccezione.

Un'eccezione dovrebbe contenere all'interno dei propri membri variabili il maggior numero possibile di dettagli su ciò che è accaduto; ad esempio, un IndexOutOfRangeException dovrebbe contenere il valore dell'indice che è stato trovato non valido, nonché i valori superiore e inferiore validi al momento del lancio dell'eccezione. In questo modo, usando la riflessione puoi avere automaticamente un messaggio che legge così: IndexOutOfRangeException: index = -1; min=0; max=5 e questo, insieme alla traccia dello stack, dovrebbero essere tutte le informazioni oggettive necessarie per risolvere il problema. La formattazione in un bel messaggio come "indice -1 non era compreso tra 0 e 5" non aggiunge alcun valore.

Nel tuo esempio particolare, la classe NodePropertyNotFoundException conterrebbe il nome della proprietà non trovata e un riferimento al nodo che non conteneva la proprietà. Questo è importante: non dovrebbe non contenere il nome del nodo; dovrebbe contenere un riferimento al nodo effettivo. Nel tuo caso particolare questo potrebbe non essere necessario, ma è una questione di principio e un modo di pensare preferito: la preoccupazione principale quando si costruisce un'eccezione è che deve essere utilizzabile dal codice che potrebbe catturarlo. L'usabilità da parte dell'uomo è una preoccupazione importante, ma solo secondaria.

Questo si occupa della situazione molto frustrante a cui potresti aver assistito in un certo momento della tua carriera, in cui potresti aver riscontrato un'eccezione contenente informazioni vitali su ciò che è accaduto nel testo del messaggio, ma non all'interno delle sue variabili membro, quindi tu ho dovuto eseguire l'analisi delle stringhe del testo per capire cosa è successo, sperando che il testo del messaggio rimanga lo stesso nelle versioni future del livello sottostante e pregare che il testo del messaggio non sia in una lingua straniera quando il programma è correre in altri paesi.

Naturalmente, poiché il nome della classe dell'eccezione è il messaggio dell'eccezione, (e le variabili membro dell'eccezione sono i dettagli specifici), ciò significa che sono necessarie molte e diverse eccezioni per trasmettere tutti i diversi messaggi, e questo va bene.

Ora, a volte, mentre scriviamo il codice, ci imbattiamo in una situazione errata per la quale vogliamo solo codificare rapidamente un'istruzione throw e procedere a scrivere il nostro codice invece di dover interrompere ciò che stiamo facendo per creare un nuovo classe di eccezione in modo che possiamo lanciarla proprio lì. Per questi casi, ho una classe GenericException che in effetti accetta un messaggio di stringa come parametro di costruzione-tempo, ma il costruttore di questa classe di eccezione è ornato da un grande grande enorme viola brillante FIXME XXX TODO commento affermando che ogni singola istanza di questa classe deve essere sostituita con un'istanza di qualche classe di eccezione più specializzata prima che il sistema software venga rilasciato, preferibilmente prima che il codice venga impegnato.

23
Mike Nakis

Come regola generale, n'eccezione dovrebbe aiutare gli sviluppatori a individuare la causa fornendo informazioni utili (valori attesi, valore effettivo, possibili cause/soluzione, ecc.).

Nuovi tipi di eccezione dovrebbero essere creati quando nessuno dei tipi predefiniti ha senso. Un tipo specifico consente ad altri sviluppatori di catturare un'eccezione specifica e gestirla. Se lo sviluppatore sapesse come gestire la tua eccezione ma il tipo è Exception, non sarà in grado di gestirla correttamente.

13
mbillard

In .NET non devi mai throw new Exception("...") (come ha mostrato l'autore della domanda). L'eccezione è il tipo di eccezione radice e non dovrebbe mai essere lanciata direttamente. Invece, lancia uno dei tipi di eccezione .NET derivati ​​o crea la tua eccezione personalizzata che deriva dall'eccezione (o da un altro tipo di eccezione).

Perché non lanciare un'eccezione? Perché lanciare Eccezione non fa nulla per descrivere la tua eccezione e forza il tuo codice chiamante a scrivere codice come catch(Exception ex) { ... } che generalmente non è una buona cosa! :-).

4
bytedev

Le cose che si desidera cercare per "aggiungere" all'eccezione sono quegli elementi di dati che non sono inerenti all'eccezione o alla traccia dello stack. Se questi sono parte del "messaggio" o devono essere allegati quando si accede è una domanda interessante.

Come hai già notato, l'eccezione dice probabilmente cosa, lo stacktrace probabilmente ti dice dove ma il "perché" potrebbe essere più coinvolto (dovrebbe essere, si spera) che andare a scrutare una o due righe e dire " doh! Certo ". Questo è ancora più vero quando si registrano errori nel codice di produzione - troppo spesso sono stato morso da dati errati che si sono fatti strada in un sistema live che non esiste nei nostri sistemi di test. Qualcosa di così semplice come sapere qual è l'ID del record nel database che sta causando (o contribuendo) all'errore può farti risparmiare una notevole quantità di tempo.

Quindi ... O elencati o, per .NET, aggiunti alla raccolta dati delle eccezioni registrate (c.f. @Plip!):

  • Parametri (questo può diventare un po 'interessante: non è possibile aggiungere alla raccolta dati se non si serializza e talvolta un singolo parametro può essere sorprendentemente complesso)
  • I dati aggiuntivi restituiti da ADO.NET o Linq a SQL o simili (questo può anche diventare un po 'interessante!).
  • Qualsiasi altra cosa potrebbe non essere evidente.

Alcune cose, ovviamente, non saprai che ti serviranno fino a quando non le avrai nel tuo rapporto/registro di errore iniziale. Alcune cose che non ti rendi conto che puoi ottenere finché non scopri di averne bisogno.

2
Murph

Cosa sono le eccezioni per ?

(1) Dire all'utente che qualcosa è andato storto?

Questa dovrebbe essere l'ultima risorsa, perché il tuo codice dovrebbe intercedere e mostrare loro qualcosa di "più bello" di un'eccezione.

Il messaggio "errore" dovrebbe indicare chiaramente e in modo succinto cosa è andato storto e cosa, se non altro, l'utente può fare per recuperare dalla condizione di errore.

per esempio. "Per favore, non premere di nuovo questo pulsante"

(2) Raccontare uno sviluppatore quando è andato storto?

Questo è il genere di cose che accedi a un file per le analisi successive.
Stack Trace indicherà allo Sviluppatore dove il codice si è rotto; il messaggio dovrebbe, di nuovo, indicare cosa è andato storto.

(3) Dire a un gestore di eccezioni (ad es. Codice) che qualcosa è andato storto?

Tipo dell'eccezione deciderà quale gestore di eccezioni lo vedrà e il proprietà definito sull'oggetto Exception consentirà al gestore di gestirlo.

Il messaggio dell'eccezione è totalmente irrilevante .

0
Phill W.