Utilizzo di una validazione try-finally (senza catch) vs enum-state


9

Ho letto i consigli su questa domanda su come un'eccezione dovrebbe essere trattata il più vicino possibile a dove viene sollevata.

Il mio dilemma sulla migliore pratica è se si debba usare un tentativo / cattura / infine per restituire un enum (o un int che rappresenta un valore, 0 per errore, 1 per ok, 2 per avviso ecc., A seconda del caso) in modo che una risposta è sempre in ordine o si dovrebbe lasciare passare l'eccezione in modo che la parte chiamante la gestisca?

Da quello che posso raccogliere, questo potrebbe essere diverso a seconda del caso, quindi il consiglio originale sembra strano.

Ad esempio, su un servizio Web, si vorrebbe sempre restituire uno stato ovviamente, quindi qualsiasi eccezione deve essere trattata sul posto, ma diciamo all'interno di una funzione che pubblica / ottiene alcuni dati tramite http, si vorrebbe eccezione (ad esempio nel caso di 404) per passare solo a quello che l'ha sparato. In caso contrario, dovresti creare un modo per informare la parte chiamante della qualità del risultato (errore: 404), nonché del risultato stesso.

Sebbene sia possibile provare a rilevare l'eccezione 404 all'interno della funzione di supporto che ottiene / pubblica i dati, dovresti? Sono solo io che uso un smallint per indicare gli stati nel programma (e documentarli in modo appropriato ovviamente), e quindi usare queste informazioni per scopi di convalida della sanità (tutto ok / gestione degli errori) all'esterno?

Aggiornamento: mi aspettavo un'eccezione fatale / non fatale per la classificazione principale, ma non volevo includerla per non pregiudicare le risposte. Vorrei chiarire di cosa si tratta: gestire le eccezioni generate, non generare eccezioni. Qual è l'effetto desiderato: rileva un errore e prova a recuperarlo. Se il recupero non è possibile, fornire il feedback più significativo.

Ancora una volta, con l'esempio http get / post, la domanda è: dovresti fornire un nuovo oggetto che descriva cosa è successo al chiamante originale? Se questo helper fosse in una libreria che stai usando, ti aspetteresti che ti fornisca un codice di stato per l'operazione o lo includeresti in un blocco try-catch? Se lo stai progettando , forniresti un codice di stato o lanci un'eccezione e permetteresti al livello superiore di tradurlo in un codice / messaggio di stato?

Sinossi: Come scegli se un pezzo di codice invece di produrre un'eccezione, restituisce un codice di stato insieme a tutti i risultati che può produrre?


1
Ciò non ha a che fare con l'errore che sta cambiando la forma di gestione degli errori in uso. Nel caso 404 lo lasceresti passare perché non sei in grado di gestirlo.
Stonemetal,

"un int che rappresenta un valore, 0 per errore, 1 per ok, 2 per avviso ecc." Si prega di dire che questo è stato un esempio immediato! Usare 0 per indicare OK è decisamente lo standard ...
anaximander,

Risposte:


15

Le eccezioni dovrebbero essere utilizzate per condizioni eccezionali. Generare un'eccezione significa fondamentalmente affermare: "Non riesco a gestire questa condizione qui; qualcuno in alto nello stack di chiamate può catturarlo e gestirlo?"

Restituire un valore può essere preferibile, se è chiaro che il chiamante prenderà quel valore e farà qualcosa di significativo con esso. Ciò è particolarmente vero se il lancio di un'eccezione ha conseguenze sulle prestazioni, vale a dire che può verificarsi in un circuito ristretto. Generare un'eccezione richiede molto più tempo che restituire un valore (di almeno due ordini di grandezza).

Le eccezioni non devono mai essere utilizzate per implementare la logica del programma. In altre parole, non lanciare un'eccezione per fare qualcosa; gettare un'eccezione per affermare che non si poteva fare.


Grazie per la risposta, è il più informativo ma il mio focus è sulla gestione delle eccezioni e non sul lancio di eccezioni. Dovresti prendere l'eccezione 404 non appena la ricevi o dovresti lasciarla andare più in alto nello stack?
Mihalis Bagos,

Vedo la tua modifica, ma non cambia la mia risposta. Converti l'eccezione in un codice di errore se questo è significativo per il chiamante. In caso contrario, gestire l'eccezione; lascialo andare in pila; o catturarlo, fare qualcosa con esso (come scriverlo su un registro o qualcos'altro) e riprovare.
Robert Harvey,

1
+1: per una spiegazione ragionevole ed equilibrata. Un'eccezione dovrebbe essere utilizzata per gestire casi eccezionali. Altrimenti, una funzione o un metodo dovrebbe restituire un valore significativo (enum o tipo di opzione) e il chiamante dovrebbe gestirlo correttamente. Forse si potrebbe menzionare una terza alternativa che è popolare nella programmazione funzionale, vale a dire continuazioni.
Giorgio,

4

Qualche buon consiglio che ho letto una volta era, dare eccezioni quando non è possibile progredire dato lo stato dei dati con cui si ha a che fare, tuttavia se si dispone di un metodo che può generare un'eccezione, fornire anche ove possibile un metodo per affermare se i dati sono effettivamente valido prima che venga chiamato il metodo

Ad esempio, System.IO.File.OpenRead () genererà un FileNotFoundException se il file fornito non esiste, tuttavia fornisce anche un metodo .Exists () che restituisce un valore booleano che indica se il file è presente che è necessario chiamare prima chiamando OpenRead () per evitare eccezioni impreviste.

Per rispondere alla parte "quando dovrei occuparmi di un'eccezione", vorrei dire ovunque tu possa effettivamente fare qualcosa al riguardo. Se il tuo metodo non è in grado di gestire un'eccezione generata da un metodo che chiama, non prenderlo. Lascia che aumenti più in alto nella catena di chiamate a qualcosa che può gestirlo. In alcuni casi, potrebbe trattarsi solo di un logger che ascolta Application.UnhandledException.


Molte persone, in particolare i programmatori Python , preferiscono l' EAFP, ovvero "È più facile chiedere perdono che ottenere l'autorizzazione"
Mark Booth,

1
+1 per un commento sull'evitare eccezioni come con .Exists ().
codingoutloud

+1 Questo è ancora un buon consiglio. Nel codice che scrivo / gestisco, un'eccezione è "Eccezionale", 9/10 volte un'eccezione è pensata per uno sviluppatore, dice ehi, dovresti essere una programmazione difensiva! L'altra volta, è qualcosa che non possiamo affrontare, lo registriamo e usciamo al meglio. La coerenza è importante, ad esempio, per convenzione normalmente avremmo una vera risposta falsa e messaggi interni per tariffa / elaborazione standard. API di terze parti che sembrano generare eccezioni per tutto ciò che può essere gestito su chiamata e restituito utilizzando il processo standard concordato.
Gavin Howden,

3

usa un tentativo / cattura / infine per restituire un enum (o un int che rappresenta un valore, 0 per errore, 1 per ok, 2 per avviso ecc., a seconda del caso) in modo che una risposta sia sempre in ordine,

È un design terribile. Non "mascherare" un'eccezione traducendo in un codice numerico. Lascialo come un'eccezione corretta e inequivocabile.

o si dovrebbe lasciare passare l'eccezione in modo che la parte chiamante possa gestirla?

Ecco a cosa servono le eccezioni.

affrontato il più vicino possibile al luogo di raccolta

Non è affatto una verità universale . È una buona idea alcune volte. Altre volte non è così utile.


Non è un design terribile. Microsoft lo implementa in molti punti, in particolare sul provider di appartenenza asp.NET predefinito. A parte questo, non riesco a vedere come questa risposta contribuisca alla conversazione
Mihalis Bagos,

@MihalisBagos: Tutto quello che posso fare è suggerire che l'approccio di Microsoft non è abbracciato da tutti i linguaggi di programmazione. Nelle lingue senza eccezioni, la restituzione di un valore è essenziale. C è l'esempio più notevole. Nelle lingue con eccezioni, restituire "valori di codice" per indicare errori è un progetto terribile. Porta a ifdichiarazioni (a volte) ingombranti anziché a (a volte) una gestione delle eccezioni più semplice. La risposta ("Come scegliere ...") è semplice. Non farlo . Penso che questo aggiunga alla conversazione. Sei libero di ignorare questa risposta, comunque.
S. Lott,

Non sto dicendo che la tua opinione non conta ma sto dicendo che la tua opinione non è sviluppata. Con quel commento, prendo il ragionamento è che dove possiamo usare le eccezioni, dovremmo, solo perché possiamo? Non vedo il codice di stato come un mascheramento, piuttosto che una categorizzazione / organizzazione dei casi di flusso di codice
Mihalis Bagos,

1
L'eccezione è già una categorizzazione / organizzazione. Non vedo il valore nel sostituire un'eccezione non ambigua con un valore di ritorno che può essere facilmente confuso con valori di ritorno "normali" o "non eccezionali". I valori di ritorno devono essere sempre non eccezionali. La mia opinione non è molto sviluppata: è molto semplice. Non trasformare un'eccezione in un codice numerico. Era già un oggetto dati perfettamente valido che serve tutti i casi d'uso perfettamente validi che possiamo immaginare.
S. Lott,

3

Sono d'accordo con S.Lott . Catturare l'eccezione il più vicino possibile alla fonte può essere una buona idea o una cattiva idea a seconda della situazione. La chiave per gestire le eccezioni è catturarle solo quando puoi fare qualcosa al riguardo. Catturarli e restituire un valore numerico alla funzione chiamante è generalmente un progetto errato. Vuoi solo lasciarli galleggiare fino a quando non riesci a recuperare.

Considero sempre la gestione delle eccezioni un passo dalla mia logica applicativa. Mi chiedo, se viene generata questa eccezione, fino a che punto del backup dello stack di chiamate devo eseguire la scansione prima che la mia applicazione sia in uno stato recuperabile? In molti casi, se non c'è nulla che io possa fare all'interno dell'applicazione per recuperare, ciò potrebbe significare che non lo prendo fino al livello superiore e registro solo l'eccezione, fallisco il lavoro e provo a chiudere in modo pulito.

Non esiste davvero alcuna regola rigida per quando e come impostare la gestione delle eccezioni se non lasciarli soli fino a quando non si sa cosa farne.


1

Le eccezioni sono cose belle. Consentono di produrre una chiara descrizione di un problema di runtime senza ricorrere a inutili ambiguità. Le eccezioni possono essere digitate, sottotitolate e possono essere gestite per tipo. Possono essere passati in giro per la gestione altrove e, se non possono essere gestiti, possono essere rilanciati per essere gestiti a un livello superiore nell'applicazione. Torneranno anche automaticamente dal tuo metodo senza la necessità di invocare molta logica folle per gestire codici di errore offuscati.

Dovresti generare un'eccezione immediatamente dopo aver riscontrato dati non validi nel tuo codice. Dovresti racchiudere le chiamate ad altri metodi in un try..catch..finally per gestire eventuali eccezioni che potrebbero essere generate e, se non sai come rispondere a una determinata eccezione, lanciarle di nuovo per indicare ai livelli superiori che c'è qualcosa di sbagliato che dovrebbe essere gestito altrove.

La gestione dei codici di errore può essere molto difficile. Di solito si finisce con molte duplicazioni non necessarie nel codice e / o molta logica disordinata per gestire gli stati di errore. Essere un utente e riscontrare un codice di errore è anche peggio, poiché il codice stesso non sarà significativo e non fornirà all'utente un contesto per l'errore. D'altra parte, un'eccezione può dire all'utente qualcosa di utile, come "Hai dimenticato di inserire un valore" o "Hai inserito un valore non valido, ecco l'intervallo valido che puoi usare ...", oppure "Non lo so sapere cosa è successo, contattare l'assistenza tecnica e dire loro che sono appena andato in crash e dare loro la seguente traccia di stack ... ".

Quindi la mia domanda all'OP è: perché mai NON vorresti usare le eccezioni sulla restituzione dei codici di errore?


0

Tutte buone risposte. Vorrei anche aggiungere che restituire un codice di errore anziché generare un'eccezione può rendere il codice del chiamante più complicato. Dire che il metodo A chiama il metodo B che chiama il metodo C e C che rileva un errore. Se C restituisce un codice di errore, ora B deve disporre della logica per determinare se è in grado di gestire quel codice di errore. In caso contrario, è necessario restituirlo ad A. Se A non è in grado di gestire l'errore, cosa si fa? Generare un'eccezione? In questo esempio, il codice è molto più pulito se C genera semplicemente un'eccezione, B non rileva l'eccezione, quindi si interrompe automaticamente senza che sia necessario alcun codice aggiuntivo e A può rilevare alcuni tipi di eccezioni mentre consente ad altri di continuare la chiamata pila.

Questo fa venire in mente una buona regola per codificare:

Le righe di codice sono come proiettili d'oro. Vuoi usare il minor numero possibile per portare a termine il lavoro.


0

Ho usato una combinazione di entrambe le soluzioni: per ogni funzione di convalida, passo un record che riempio con lo stato di convalida (un codice di errore). Alla fine della funzione, se esiste un errore di convalida, lancio un'eccezione, in questo modo non lancio un'eccezione per ogni campo, ma solo una volta.

Ho anche approfittato del fatto che il lancio di un'eccezione interromperà l'esecuzione perché non voglio che l'esecuzione continui quando i dati non sono validi.

Per esempio

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Se si basa solo su un valore booleano, lo sviluppatore che utilizza la mia funzione dovrebbe tenerne conto durante la scrittura:

if not Validate() then
  DoNotContinue();

ma potrebbe aver dimenticato e solo chiamare Validate()(so che non dovrebbe, ma forse potrebbe).

Quindi, nel codice sopra ho ottenuto i due vantaggi:

  1. Solo un'eccezione nella funzione di convalida.
  2. L'eccezione, anche non rilevata, interromperà l'esecuzione e apparirà al momento del test

0

Non c'è una risposta qui - un po 'come se non ci fosse un tipo di HttpException.

Ha molto senso che le librerie HTTP sottostanti generino un'eccezione quando ottengono una risposta 4xx o 5xx; l'ultima volta che ho esaminato le specifiche HTTP erano errori.

Per quanto riguarda il lancio di tale eccezione - o il confezionamento e il rilancio - penso che sia davvero una questione di caso d'uso. Ad esempio, se si sta scrivendo un wrapper per acquisire alcuni dati dall'API ed esporli alle applicazioni, è possibile decidere che semanticamente una richiesta per una risorsa inesistente che restituisce un HTTP 404 avrebbe più senso catturarlo e restituire null. D'altra parte, vale la pena lanciare un errore 406 (non accettabile) poiché ciò significa che qualcosa è cambiato e che l'app dovrebbe andare in crash, bruciare e gridare aiuto.

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.