Come scrivere un buon messaggio di eccezione


101

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 ne esiste già una, per il motivo generale (ad es PropertyNotFoundException. Il perché ), quindi quando lo lancio il messaggio indica cosa è andato storto (ad es. "Impossibile trovare la proprietà 'IDontExist' sul nodo 1234 "- il cosa ). Il dove è nel StackTrace. Il quando può finire nel registro (se applicabile). Il modo in cui lo sviluppatore deve risolvere (e correggere)

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


4
Questi sono per i file di registro o da presentare all'utente?
Jon Hopkins,

5
Solo per debug. Potrebbero finire in un registro. Non verrebbero presentati all'utente. Non sono un fan della presentazione di messaggi di eccezione all'utente.
Colin Mackay,

Risposte:


72

Indirizzerò maggiormente 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 'abc@xyz.com' 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 'abc@xyz.com' 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 le istruzioni "se" devono essere eseguite nel codice, perché coinvolgere l'utente in qualcosa che può essere automatizzato? Le probabilità sono che è dal profondo nel codice e qualcuno ha fatto la cosa pigro e gettato IOException da qualsiasi numero di metodi e catturato i potenziali errori da tutti loro in un blocco di codice chiamante che non si può descrivere adeguatamente cosa è andato storto, quello che la specificacontesto è 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.


1
+1 Questo è un buon sistema. Che è più importante: essere sicuri che le informazioni vengano trasmesse o usare parole brevi?
Michael K,

3
+1 perché mi piace la soluzione di inclusione, contesto, problema, soluzione
WebDev,

1
Questa tecnica è molto utile. Lo userò sicuramente.
Kid Diamond

Il contesto è dati non necessari. È già presente nella traccia dello stack. Avere una soluzione è preferibile ma non sempre possibile / utile. La maggior parte dei problemi è il tipo di arresto grazioso dell'applicazione o ignora le operazioni in sospeso e torna al ciclo di esecuzione principale dell'applicazione sperando che la prossima volta che ci riuscirai ... Il nome della classe di eccezione dovrebbe essere tale che la soluzione diventerà ovvia, perché FileNotFoundo ConnectExceptionsai cosa fare))
gavenkoa,

2
@ThomasFlinkow Nel tuo esempio le tracce avranno init (), execute () e cleanup () nella traccia dello stack. Con un buon schema di denominazione in libreria e API pulite / comprensibili non hai bisogno di spiegazioni di stringhe. E fallisci velocemente, non portare lo stato rotto su tutto il sistema. Le tracce e i record di registrazione con ID univoco possono spiegare il flusso / lo stato dell'applicazione.
gavenkoa,

23

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 IndexOutOfRangeExceptiondovrebbe contenere il valore dell'indice che è stato trovato non valido, così come i valori superiore e inferiore che erano validi al momento del lancio dell'eccezione. In questo modo, usando la riflessione puoi costruire automaticamente un messaggio che legge in questo modo: IndexOutOfRangeException: index = -1; min=0; max=5e questo, insieme alla traccia dello stack, dovrebbero essere tutte le informazioni oggettive di cui hai bisogno 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 NodePropertyNotFoundExceptionclasse conterrebbe il nome della proprietà che non è stata trovata e un riferimento al nodo che non contiene la proprietà. Questo è importante: 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 throwun'istruzione e continuare a scrivere il nostro codice invece di dover interrompere ciò che stiamo facendo per creare una nuova classe di eccezione in modo che possiamo buttalo lì. Per questi casi, ho una GenericExceptionclasse che in effetti accetta un messaggio di stringa come parametro di costruzione-tempo, ma il costruttore di questa classe di eccezione è adornato con un grande grande FIXME XXX TODOcommento viola brillante che afferma che ogni singola istanza di questa classe deve essere sostituito con un'istanza di qualche classe di eccezione più specializzata prima che il sistema software venga rilasciato, preferibilmente prima che il codice venga impegnato.


8
Se sei in una lingua che non ha un GC, come C ++, dovresti stare molto attento a mettere riferimenti a dati arbitrari in eccezioni che invii allo stack. È probabile che qualunque cosa tu faccia riferimento sia stata distrutta dal momento in cui l'eccezione viene rilevata.
Sebastian Redl,

4
@SebastianRedl True. E lo stesso potrebbe valere per C # e Java se l' nodeoggetto è protetto da una clausola use-disposable (in C #) o try-with-resources (in Java): l'oggetto archiviato con l'eccezione verrebbe eliminato / chiuso, rendendolo illegale per accedervi al fine di ricavarne informazioni utili nel luogo in cui viene gestita l'eccezione. Suppongo che in questi casi una sorta di sommario dell'oggetto debba essere archiviato all'interno dell'eccezione, invece dell'oggetto stesso. Non riesco a pensare a un modo infallibile per gestirlo in modo generico in tutti i casi.
Mike Nakis,

13

Come regola generale, un'eccezione dovrebbe aiutare gli sviluppatori a individuare la causa fornendo informazioni utili (valori previsti, 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.


+1: i valori previsti rispetto ai valori effettivi sono estremamente utili. Nell'esempio fornito nella domanda, non dovresti semplicemente dire che un metodo non è riuscito, ma perché non è riuscito (sostanzialmente, il comando esatto che ha fallito e le circostanze che hanno causato il fallimento).
Felix Dombek,

4

In .NET non lo fai 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 quello catch(Exception ex) { ... }che generalmente non è una buona cosa! :-).


2

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 ". Ciò è 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 elencato o, per .NET, aggiunto alla raccolta dati delle eccezioni registrate (vedi @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.


0

Quali sono eccezioni per ?

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

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

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

ad es. "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.
L' Analisi dello stack dirà la Developer in cui il codice è rotto; il messaggio dovrebbe, di nuovo, indicare cosa è andato storto.

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

Il Tipo di Eccezione deciderà quale gestore di eccezioni lo vedrà e le proprietà definite sull'oggetto Exception consentiranno al gestore di gestirlo.

Il messaggio dell'eccezione è totalmente irrilevante .


-3

Non creare nuovi tipi se puoi aiutarlo. Possono causare ulteriore confusione, complessità e portare a più codice da mantenere. Potrebbero far estendere il tuo codice. La progettazione di una gerarchia di eccezioni richiede molte considerazioni e test. Non è un ripensamento. È spesso preferibile utilizzare la gerarchia di eccezioni del linguaggio integrata.

Il contenuto del messaggio di eccezione dipende dal destinatario del messaggio, quindi devi metterti nei panni di quella persona.

Un tecnico dell'assistenza dovrà essere in grado di identificare l'origine dell'errore il più rapidamente possibile. Includi una breve stringa descrittiva più tutti i dati che possono aiutarti a risolvere il problema. Includi sempre la traccia dello stack - se puoi - questa sarà l'unica vera fonte di informazioni.

La presentazione degli errori agli utenti generali del sistema dipende dal tipo di errore: se l'utente può risolvere il problema, fornendo ad esempio input diversi, è necessario un messaggio descrittivo conciso. Se l'utente non è in grado di risolvere il problema, è consigliabile dichiarare che si è verificato un errore e registrare / inviare un errore al supporto (utilizzando le linee guida sopra).

Inoltre, non vomitare un grosso "HALT ERROR!" icona. È un errore, non è la fine del mondo.

Quindi, in sintesi: pensa agli attori e usa i casi per il tuo sistema. Mettiti nei panni di quegli utenti. Sii utile. Sii gentile. Pensaci in anticipo nella progettazione del tuo sistema. Dal punto di vista dei tuoi utenti - questi casi di eccezione e il modo in cui il sistema li gestisce, sono importanti tanto quanto i normali casi nel tuo sistema.


10
Non sono d'accordo. Esistono molte buone ragioni per implementare le tue eccezioni quando l'API della lingua non copre le tue esatte esigenze. Uno dei motivi è che in un metodo in cui più cose potrebbero non riuscire, è possibile scrivere diverse clausole di cattura per diversi tipi di eccezioni, in cui è possibile reagire al problema esatto. Un altro è che puoi separare più livelli di eccezioni che rappresentano diversi livelli di astrazione, in cui il livello esatto a cui appartiene un'eccezione può essere codificato nel suo tipo. Basta usare "Exception" o "IllegalStateException" e una stringa di messaggi non aiuta molto lì.
Felix Dombek,

1
Sono anche in disaccordo. I tipi di eccezione dovrebbero avere senso per il codice chiamante che lo utilizzerà. Ad esempio, se sto chiamando un framework e questo provoca internamente una FileDoesNotExistException, ciò potrebbe non avere alcun senso per me come chiamante del framework. Invece potrebbe essere meglio creare un'eccezione personalizzata e passare l'eccezione generata come eccezione interna.
bytedev,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.