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

9

Ho letto il consiglio su questa domanda su come deve essere gestita un'eccezione il più vicino possibile al punto in cui viene sollevata.

Il mio dilemma sulla miglior pratica è se si dovrebbe usare un try / catch / finally per restituire un enum (o un int che rappresenta un valore, 0 per errore, 1 per ok, 2 per avvertimento ecc., a seconda sul caso) in modo che una risposta sia sempre in ordine, o si debba lasciare passare l'eccezione in modo che la parte chiamante possa occuparsene?

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 gestita sul posto, ma diciamo che all'interno di una funzione che pubblica / riceve alcuni dati tramite http, tu vorrebbe l'eccezione (ad esempio nel caso di 404) per passare semplicemente a quella che l'ha licenziata. Se non lo fai, dovresti creare un modo per informare la parte chiamante della qualità del risultato (errore: 404), così come il risultato stesso.

Sebbene sia possibile provare a catturare l'eccezione 404 all'interno della funzione helper che ottiene / pubblica i dati, dovresti? È solo io che uso un piccolo intento per indicare gli stati nel programma (e documentarli in modo appropriato, ovviamente), e quindi utilizzare queste informazioni per scopi di convalida dell'idoneità (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. Permettetemi di chiarire di che cosa si tratta: gestire le eccezioni lanciate, non lanciare eccezioni. Qual è l'effetto desiderato: rileva un errore e prova a ripristinarlo. 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 si trovasse in una libreria che stai usando, ti aspetteresti che fornisca un codice di stato per l'operazione o lo includerai in un blocco try-catch? Se si sta progettando , fornire un codice di stato o generare un'eccezione e lasciare che il livello superiore lo traduca in un codice / messaggio di stato?

Sintesi: Come scegli se un pezzo di codice anziché produrre un'eccezione, restituisce un codice di stato insieme ai risultati che potrebbe produrre?

    
posta Mihalis Bagos 15.02.2012 - 19:21
fonte

8 risposte

15

Le eccezioni dovrebbero essere utilizzate per condizioni eccezionali. Lanciare un'eccezione significa sostanzialmente affermare: "Non posso gestire questa condizione qui, qualcuno che si trova più in alto nello stack delle chiamate può prenderlo e gestirlo?"

Restituire un valore può essere preferibile, se è chiaro che chi chiama prenderà quel valore e farà qualcosa di significativo con esso. Ciò è particolarmente vero se il lancio di un'eccezione ha implicazioni sulle prestazioni, ad esempio può verificarsi in un ciclo stretto. Lanciare un'eccezione richiede molto più tempo rispetto alla restituzione di un valore (di almeno due ordini di grandezza).

Le eccezioni non dovrebbero mai essere utilizzate per implementare la logica del programma. In altre parole, non fare un'eccezione per ottenere qualcosa; lanciare un'eccezione per dichiarare che non si può fare.

    
risposta data 15.02.2012 - 19:57
fonte
4

Alcuni buoni consigli che ho letto una volta erano, lanciare eccezioni quando non puoi progredire dato lo stato dei dati con cui hai a che fare, tuttavia se hai un metodo che può lanciare un'eccezione, fornisci anche dove possibile un metodo per affermare se il i dati sono effettivamente validi prima che il metodo venga chiamato.

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

Per rispondere alla parte della domanda "Quando dovrei affrontare un'eccezione", direi ovunque tu possa effettivamente fare qualcosa al riguardo. Se il tuo metodo non può gestire un'eccezione generata da un metodo che chiama, non rilevarlo. Lascia che cresca più in alto nella catena di chiamate verso qualcosa che possa affrontarlo. In alcuni casi, questo potrebbe essere solo un logger che ascolta Application.UnhandledException.

    
risposta data 15.02.2012 - 23:26
fonte
3

use a try/catch/finally to return an enum (or an int that represents a value, 0 for error, 1 for ok, 2 for warning etc, depending on the case) so that an answer is always in order,

È un design terribile. Non "mascherare" un'eccezione traducendo in un codice numerico. Lascia che sia un'eccezione corretta e non ambigua.

or should one let the exception go through so that the calling part would deal with it?

Ecco a cosa servono le eccezioni.

dealt with as close to where it is raised as possible

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

    
risposta data 15.02.2012 - 23:32
fonte
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 è prenderle solo quando puoi fare qualcosa al riguardo. Catturarli e restituire un valore numerico alla funzione chiamante è generalmente un cattivo progetto. Devi solo lasciarli fluttuare finché non puoi recuperare.

Considero sempre la gestione delle eccezioni un passo avanti rispetto alla logica della mia applicazione. Mi chiedo, se questa eccezione viene lanciata fino a quando il backup dello stack delle chiamate è necessario eseguire la scansione prima che la mia applicazione si trovi in uno stato ripristinabile? In molti casi, se non c'è nulla che io possa fare all'interno dell'applicazione per il ripristino, ciò potrebbe significare che non lo prendo fino al livello più alto e registro solo l'eccezione, fallire il lavoro e provare a chiudere in modo pulito.

Non c'è davvero una regola ferrea su quando e come impostare la gestione delle eccezioni se non lasciarli soli fino a quando non sai cosa fare con loro.

    
risposta data 16.02.2012 - 04:08
fonte
1

Le eccezioni sono cose bellissime. Ti permettono di produrre una chiara descrizione di un problema di run time senza ricorrere a inutili ambiguità. Le eccezioni possono essere digitate, sovrascritte e possono essere gestite per tipo. Possono essere passati in giro per la gestione altrove, e se non possono essere gestiti possono essere controrilanciati per essere trattati a un livello superiore nella tua applicazione. Verranno automaticamente restituiti dal tuo metodo senza bisogno di invocare un sacco di logica pazzesca per gestire codici di errore offuscati.

Dovresti lanciare un'eccezione immediatamente dopo aver incontrato dati non validi nel tuo codice. Si dovrebbe avvolgere le chiamate ad altri metodi in un try..catch..istruzione per gestire tutte le eccezioni che potrebbero essere lanciati, e se non si sa come rispondere a qualsiasi eccezione, si gettano 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 un sacco di inutili duplicazioni nel codice e / o molte logiche disordinate per gestire gli stati di errore. Essere un utente e incontrare un codice di errore è anche peggio, poiché il codice stesso non è significativo e non fornirà all'utente un contesto per l'errore. Un'eccezione invece 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 ...", o "Non lo faccio sapere cosa è successo, contattare il supporto tecnico e dire loro che sono appena andato in crash, e dare loro la seguente traccia dello stack ... ".

Quindi la mia domanda all'OP è perché sulla Terra vorresti NON voler utilizzare le eccezioni sulla restituzione dei codici di errore?

    
risposta data 16.02.2012 - 14:39
fonte
0

Tutte le buone risposte. Vorrei anche aggiungere che restituire un codice di errore anziché lanciare un'eccezione può rendere più complicato il codice del chiamante. Dì metodo Un metodo di chiamata B chiama il metodo C e C incontra un errore. Se C restituisce un codice di errore, ora B deve avere la logica per determinare se può gestire quel codice di errore. Se non può, allora è necessario restituirlo a A. Se A non può gestire l'errore, allora cosa fai? Lanciare un'eccezione? In questo esempio, il codice è molto più pulito se C emette semplicemente un'eccezione, B non cattura l'eccezione, quindi annulla automaticamente senza alcun codice aggiuntivo necessario e A può rilevare determinati tipi di eccezioni lasciando che altri continuino la chiamata impilare.

Questo fa pensare a una buona regola da codificare:

Lines of code are like golden bullets. You want to use as few as possible to get the job done.

    
risposta data 16.02.2012 - 15:44
fonte
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 lanciare un'eccezione interrompe l'esecuzione perché non voglio che l'esecuzione continui quando i dati non sono validi.

Ad 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 booleano, lo sviluppatore che usa la mia funzione dovrebbe tener conto di ciò scrivendo:

if not Validate() then
  DoNotContinue();

ma potrebbe dimenticarsi e chiamare solo Validate() (so che non dovrebbe, ma forse potrebbe farlo).

Quindi, nel codice sopra ho guadagnato i due vantaggi:

  1. Solo un'eccezione nella funzione di convalida.
  2. L'eccezione, anche non risolta, interrompe l'esecuzione e viene visualizzata al momento del test
risposta data 15.05.2013 - 12:43
fonte
0

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

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

Per quanto riguarda il lancio di quell'eccezione - o l'involucro e il ripensamento - penso che sia davvero una questione di utilizzo. Ad esempio, se si sta scrivendo un wrapper per prelevare alcuni dati dall'API ed esporlo alle applicazioni, si potrebbe decidere che semanticamente una richiesta di una risorsa inesistente che restituisce un 404 HTTP avrebbe più senso rilevarla e restituire null. D'altra parte un errore 406 (non accettabile) potrebbe valere la pena di lanciare un errore in quanto ciò significa che qualcosa è cambiato e l'app dovrebbe andare in crash, bruciare e urlare aiuto.

    
risposta data 15.05.2013 - 13:51
fonte

Leggi altre domande sui tag