Chi dovrebbe leggere Exception.Message if affatto?


27

Quando si progettano le eccezioni, devo scrivere messaggi che un utente o uno sviluppatore dovrebbero comprendere? Chi dovrebbe effettivamente essere il lettore di messaggi di eccezione?

Trovo che i messaggi di eccezione non siano affatto utili e faccio sempre fatica a scriverli. Per convenzione il tipo di eccezione dovrebbe già dirci perché qualcosa non ha funzionato e le proprietà personalizzate potrebbero aggiungere ancora più informazioni come nomi di file, indici, chiavi ecc. Quindi perché ripeterlo nel messaggio stesso? Un messaggio generato automaticamente potrebbe anche fare e tutto ciò che doveva contenere era il nome dell'eccezione con un elenco di proprietà aggiuntive. Questo sarebbe esattamente utile come un testo scritto a mano.

Non sarebbe meglio non scrivere affatto messaggi ma avere invece dei renderer di eccezioni speciali che si occupano di creare messaggi significativi forse in lingue diverse piuttosto che codificarli nel codice?


Mi è stato chiesto se una di queste domande fornisce una risposta alla mia domanda:

Li ho letti entrambi e non ero contento delle loro risposte. Parlano degli utenti in generale e si concentrano sul contenuto del messaggio stesso piuttosto che sul destinatario e si scopre che possono essercene almeno due: l'utente finale e lo sviluppatore. Non so mai con quale dovrei parlare quando scrivo messaggi di eccezione.

Penso anche che il famoso messaggio non abbia alcun valore reale in quanto ripete semplicemente il nome del tipo di eccezione in parole diverse, quindi perché preoccuparsi di scriverli affatto? Posso generarli perfettamente automaticamente.

Per me il messaggio di eccezione manca di una differenziazione dei suoi lettori. Un perfetto eccezione sarebbe necessario fornire almeno due versioni del messaggio: una per l'utente finale e uno per lo sviluppatore. Chiamarlo solo un messaggio è troppo generico. Un messaggio per sviluppatori dovrebbe quindi essere scritto in inglese, ma potrebbe essere necessario che il messaggio dell'utente finale sia tradotto in altre lingue. Non è possibile ottenere tutto ciò con un solo messaggio, quindi un'eccezione dovrebbe fornire un identificatore al messaggio dell'utente finale che, come ho appena detto, potrebbe essere disponibile in diverse lingue.

Quando leggo tutte le altre domande collegate ho l'impressione che un messaggio di eccezione sia effettivamente destinato a essere letto da un utente finale e non da uno sviluppatore ... un singolo messaggio è come avere una torta e mangiarla anche.


4
Ci sono altre due domande poste qui sui programmatori che vengono in mente. Non sono sicuro che siano duplicati o meno, ma penso che dovresti leggerli e forse affronteranno alcune o anche tutte le tue preoccupazioni: come scrivere un buon messaggio di eccezione e perché molti messaggi di eccezione non contengono dettagli utili ?
Thomas Owens

2
@ThomasOwens Li ho letti entrambi ma non si rivolgono al destinatario specifico di un messaggio di eccezione. Parlano di utenti in generale, ma chi è lui? È l'utente del software che ora ha idea della programmazione o degli sviluppatori che dovrebbe analizzare ciò che è andato storto? Non sono mai sicuro a chi dovrei rivolgermi quando scrivo i messaggi.
t3chb0t,

2
Penso che una buona risposta qui possa riferirsi a una o entrambe le domande, ma sono tutt'altro che duplicati.
Corse di leggerezza con Monica,

1
Perché mai questo è stato chiuso come duplicato? Anche se fosse un duplicato di quell'altra domanda (che non lo è), questa ha chiaramente una risposta migliore, dovrebbe essere l'altra contrassegnata come duplicata.
Ben Aaronson,

2
@MichaelT Non vedo ancora molta sovrapposizione lì a meno che tu non supponga che i messaggi di eccezione siano per gli utenti
Ben Aaronson,

Risposte:


46

Quei messaggi sono per altri sviluppatori

Questi messaggi dovrebbero essere letti dagli sviluppatori per aiutarli a eseguire il debug dell'applicazione. Questo può assumere due forme:

  • Debug attivo. In realtà stai eseguendo un debugger mentre scrivi il codice e cerchi di capire cosa succede. In questo contesto, un'utile eccezione ti guiderà semplificando la comprensione di ciò che non funziona o suggerendo eventualmente una soluzione alternativa (sebbene sia facoltativa).

  • Debug passivo. Il codice viene eseguito in produzione e non riesce. L'eccezione viene registrata, ma ottieni solo il messaggio e la traccia dello stack. In questo contesto, un utile messaggio di eccezione ti aiuterà a localizzare rapidamente il bug.

    Poiché tali messaggi vengono spesso registrati, significa anche che non dovresti includere lì informazioni sensibili (come chiavi private o password, anche se potrebbe essere utile eseguire il debug dell'app).

Ad esempio, il IOSecurityExceptiontipo di un'eccezione generata durante la scrittura di un file non è molto esplicito sul problema: è perché non abbiamo le autorizzazioni per accedere a un file? O forse possiamo leggerlo, ma non scrivere? O forse il file non esiste e non abbiamo i permessi per creare file lì? O forse è bloccato (si spera, il tipo di eccezione sarà diverso in questo caso, ma in pratica, i tipi a volte possono essere criptici). O forse Code Access Security ci impedisce di eseguire qualsiasi operazione di I / O?

Anziché:

IOSecurityException: il file è stato trovato ma l'autorizzazione a leggere il suo contenuto è stata negata.

è molto più esplicito. Qui, sappiamo immediatamente che le autorizzazioni sulla directory sono impostate correttamente (altrimenti, non saremo in grado di sapere che il file esiste), ma le autorizzazioni a livello di file sono problematiche.

Ciò significa anche che se non è possibile fornire ulteriori informazioni che non sono già nel tipo di eccezione, è possibile mantenere vuoto il messaggio. DivisionByZeroExceptionè un buon esempio in cui il messaggio è ridondante. D'altra parte, il fatto che la maggior parte delle lingue ti permetta di generare un'eccezione senza specificare il suo messaggio viene fatto per un motivo diverso: o perché il messaggio predefinito è già disponibile o perché verrà generato in seguito, se necessario (e la generazione di questo il messaggio è racchiuso nel tipo di eccezione, che ha perfettamente senso, parlando "OOPly" ).

Nota che per motivi tecnici (spesso prestazionali), alcuni messaggi finiscono per essere molto più criptici di quanto dovrebbero essere. .NET NullReferenceException:

Il riferimento non impostato su un'istanza di un oggetto.

è un eccellente esempio di messaggio non utile. Un messaggio utile sarebbe:

Riferimento oggetto productnon impostato su un'istanza di un oggetto quando viene chiamato product.Price.

Quei messaggi non sono per gli utenti finali!

Gli utenti finali non devono vedere i messaggi di eccezione. Mai. Sebbene alcuni sviluppatori finiscano per mostrare questi messaggi agli utenti, ciò porta a una scarsa esperienza utente e frustrazione. Messaggi come:

Il riferimento non impostato su un'istanza di un oggetto.

non significa assolutamente nulla per un utente finale e dovrebbe essere evitato a tutti i costi.

Lo scenario peggiore è avere un tentativo / cattura globale che genera l'eccezione per l'utente ed esce dall'app. Un'applicazione che ha a cuore i suoi utenti:

  • Gestisce le eccezioni in primo luogo. La maggior parte di essi può essere gestita senza disturbare l'utente. La rete non funziona? Perché non aspettare qualche secondo e riprovare?

  • Impedisce a un utente di condurre l'applicazione a un caso eccezionale. Se chiedi all'utente di inserire due numeri e dividere il primo per il secondo, perché dovresti consentire all'utente di inserire zero nel secondo caso per biasimarlo qualche secondo dopo? Che ne dici di evidenziare la casella di testo in rosso (con un utile suggerimento che dice che il numero dovrebbe essere diverso da zero) e disabilitare il pulsante di convalida fino a quando il campo rimane rosso?

  • Invita un utente a eseguire un'azione in un modulo che non è un errore. Non ci sono autorizzazioni sufficienti per accedere a un file? Perché non chiedere all'utente di concedere autorizzazioni amministrative o scegliere un altro file?

  • Se nient'altro funziona, mostra un errore utile e amichevole che è stato scritto appositamente per ridurre la frustrazione dell'utente, aiutare l'utente a capire cosa è andato storto e alla fine risolvere il problema, e anche aiutarlo a prevenire l'errore in futuro (se applicabile).

    Nella tua domanda, hai suggerito di avere due messaggi in un'eccezione: uno tecnico per gli sviluppatori e uno per gli utenti finali. Sebbene questo sia un suggerimento valido in alcuni casi minori, la maggior parte delle eccezioni sono prodotte a un livello in cui è impossibile produrre un messaggio significativo per gli utenti. Prendi DivisionByZeroExceptione immagina che non possiamo impedire che si verifichi l'eccezione e non possiamo gestirla da soli. Quando si verifica la divisione, il framework (dal momento che è il framework, e non il codice aziendale, che genera l'eccezione) sa quale sarà un messaggio utile per un utente? Assolutamente no:

    Si è verificata la divisione per zero. [OK]

    Invece, si può lasciare che generi l'eccezione e quindi catturarlo a un livello superiore in cui conoscevamo il contesto aziendale e potevamo agire di conseguenza al fine di aiutare effettivamente l'utente finale, mostrando così qualcosa di simile:

    Il campo D13 non può avere il valore identico a quello nel campo E6, poiché la sottrazione di tali valori viene utilizzata come divisore. [OK]

    o forse:

    I valori riportati dal servizio ATP non sono coerenti con i dati locali. Ciò può essere causato dalla mancata sincronizzazione dei dati locali. Desideri sincronizzare le informazioni di spedizione e riprovare? [Si No]

Quei messaggi non sono per l'analisi

Non è previsto che i messaggi di eccezione vengano analizzati o utilizzati a livello di codice. Se si ritiene che il chiamante possa richiedere ulteriori informazioni, includerle nell'eccezione parallelamente al messaggio. Questo è importante perché il messaggio è soggetto a modifiche senza preavviso. Il tipo fa parte dell'interfaccia, ma il messaggio non lo è: non fare mai affidamento su di esso per la gestione delle eccezioni.

Immagina il messaggio di eccezione:

La connessione al server di memorizzazione nella cache è scaduta dopo aver atteso 500 ms. Aumentare il timeout o controllare il monitoraggio delle prestazioni per identificare un calo delle prestazioni del server. Il tempo medio di attesa per il server di memorizzazione nella cache era di 6 ms. per l'ultimo mese, 4 ms. per l'ultima settimana e 377 ms. per l'ultima ora.

Si desidera estrarre i valori "500", "6", "4" e "377". Pensa un po 'all'approccio che utilizzerai per eseguire l'analisi, e solo allora continua a leggere.

Hai l'idea? Grande.

Ora, lo sviluppatore originale ha scoperto un errore di battitura:

Connecting to the caching sever timed out after waiting [...]

dovrebbe essere:

                            ↓
Connecting to the caching server timed out after waiting [...]

Inoltre, lo sviluppatore ritiene che il mese / settimana / un'ora non siano particolarmente rilevanti, quindi effettua anche una modifica aggiuntiva:

Il tempo medio di attesa per il server di memorizzazione nella cache era di 6 ms. per l'ultimo mese, 5 ms. per le ultime 24 ore e 377 ms. per l'ultima ora.

Cosa succede con l'analisi?

Invece di analizzare, potresti utilizzare le proprietà dell'eccezione (che possono contenere ciò che desideri, non appena i dati possono essere serializzati):

{
    message: "Connecting to the caching [...]",
    properties: {
        "timeout": 500,
        "statistics": [
            { "timespan": 1, "unit": "month", "average-timeout": 6 },
            { "timespan": 7, "unit": "day", "average-timeout": 4 },
            { "timespan": 1, "unit": "hour", "average-timeout": 377 },
        ]
    }
}

Quanto è facile utilizzare questi dati ora?

A volte (come all'interno di .NET), il messaggio può anche essere tradotto nella lingua dell'utente (IMHO, tradurre quei messaggi è assolutamente sbagliato, dal momento che ogni sviluppatore dovrebbe essere in grado di leggere in inglese). L'analisi di tali messaggi è quasi impossibile.


2
Un buon punto per l'utente finale. Vorrei aggiungere che l'utente finale non dovrebbe vedere i messaggi di eccezione anche per motivi di sicurezza. Conoscere la natura esatta di un errore per un utente non profano può presentare un exploit. L'utente finale dovrebbe conoscere l'errore solo nel contesto di ciò che stava facendo.
Neil,

1
@Neil: è un po 'troppo teorico. In pratica, il vantaggio di nascondere i registri all'utente è compensato dalla complessità della registrazione online. Senza contare l'aspetto intuitivo. Immagina di installare Visual Studio sulla tua nuova macchina virtuale Windows. Improvvisamente, l'installazione non riesce. Preferiresti semplicemente andare nei registri e diagnosticare tu stesso il problema (con l'aiuto di Stack Exchange e blog), o aspettare che Microsoft risolva il problema e pubblichi una correzione?
Arseni Mourzenko,

4
Penso che tutti siano consapevoli che il messaggio "riferimento all'oggetto ..." non è utile. La domanda rilevante è: quale codice macchina vorresti generare dal jitter che produce il messaggio desiderato? Se fai questo esercizio, scopri rapidamente che il messaggio non può essere facilmente generato senza un enorme costo prestazionale imposto a tutti i casi non eccezionali . Il vantaggio di rendere leggermente più semplice il lavoro di uno sviluppatore incurante non è pagato dal risultato positivo per l'utente finale.
Eric Lippert,

7
Per quanto riguarda le eccezioni di sicurezza: meno informazioni nell'eccezione, meglio è. Il mio aneddoto preferito è che in una versione pre-beta di .NET 1.0 potresti ricevere un messaggio di eccezione del tipo "Non hai i permessi per determinare il nome del file C: \ foo.txt". Fantastico, grazie per il messaggio di eccezione dettagliato!
Eric Lippert,

2
Non sono d'accordo con il fatto di non mostrare all'utente finale un messaggio di errore dettagliato. Molte volte mi è capitato di ricevere un messaggio di errore criptico che mi impediva di avviare il mio gioco / software, ma quando lo cerco su Google scopro che esiste una soluzione semplice. Senza quel messaggio di errore non ci sarebbe modo di trovare la soluzione alternativa. Forse è meglio nascondere i dettagli sotto un pulsante "più dettagli"?
BlueRaja - Danny Pflughoeft,

2

La risposta a questa dipende interamente dall'eccezione.

La domanda più importante da porsi è "chi può risolvere il problema?" Se l'eccezione è causata da un errore del file system durante l'apertura di un file, può essere logico dare quel messaggio all'utente finale. Il messaggio può contenere ulteriori informazioni su come correggere l'errore. Mi viene in mente un caso definitivo nel mio lavoro in cui avevo un sistema di plugin che caricava DLL. Se un utente ha fatto un refuso sulla riga di comando per caricare il plugin sbagliato, il messaggio di errore di sistema sottostante conteneva effettivamente informazioni utili per consentire loro di correggere il problema.

Il più delle volte, tuttavia, l'utente non può correggere un'eccezione non rilevata. La maggior parte di essi prevede di apportare modifiche al codice. In questi casi, il consumatore del messaggio è chiaramente uno sviluppatore.

Il caso delle eccezioni rilevate è più complicato perché hai l'opportunità di elaborare correttamente l'eccezione e lanciarne una più amichevole. Un linguaggio di scripting che ho scritto ha generato eccezioni il cui messaggio era chiaramente inteso per uno sviluppatore. Tuttavia, man mano che lo stack si svolgeva, ho aggiunto tracce di stack leggibili dall'utente all'interno del linguaggio di scripting. I miei utenti molto raramente potrebbero sgridare il messaggio, ma possono guardare la traccia dello stack nel loro script e capire cosa è successo il 95% delle volte. Per il restante 5%, quando mi hanno chiamato, non avevamo solo una traccia dello stack, ma un messaggio pronto per lo sviluppatore per aiutarmi a capire cosa non andava.

In tutto, tratto il messaggio di eccezione come un contenuto sconosciuto. Qualcuno più a monte, che sapeva di più sulle implementazioni, ha dato un'eccezione al mio software. A volte ho la sensazione che contenuti sconosciuti possano essere utili a un utente finale, a volte no.


1
"Se l'eccezione è causata da un errore del file system durante l'apertura di un file, può avere senso dare quel messaggio all'utente finale": ma quando si genera l'errore del file system, non si conosce il contesto: potrebbe essere l'azione dell'utente o qualcosa di completamente non correlato (come la tua app che esegue un backup di una configurazione, senza che l'utente se ne accorga). Ciò implica che avrai un blocco try / catch che mostra un messaggio di errore , che è molto diverso dalla visualizzazione diretta del messaggio di eccezione .
Arseni Mourzenko,

@MainMa Potresti non conoscere esplicitamente il contesto, ma ci sono tonnellate di suggerimenti. Ad esempio, se stanno aprendo un file e l'eccezione è "IOError: permesso negato", c'è una ragionevole possibilità che l'utente possa mettere insieme 2 e 2 e capire il file in questione. Il mio editor preferito lo fa: genera semplicemente il testo dell'eccezione in una finestra di dialogo, senza lanugine. D'altra parte, se stavo caricando la mia applicazione e mi venisse "negato il permesso" durante il caricamento di un mucchio di immagini, è molto meno ragionevole supporre che l'utente possa fare qualcosa con le informazioni.
Cort Ammon - Ripristina Monica il

Discutendo per il tuo punto, non ho mai visto alcun valore nel mostrare a un utente una NullReferenceException, a parte il fatto che il semplice atto di visualizzare quel messaggio mostra che il tuo software non aveva idea di come recuperare! Quel messaggio di eccezione non è mai utile per un utente finale.
Cort Ammon - Ripristina Monica il
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.