Mi è stato detto che le eccezioni dovrebbero essere utilizzate solo in casi eccezionali. Come faccio a sapere se il mio caso è eccezionale?


99

Il mio caso specifico qui è che l'utente può passare una stringa nell'applicazione, l'applicazione la analizza e la assegna a oggetti strutturati. A volte l'utente può digitare qualcosa di non valido. Ad esempio, il loro contributo può descrivere una persona, ma possono dire che la loro età è "mela". Il comportamento corretto in quel caso è il rollback della transazione e per dire all'utente che si è verificato un errore e dovranno riprovare. Potrebbe essere necessario segnalare ogni errore che possiamo trovare nell'input, non solo il primo.

In questo caso, ho sostenuto che dovremmo lanciare un'eccezione. Non era d'accordo, dicendo: "Le eccezioni dovrebbero essere eccezionali: si prevede che l'utente possa inserire dati non validi, quindi questo non è un caso eccezionale" Non sapevo davvero come argomentare questo punto, perché per definizione della parola, egli sembra avere ragione.

Ma capisco che questo è il motivo per cui le eccezioni sono state inventate in primo luogo. Ha usato essere che si doveva ispezionare il risultato per vedere se si è verificato un errore. Se non riesci a controllare, potrebbero accadere cose brutte senza che te ne accorga.

Senza eccezioni, ogni livello dello stack deve controllare il risultato dei metodi che chiamano e se un programmatore dimentica di controllare uno di questi livelli, il codice potrebbe procedere accidentalmente e salvare dati non validi (ad esempio). Sembra più incline all'errore in quel modo.

Ad ogni modo, sentiti libero di correggere qualsiasi cosa abbia detto qui. La mia domanda principale è se qualcuno dice che le eccezioni dovrebbero essere eccezionali, come faccio a sapere se il mio caso è eccezionale?


3
possibile duplicato? Quando lanciare un'eccezione . Sebbene fosse chiuso lì, ma penso che si adatti qui. È ancora un po 'di filosofia, alcune persone e comunità tendono a vedere le eccezioni come una sorta di controllo del flusso.
Thorsten Müller,

8
Quando gli utenti sono stupidi, forniscono input non validi. Quando gli utenti sono intelligenti, giocano fornendo input non validi. Pertanto, l'input dell'utente non valido non costituisce un'eccezione.
mouviciel,

7
Inoltre, non confondere un'eccezione , che è un meccanismo molto specifico in Java e .NET, con un errore che è un termine molto più generico. C'è di più nella gestione degli errori oltre a generare eccezioni. Questa discussione tocca le sfumature tra eccezioni ed errori .
Eric King,

4
"Exceptional"! = "Rarely Happens"
ConditionRacer

3
Trovo che le eccezioni di Vexing di Eric Lippert siano un consiglio decente.
Brian

Risposte:


87

Sono state inventate eccezioni per facilitare la gestione degli errori con meno ingombro di codice. Dovresti usarli nei casi in cui semplificano la gestione degli errori con meno ingombro di codice. Questa attività "eccezioni solo per circostanze eccezionali" deriva da un'epoca in cui la gestione delle eccezioni era considerata un risultato inaccettabile in termini di prestazioni. Questo non è più il caso nella stragrande maggioranza del codice, ma le persone continuano a pronunciare la regola senza ricordare il motivo.

Soprattutto in Java, che è forse il linguaggio più amante delle eccezioni mai concepito, non dovresti sentirti male nell'usare le eccezioni quando semplifica il tuo codice. In effetti, la Integerclasse propria di Java non ha un mezzo per verificare se una stringa è un numero intero valido senza potenzialmente lanciare a NumberFormatException.

Inoltre, anche se non puoi fare affidamento solo sulla convalida dell'interfaccia utente, tieni presente se l'interfaccia utente è progettata correttamente, ad esempio l'utilizzo di uno spinner per l'immissione di valori numerici brevi, quindi un valore non numerico che lo rende nel back-end sarebbe davvero un condizioni eccezionali.


10
Bel colpo, lì. In realtà, nell'app del mondo reale che ho progettato, il successo delle prestazioni ha fatto la differenza e ho dovuto cambiarlo per non generare eccezioni per alcune operazioni di analisi.
Robert Harvey,

17
Non sto dicendo che non ci sono ancora casi in cui l'hit performance è una ragione valida, ma quei casi sono l'eccezione (gioco di parole) piuttosto che la regola.
Karl Bielefeldt,

11
@RobertHarvey Il trucco di Java è lanciare oggetti di eccezione prefabbricati, piuttosto che throw new .... In alternativa, genera eccezioni personalizzate, in cui fillInStackTrace () viene sovrascritto. Quindi non dovresti notare alcun peggioramento delle prestazioni, per non parlare dei successi .
Ingo,

3
+1: esattamente quello a cui avrei risposto. Usalo quando semplifica il codice. Le eccezioni possono fornire un codice molto più chiaro in cui non è necessario preoccuparsi di controllare i valori di ritorno su ogni livello dello stack di chiamate. (Come tutto il resto però, se usato nel modo sbagliato può rendere il tuo codice un casino orribile.)
Leo

4
@Brendan Supponiamo che si verifichi qualche eccezione e che il codice di gestione degli errori sia inferiore di 4 livelli nello stack di chiamate. Se si utilizza un codice di errore, tutte e 4 le funzioni sopra il gestore devono avere il tipo di codice di errore come valore di ritorno e si deve fare una catena di if (foo() == ERROR) { return ERROR; } else { // continue }ogni livello. Se si genera un'eccezione non selezionata, non vi sono rumori e ridondanti "se l'errore restituisce errore". Inoltre, se si passano funzioni come argomenti, l'utilizzo di un codice di errore può modificare la firma della funzione in un tipo incompatibile, anche se l'errore potrebbe non verificarsi.
Doval,

72

Quando deve essere generata un'eccezione? Quando si tratta di codice, penso che la seguente spiegazione sia molto utile:

Un'eccezione è quando un membro non riesce a completare l'attività che dovrebbe svolgere come indicato dal suo nome . (Jeffry Richter, CLR via C #)

Perché è utile? Suggerisce che dipende dal contesto in cui qualcosa deve essere gestito come un'eccezione o meno. A livello di chiamate al metodo, il contesto è dato da (a) il nome, (b) la firma del metodo e (b) il codice client, che utilizza o dovrebbe utilizzare il metodo.

Per rispondere alla tua domanda dovresti dare un'occhiata al codice, dove viene elaborato l'input dell'utente. Potrebbe assomigliare a questo:

public void Save(PersonData personData) {  }

Il nome del metodo suggerisce che è stata eseguita una convalida? No. In questo caso un PersonData non valido dovrebbe generare un'eccezione.

Supponiamo che la classe abbia un altro metodo che assomigli a questo:

public ValidationResult Validate(PersonData personData) {  }

Il nome del metodo suggerisce che è stata eseguita una convalida? Sì. In questo caso un PersonData non valido non dovrebbe generare un'eccezione.

Per mettere insieme le cose, entrambi i metodi suggeriscono che il codice client dovrebbe apparire così:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Quando non è chiaro se un metodo debba generare un'eccezione, forse è dovuto un nome o una firma del metodo scarsamente scelti. Forse il design della classe non è chiaro. A volte è necessario modificare la progettazione del codice per ottenere una risposta chiara alla domanda se un'eccezione deve essere generata o meno.


Proprio ieri ho creato un struct"ValidationResult" e strutturato il mio codice nel modo in cui lo descrivi.
paul

4
Non aiuta a rispondere alla tua domanda, ma mi piace solo sottolineare che hai seguito implicitamente o intenzionalmente il principio di separazione Command-query ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff,

Bella idea! Uno svantaggio: nel tuo esempio, la convalida viene effettivamente eseguita due volte: una volta durante Validate(restituendo False se non valido) e una volta durante Save(generando un'eccezione specifica e ben documentata se non valida). Naturalmente, il risultato della convalida potrebbe essere memorizzato nella cache all'interno dell'oggetto, ma ciò aggiungerebbe ulteriore complessità, poiché il risultato della convalida dovrebbe essere invalidato sulle modifiche.
Heinzi,

@Heinzi sono d'accordo. Potrebbe essere rifattorizzato in modo tale da Validate()essere chiamato all'interno del Save()metodo e dettagli specifici da ValidationResultpotrebbero essere usati per costruire un messaggio appropriato per l'eccezione.
Phil

3
Penso sia meglio della risposta accettata. Lancia quando la chiamata non può fare la cosa che doveva fare.
Andy,

31

Le eccezioni dovrebbero essere eccezionali: si prevede che l'utente possa inserire dati non validi, quindi questo non è un caso eccezionale

Su tale argomento:

  • Si prevede che un file potrebbe non esistere, quindi non è un caso eccezionale.
  • Si prevede che la connessione al server potrebbe andare persa, quindi non è un caso eccezionale
  • Si prevede che il file di configurazione possa essere confuso, quindi non è un caso eccezionale
  • Si prevede che a volte la tua richiesta possa cadere, quindi non è un caso eccezionale

Qualsiasi eccezione catturata, devi aspettarti perché, beh, hai deciso di prenderla. Quindi, secondo questa logica, non dovresti mai generare eccezioni che intendi effettivamente catturare.

Quindi penso che "le eccezioni dovrebbero essere eccezionali" è una terribile regola empirica.

Quello che dovresti fare dipende dalla lingua. Lingue diverse hanno convenzioni diverse su quando devono essere lanciate eccezioni. Python, ad esempio, genera eccezioni per tutto e quando in Python seguo l'esempio. Il C ++, d'altra parte, genera relativamente poche eccezioni, e qui seguo l'esempio. Puoi trattare C ++ o Java come Python e generare eccezioni per tutto, ma stai lavorando in contrasto con il modo in cui il linguaggio si aspetta di essere utilizzato.

Preferisco l'approccio di Python, ma penso che sia una cattiva idea inserirmi in altre lingue.


1
@gnat, lo so. Il mio punto era che dovresti seguire le convenzioni della lingua (in questo caso Java) anche se non sono le tue preferite.
Winston Ewert,

6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Ben detto! Questa è una di quelle cose che le persone ripetono semplicemente senza pensarci.
Andres F.

2
"Previsto" è definito non da argomentazioni o convenzioni soggettive ma dal contratto dell'API / funzione (questo potrebbe essere esplicito, ma spesso è semplicemente implicito). Diverse funzioni / API / sottosistemi possono avere aspettative diverse, ad esempio per alcune funzionalità di livello superiore un file inesistente è un caso previsto da gestire (potrebbe segnalarlo a un utente tramite una GUI), per altre funzioni di livello inferiore è probabilmente no (e quindi dovrebbe generare un'eccezione). Questa risposta sembra mancare quel punto importante ....
Mikera,

1
@mikera, sì una funzione dovrebbe (solo) generare le eccezioni definite nel suo contratto. Ma questa non è la domanda. La domanda è come decidere quale dovrebbe essere quel contratto. Ritengo che la regola empirica, "le eccezioni dovrebbero essere eccezionali" non è utile nel prendere questa decisione.
Winston Ewert,

1
@supercat, non penso che importi davvero cosa finisce per essere più comune. Penso che la domanda critica stia avendo un default sano. Se non gestisco esplicitamente la condizione di errore, il mio codice finge che non sia successo nulla o ricevo un messaggio di errore utile?
Winston Ewert,

30

Penso sempre a cose come l'accesso al server di database o un'API Web quando penso alle eccezioni. Ti aspetti che l'API server / web funzioni, ma in un caso eccezionale potrebbe non funzionare (server inattivo). Una richiesta Web potrebbe essere veloce in genere, ma in circostanze eccezionali (carico elevato) potrebbe scadere. Questo è qualcosa fuori dal tuo controllo.

I dati di input degli utenti sono sotto il tuo controllo, poiché puoi controllare ciò che inviano e fare con esso ciò che ti piace. Nel tuo caso, convaliderei l'input dell'utente prima ancora di provare a salvarlo. E tendo a concordare sul fatto che gli utenti che forniscono dati non validi dovrebbero essere previsti e la tua app dovrebbe tenerne conto convalidando l'input e fornendo un messaggio di errore intuitivo.

Detto questo, uso eccezioni nella maggior parte dei miei setter di modelli di dominio, dove non dovrebbe esserci alcuna possibilità che vengano inseriti dati non validi. Tuttavia, questa è un'ultima linea di difesa e tendo a costruire i miei moduli di input con regole di convalida avanzate , in modo che non vi sia praticamente alcuna possibilità di innescare tale eccezione al modello di dominio. Quindi quando un setter si aspetta una cosa e ne ottiene un'altra, si tratta di una situazione eccezionale, che non avrebbe dovuto accadere in circostanze normali.

EDIT (qualcos'altro da considerare):

Quando si inviano dati forniti dall'utente al db, si sa in anticipo cosa si dovrebbe e non si dovrebbe inserire nelle tabelle. Ciò significa che i dati possono essere convalidati in base al formato previsto. Questo è qualcosa che puoi controllare. Ciò che non puoi controllare è il fallimento del tuo server nel mezzo della tua query. Quindi sai che la query è ok e i dati sono filtrati / convalidati, provi la query e continua a fallire, questa è una situazione eccezionale.

Analogamente alle richieste Web, non è possibile sapere se la richiesta scadrà o non riuscirà a connettersi prima di provare a inviarla. Pertanto, ciò garantisce anche un approccio try / catch, poiché non è possibile chiedere al server se funzionerà qualche millisecondo dopo l'invio della richiesta.


8
Ma perché? Perché le eccezioni sono meno utili nella gestione dei problemi più attesi?
Winston Ewert,

6
@Pinetree, verificare l'esistenza del file prima di aprire un file non è una buona idea. Il file potrebbe cessare di esistere tra il controllo e l'apertura, il file non potrebbe disporre delle autorizzazioni che consentono di aprirlo e il controllo dell'esistenza e quindi l'apertura del file richiederà due costose chiamate di sistema. È meglio provare ad aprire il file e quindi gestire l'errore.
Winston Ewert,

10
Per quanto posso vedere, praticamente tutti i possibili guasti vengono gestiti meglio come recupero dall'errore piuttosto che tentare di verificare il successo prima della mano. Indipendentemente dal fatto che tu usi o meno eccezioni o qualcos'altro, indicare che l'errore è un problema separato. Preferisco le eccezioni perché non posso ignorarle accidentalmente.
Winston Ewert,

11
Non sono d'accordo con la tua premessa che, poiché sono previsti dati utente non validi, non possono essere considerati eccezionali. Se scrivo un parser e qualcuno gli fornisce dati non analizzabili, questa è un'eccezione. Non riesco a continuare ad analizzare. Come viene gestita l'eccezione è un'altra domanda.
ConditionRacer

4
File.ReadAllBytes genererà un FileNotFoundExceptioninput errato (ad esempio un nome di file inesistente). Questo è l'unico modo valido per minacciare questo errore, cos'altro puoi fare senza comportare la restituzione di codici di errore?
oɔɯǝɹ

16

Riferimento

Da The Pragmatic Programmer:

Riteniamo che le eccezioni debbano essere usate raramente come parte del normale flusso di un programma; le eccezioni dovrebbero essere riservate agli eventi imprevisti. Supponiamo che un'eccezione non rilevata interrompa il programma e chiediti "Questo codice verrà comunque eseguito se rimuovo tutti i gestori di eccezioni?" Se la risposta è "no", allora forse le eccezioni vengono utilizzate in circostanze non eccezionali.

Continuano a esaminare l'esempio dell'apertura di un file per la lettura e il file non esiste - dovrebbe sollevare un'eccezione?

Se il file avrebbe dovuto essere lì, allora è garantita un'eccezione. [...] D'altra parte, se non hai idea se il file debba esistere o meno, allora non sembra eccezionale se non riesci a trovarlo e un errore restituito è appropriato.

Successivamente, discutono del perché hanno scelto questo approccio:

[A] n eccezione rappresenta un trasferimento immediato e non locale del controllo - è una specie di cascata goto. I programmi che utilizzano le eccezioni come parte della loro normale elaborazione soffrono di tutti i problemi di leggibilità e manutenibilità del classico codice spaghetti. Questi programmi interrompono l'incapsulamento: le routine e i loro chiamanti sono più strettamente accoppiati tramite la gestione delle eccezioni.

Per quanto riguarda la tua situazione

La tua domanda si riduce a "Gli errori di convalida dovrebbero sollevare eccezioni?" La risposta è che dipende da dove sta avvenendo la validazione.

Se il metodo in questione si trova all'interno di una sezione del codice in cui si presume che i dati di input siano già stati convalidati, i dati di input non validi dovrebbero sollevare un'eccezione; se il codice è progettato in modo tale che questo metodo riceva l'input esatto immesso da un utente, si prevedono dati non validi e non si deve sollevare un'eccezione.


11

C'è molta pontificazione filosofica qui, ma in generale, condizioni eccezionali sono semplicemente quelle condizioni che non puoi o non vuoi gestire (oltre alla pulizia, alla segnalazione degli errori e simili) senza l'intervento dell'utente. In altre parole, sono condizioni irrecuperabili.

Se si passa a un programma un percorso di file, con l'intenzione di elaborarlo in qualche modo e il file specificato da quel percorso non esiste, questa è una condizione eccezionale. Non puoi fare nulla al riguardo nel tuo codice, oltre a segnalarlo all'utente e consentire loro di specificare un percorso file diverso.


1
+1, molto vicino a quello che stavo per dire. Direi che riguarda di più l'ambito e non ha davvero nulla a che fare con l'utente. Un buon esempio di ciò è la differenza tra le due funzioni .Net int.Parse e int. TryParse, la prima non ha altra scelta che gettare un'eccezione su input errati, la
seconda

1
@jmoreno: Ergo, useresti TryParse quando il codice poteva fare qualcosa per la condizione non analizzabile e Parse quando non poteva.
Robert Harvey,

7

Ci sono due preoccupazioni che dovresti considerare:

  1. discuti di una singola preoccupazione - chiamiamola Assignerpoiché questa preoccupazione è assegnare input a oggetti strutturati - ed esprimi il vincolo che i suoi input siano validi

  2. un'interfaccia utente ben implementata ha un'ulteriore preoccupazione: convalida dell'input dell'utente e feedback costruttivo sugli errori (chiamiamo questa parte Validator)

Dal punto di vista del Assignercomponente, lanciare un'eccezione è del tutto ragionevole, dal momento che hai espresso un vincolo che è stato violato.

Dal punto di vista dell'esperienza dell'utente , l'utente non dovrebbe parlare direttamente con questo Assignerin primo luogo. Dovrebbero parlarne tramite il Validator.

Ora, l' Validatorinput dell'utente non valido non è un caso eccezionale, è davvero il caso che ti interessa di più. Quindi qui un'eccezione non sarebbe appropriata, ed è anche qui che dovresti identificare tutti gli errori piuttosto che salvando il primo.

Noterai che non ho menzionato il modo in cui queste preoccupazioni sono implementate. Sembra che tu stia parlando di Assignere il tuo collega stia parlando di un combinato Validator+Assigner. Una volta che ti rendi conto che ci sono due preoccupazioni separate (o separabili), almeno puoi discuterne sensatamente.


Per rispondere al commento di Renan, sto solo supponendo che una volta identificate le tue due preoccupazioni separate, è ovvio quali casi debbano essere considerati eccezionali in ogni contesto.

In effetti, se non è ovvio se qualcosa debba essere considerato eccezionale, direi che probabilmente non hai finito di identificare le preoccupazioni indipendenti nella tua soluzione.

Immagino che sia la risposta diretta a

... come faccio a sapere se il mio caso è eccezionale?

continua a semplificare fino a quando non è ovvio . Quando hai una pila di concetti semplici che capisci bene, puoi ragionare chiaramente sulla loro ricomposizione in codice, classi, librerie o altro.


-1 Sì, ci sono due dubbi, ma questo non risponde alla domanda "Come faccio a sapere se il mio caso è eccezionale?"
RMalke,

Il punto è che lo stesso caso può essere eccezionale in un contesto e non in un altro. Identificare il contesto di cui stai effettivamente parlando (piuttosto che confonderli entrambi) risponde alla domanda qui.
Inutile

... in realtà, forse no - ho affrontato il tuo punto nella mia risposta, invece.
Inutile

4

Altri hanno risposto bene, ma ecco ancora la mia breve risposta. L'eccezione è una situazione in cui qualcosa nell'ambiente ha qualcosa di sbagliato, che non puoi controllare e il tuo codice non può andare avanti affatto. In questo caso dovrai anche informare l'utente su cosa è andato storto, perché non puoi andare oltre e qual è la risoluzione.


3

Non sono mai stato un grande fan del consiglio che dovresti dare eccezioni solo in casi eccezionali, in parte perché non dice nulla (è come dire che dovresti mangiare solo cibo commestibile), ma anche perché è molto soggettivo e spesso non è chiaro cosa costituisca un caso eccezionale e cosa no.

Tuttavia, ci sono buone ragioni per questo consiglio: lanciare e catturare le eccezioni è lento e se stai eseguendo il tuo codice nel debugger in Visual Studio con esso impostato per avvisarti ogni volta che viene generata un'eccezione, puoi finire per essere spammato da dozzine se non centinaia di messaggi molto prima di arrivare al problema.

Quindi, come regola generale, se:

  • il tuo codice è privo di bug e
  • i servizi da cui dipende sono tutti disponibili e
  • il tuo utente sta usando il tuo programma nel modo in cui era destinato a essere utilizzato (anche se alcuni degli input forniti non sono validi)

quindi il tuo codice non dovrebbe mai generare un'eccezione, anche una che viene rilevata in seguito. Per intercettare dati non validi, è possibile utilizzare i validatori a livello di interfaccia utente o codice come Int32.TryParse()nel livello di presentazione.

Per ogni altra cosa, dovresti attenersi al principio secondo cui un'eccezione significa che il tuo metodo non può fare ciò che dice il suo nome. In generale non è una buona idea utilizzare i codici di ritorno per indicare un errore (a meno che il nome del metodo non indichi chiaramente che lo fa, ad esempio TryParse()) per due motivi. Innanzitutto, la risposta predefinita a un codice di errore è ignorare la condizione di errore e proseguire indipendentemente; in secondo luogo, si può finire troppo facilmente con alcuni metodi che usano codici di ritorno e altri metodi che usano eccezioni e dimenticare quale è quale. Ho anche visto basi di codice in cui due diverse implementazioni intercambiabili della stessa interfaccia adottano approcci diversi qui.


2

Le eccezioni dovrebbero rappresentare condizioni che è probabile che il codice di chiamata immediata non sarà preparato da gestire, anche se il metodo di chiamata potrebbe. Si consideri, ad esempio, il codice che legge alcuni dati da un file, può legittimamente supporre che qualsiasi file valido finisca con un record valido e non è necessario estrarre alcuna informazione da un record parziale.

Se la routine di lettura dei dati non utilizzava eccezioni ma indicava semplicemente se la lettura aveva avuto esito positivo, il codice chiamante doveva apparire come:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

ecc. spendendo tre righe di codice per ogni utile lavoro. Al contrario, se readIntegerWill genera un'eccezione quando si incontra la fine di un file e se il chiamante può semplicemente passare l'eccezione, il codice diventa:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Aspetto molto più semplice e pulito, con enfasi molto maggiore sul caso in cui le cose funzionano normalmente. Si noti che nei casi in cui il chiamante immediato si aspetterebbe di gestire una condizione, un metodo che restituisce un codice di errore sarà spesso più utile di uno che genera un'eccezione. Ad esempio, per sommare tutti i numeri interi in un file:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

contro

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

Il codice che richiede i numeri interi si aspetta che una di quelle chiamate fallisca. Fare in modo che il codice utilizzi un ciclo infinito che verrà eseguito fino a quando ciò accade è molto meno elegante rispetto all'utilizzo di un metodo che indica gli errori tramite il suo valore di ritorno.

Poiché le classi spesso non sanno quali condizioni si aspettano o non si aspettano i loro clienti, è spesso utile offrire due versioni di metodi che potrebbero non riuscire in modi che alcuni chiamanti si aspettano e altri che non lo chiamano. Ciò consentirà di utilizzare in modo pulito tali metodi con entrambi i tipi di chiamanti. Si noti inoltre che anche i metodi "provare" dovrebbero generare eccezioni se si verificano situazioni che il chiamante probabilmente non si aspetta. Ad esempio, tryReadIntegernon dovrebbe generare un'eccezione se si verifica una condizione di fine file pulita (se il chiamante non se lo aspettava, il chiamante avrebbe usatoreadInteger). D'altra parte, probabilmente dovrebbe generare un'eccezione se i dati non possono essere letti perché, ad esempio, la memory stick che li contiene è stata scollegata. Mentre tali eventi dovrebbero essere sempre riconosciuti come una possibilità, è improbabile che il codice di chiamata immediata sia pronto a fare qualsiasi cosa utile in risposta; non dovrebbe certamente essere riportato allo stesso modo di una condizione di fine file.


2

La cosa più importante nella scrittura di software è renderlo leggibile. Tutte le altre considerazioni sono secondarie, incluso renderlo efficiente e renderlo corretto. Se è leggibile, il resto può essere curato durante la manutenzione e, se non è leggibile, è meglio buttarlo via. Pertanto, dovresti generare eccezioni quando migliora la leggibilità.

Quando scrivi un algoritmo, pensa solo alla persona in futuro che lo leggerà. Quando vieni in un posto in cui potrebbe esserci un potenziale problema, chiediti se il lettore vuole vedere come gestisci quel problema ora , o preferirebbe andare avanti con l'algoritmo?

Mi piace pensare a una ricetta per la torta al cioccolato. Quando ti dice di aggiungere le uova, ha una scelta: può presumere che tu abbia le uova e andare avanti con la ricetta, oppure può iniziare una spiegazione su come puoi ottenere le uova se non le hai. Potrebbe riempire un intero libro di tecniche per cacciare polli selvatici, tutto per aiutarti a cuocere una torta. Va bene, ma la maggior parte delle persone non vorrà leggere quella ricetta. La maggior parte delle persone preferirebbe solo supporre che le uova siano disponibili e andare avanti con la ricetta. È un giudizio che gli autori devono fare quando scrivono le ricette.

Non ci possono essere regole garantite su ciò che fa una buona eccezione e quali problemi dovrebbero essere gestiti immediatamente, perché richiede di leggere la mente del tuo lettore. La migliore che tu abbia mai fatto è la regola empirica, e "le eccezioni sono solo per circostanze eccezionali" è piuttosto buona. Di solito, quando un lettore legge il tuo metodo, è alla ricerca di ciò che il metodo farà il 99% delle volte e preferirebbe non avere quello ingombro di bizzarri casi angolari come trattare utenti che immettono input illegali e altre cose che quasi mai accadono. Vogliono vedere direttamente il normale flusso del tuo software, un'istruzione dopo l'altra come se i problemi non si verificassero mai.


2

Potrebbe essere necessario segnalare ogni errore che possiamo trovare nell'input, non solo il primo.

Questo è il motivo per cui non è possibile generare un'eccezione qui. Un'eccezione interrompe immediatamente il processo di convalida. Quindi ci sarebbe molto da aggirare per farlo.

Un cattivo esempio:

Metodo di convalida per la Dogclasse utilizzando le eccezioni:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Come chiamarlo:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Il problema qui è che il processo di validazione, per ottenere tutti gli errori, richiederebbe di saltare le eccezioni già trovate. Quanto sopra potrebbe funzionare, ma si tratta di un chiaro uso improprio delle eccezioni . Il tipo di convalida richiesto deve essere eseguito prima che venga toccato il database. Quindi non è necessario ripristinare nulla. E, i risultati della convalida probabilmente saranno errori di convalida (si spera zero, però).

L'approccio migliore è:

Chiamata di metodo:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Metodo di convalida:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Perché? Ci sono tonnellate di ragioni e la maggior parte delle ragioni sono state evidenziate nelle altre risposte. Per dirla in parole semplici: è molto più semplice da leggere e comprendere dagli altri. In secondo luogo, vuoi mostrare le tracce dello stack dell'utente per spiegargli che ha impostato la sua in modo dogerrato?

Se , durante il commit nel secondo esempio, si verifica ancora un errore , anche se il tuo validatore ha convalidato il dogproblema con zero, allora lanciare un'eccezione è la cosa giusta . Come: nessuna connessione al database, la voce del database è stata modificata da qualcun altro nel frattempo, o simili.

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.