Come evitare di generare eccezioni fastidiose?


21

Leggere l' articolo sulle eccezioni di Eric Lippert è stato sicuramente un modo per aprire gli occhi su come dovrei affrontare le eccezioni, sia come produttore che come consumatore. Tuttavia, sto ancora lottando per definire una linea guida su come evitare di lanciare eccezioni fastidiose.

In particolare:

  • Supponiamo di avere un metodo Save che può fallire perché a) Qualcun altro ha modificato il record prima di te oppure b) Il valore che stai tentando di creare esiste già . Queste condizioni sono prevedibili e non eccezionali, quindi invece di generare un'eccezione, decidi di creare una versione Try del tuo metodo, TrySave, che restituisce un valore booleano che indica se il salvataggio è riuscito. Ma se fallisce, come farà il consumatore a sapere qual è stato il problema? O sarebbe meglio restituire un enum che indica il risultato, tipo di Ok / RecordAlreadyModified / ValueAlreadyExists? Con integer. TryParse questo problema non esiste, poiché esiste solo una ragione per cui il metodo può fallire.
  • L'esempio precedente è davvero una situazione fastidiosa? O lanciare un'eccezione in questo caso sarebbe il modo preferito? So che è così che viene fatto nella maggior parte delle librerie e dei framework, incluso il framework Entity.
  • Come decidi quando creare una versione di prova del tuo metodo anziché fornire in anticipo un modo per testare se il metodo funzionerà o no? Attualmente sto seguendo queste linee guida:
    • Se esiste la possibilità di una condizione di gara, crea una versione di prova. Ciò impedisce al consumatore di cogliere un'eccezione esogena. Ad esempio, nel metodo Salva descritto in precedenza.
    • Se il metodo per testare la condizione praticamente farebbe tutto ciò che fa il metodo originale, quindi creare una versione di prova. Ad esempio, integer.TryParse ().
    • In ogni altro caso, creare un metodo per testare la condizione.

1
Il tuo esempio di salvataggio che potrebbe non riuscire non è in realtà un'eccezione terribilmente fastidiosa. È abbastanza ordinario e probabilmente dovrebbe essere semplicemente un'eccezione.
S. Lott,

@ S.Lott: Cosa intendi con è abbastanza ordinario? La situazione stessa, o gettando un'eccezione in questa situazione? Ad ogni modo, concordo con te sul fatto che non è evidente se si tratti in realtà di una situazione irritante. Aggiornerò la domanda.
Mike,

"La situazione stessa, o gettando un'eccezione in questa situazione" Entrambi.
S. Lott,

Risposte:


24

Supponiamo di avere un metodo Save che può fallire perché a) Qualcun altro ha modificato il record prima di te oppure b) Il valore che stai tentando di creare esiste già. Queste condizioni sono prevedibili e non eccezionali, quindi invece di generare un'eccezione, decidi di creare una versione Try del tuo metodo, TrySave, che restituisce un valore booleano che indica se il salvataggio è riuscito. Ma se fallisce, come farà il consumatore a sapere qual è stato il problema?

Buona domanda.

La prima domanda che mi viene in mente è: se i dati sono già lì, in che senso il salvataggio è fallito ? Sembra proprio che mi sia riuscito. Ma supponiamo, per amor di discussione, che tu abbia davvero molte ragioni diverse per cui un'operazione può fallire.

La seconda domanda che mi viene in mente è: le informazioni che desideri restituire all'utente sono fruibili ? Cioè, prenderanno alcune decisioni sulla base di tali informazioni?

Quando si accende la spia "Verifica motore", apro il cofano, verifico che nella mia auto c'è un motore che non è in fiamme e lo porto in garage. Naturalmente in garage hanno tutti i tipi di apparecchiature diagnostiche per scopi speciali che dicono loro perché la spia del motore di controllo è accesa, ma dal mio punto di vista, il sistema di allarme è ben progettato. Non mi importa se il problema è dovuto al fatto che il sensore di ossigeno sta registrando un livello anormale di ossigeno nella camera di combustione, o perché il rilevatore del minimo è scollegato, o altro. Intendo fare la stessa azione, ovvero lasciare che qualcun altro lo capisca .

Al chiamante importa perché il salvataggio non è riuscito? Faranno qualcosa al riguardo, oltre a rinunciare o riprovare?

Supponiamo per amor di discussione che il chiamante eseguirà davvero azioni diverse a seconda del motivo per cui l'operazione non è riuscita.

La terza domanda che mi viene in mente è: la modalità di fallimento è eccezionale ? Penso che potresti essere confuso possibile con non eccezionale . Penserei a due utenti che tentano di modificare lo stesso record contemporaneamente a una situazione eccezionale ma possibile, non a una situazione comune.

Partiamo dal presupposto che non è eccezionale.

La quarta domanda che viene in mente è: c'è un modo per rilevare in modo affidabile la brutta situazione in anticipo?

Se la brutta situazione è nel mio secchio "esogeno", allora no. Non c'è modo di dire in modo affidabile "un altro utente ha modificato questo record?" perché potrebbero modificarlo dopo aver posto la domanda . La risposta è viziata non appena viene prodotta.

La quinta domanda che mi viene in mente è: esiste un modo per progettare l'API in modo da prevenire la brutta situazione?

Ad esempio, è possibile che l'operazione di "salvataggio" richieda due passaggi. Fase 1: acquisire un blocco sul record da modificare. Tale operazione ha esito positivo o negativo e pertanto può restituire un valore booleano. Il chiamante può quindi avere una politica su come gestire i guasti: attendere un po 'e riprovare, arrendersi, qualunque cosa. Fase due: una volta acquisito il blocco, eseguire il salvataggio e rilasciare il blocco. Ora il salvataggio ha sempre successo e quindi non è necessario preoccuparsi di alcun tipo di gestione degli errori. Se il salvataggio fallisce, è davvero eccezionale.


Tutti i punti molto buoni, grazie. Ora ecco una domanda retorica che probabilmente riassume il mio post: Se dovessi riprogettare File.Open (), creeresti invece un File.TryOpen ()? Come comunicheresti al consumatore la ragione del fallimento? O gettare un'eccezione esogena è davvero il miglior compromesso qui?
Mike,

10
@Mike: i file system sono un buon esempio dell'uso di eccezioni esogene. Raramente falliscono, quindi il fallimento è eccezionale. Non riescono in modo imprevedibile e per ragioni completamente al di fuori del controllo del chiamante (non è possibile eseguire un "blocco" che mantenga il cavo Ethernet collegato) e gli errori sono sia diversi che utilizzabili (vale a dire, un errore perché il file è non trovato vs file viene trovato ma non si ha accesso in scrittura entrambi sono utilizzabili in diversi modi.) Tutti questi sono motivi per rappresentare un errore come eccezione.
Eric Lippert,

Considero ora la risposta alla domanda, ma sono solo curioso;) Se il metodo può fallire per 2 o più motivi, i fallimenti sono attuabili, i fallimenti sono ineccepibili e i fallimenti non possono essere rilevati in anticipo o prevenuti, Cosa faresti?
Mike,

@Mike Non posso parlare per Eric, ma sembra un buon posto per i codici di errore. Restituire un membro di un enum, forse.
Matteo Leggi il

1

Nel tuo esempio, se la situazione ValueAlreadyExists può essere facilmente verificata, dovrebbe essere verificata e potrebbe essere sollevata un'eccezione prima di tentare il salvataggio, non credo che in questa situazione dovrebbe essere necessario provare. Le condizioni di gara sono più difficili da controllare in anticipo, quindi avvolgere Save in a Try in questo caso è probabilmente un'ottima idea.

In generale, se c'è una condizione che penso sia molto probabile (come NoDataReturned, DivideByZero, ecc ...) O è molto facile da verificare (come una raccolta vuota o un valore NULL), provo a verificare in anticipo e affrontarlo prima di arrivare al punto in cui dovrei prendere un'eccezione. Ammetto che non è sempre facile conoscere queste condizioni in anticipo, a volte compaiono solo quando il codice è sottoposto a test rigorosi.


0

Il save()metodo deve generare un'eccezione.

Il livello più alto dovrebbe catturare e informare l'utente , senza terminare il programma, a meno che non sia un Unix come programmi da riga di comando, nel qual caso è OK terminare.

I valori di ritorno non sono un buon modo per gestire le eccezioni.

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.