se / else istruzioni o eccezioni [duplicato]

23

Non lo so, che questa domanda si adatta meglio su questo sito, o Stack Overflow, ma perché la mia domanda è connessa piuttosto alle pratiche, che qualche problema specifico.

Quindi, considera un oggetto che fa qualcosa. E questo qualcosa può (ma non dovrebbe!) Può andare storto. Quindi, questa situazione può essere risolta in due modi:

prima, con eccezioni:

DoSomethingClass exampleObject = new DoSomethingClass();
try
{
     exampleObject.DoSomething();
}
catch (ThisCanGoWrongException ex)
{
     [...]
}

E secondo, con if statement:

DoSomethingClass exampleObject = new DoSomethingClass();
if(!exampleObject.DoSomething())
{
     [...]
}

Secondo caso in modo più sofisticato:

DoSomethingClass exampleObject = new DoSomethingClass();
ErrorHandler error = exampleObject.DoSomething();
if (error.HasError)
{
     if(error.ErrorType == ErrorType.DivideByPotato)
     {
         [...]
     }
}

quale via è meglio? Da un lato, ho sentito che le eccezioni dovrebbero essere utilizzate solo per situazioni reali e inaspettate, e se il programmatore sa che qualcosa potrebbe accadere, dovrebbero usare if / else. D'altra parte, Robert C. Martin nel suo libro Clean Code ha scritto che le eccezioni sono molto più orientate agli oggetti, e più semplice da mantenere pulito.

    
posta Thaven 26.03.2012 - 14:26
fonte

9 risposte

33

In genere, tendo ad usare eccezioni per casi eccezionali - l'istanza in cui nulla dovrebbe andare storto, e se va male, è un grosso problema. Un esempio potrebbe essere un file di configurazione mancante fornito con il software o un driver hardware mancante. Se succedono queste cose, è un grosso problema. Eseguirò controlli su cose come input dell'utente o istanze opzionali in cui ci sono buone probabilità che mi vengano fornite informazioni non valide.

Tuttavia, va notato che alcune lingue sembrano favorire le eccezioni. Ad esempio, ho notato che l'uso di eccezioni anche per problemi minori è molto più comune in Python. Nella comunità Python, questo è noto come EAFP - "Più facile chiedere perdono che permesso" .

    
risposta data 26.03.2012 - 14:40
fonte
38

Il modo usuale di gestione degli errori è questo:

  • se è possibile risolvere il problema localmente, quindi utilizzare if if (o se la funzione genera un'eccezione, gestire tale eccezione)
  • se non riesci a risolvere il problema localmente, quindi lancia un'eccezione (o se la funzione genera un'eccezione, lasciala propagare ulteriormente) e gestila dove sai cosa fare al riguardo
risposta data 26.03.2012 - 14:56
fonte
9

Le eccezioni gestiscono casi eccezionali; cioè, casi che non rientrano nel "percorso felice" della normale esecuzione del programma. Dato questo avvertimento molto importante, è generalmente accettabile, e di fatto previsto, utilizzare try / catch nei casi in cui (a) qualcosa può fallire e (b) sai cosa fare quando lo fa.

La situazione primaria in cui non useresti try-catch è quando conoscere il successo o l'insuccesso di un metodo è almeno tanto importante quanto il risultato effettivo dell'operazione. Un esempio è l'analisi delle stringhe; molte volte, è utile almeno sapere se una stringa è costituita da un numero o meno, rispetto a ciò che è effettivamente il numero. .NET fornisce quindi i sapori "TryParse" delle funzioni di analisi incorporate nei tipi primitivi. Ti permettono di strutturare il tuo codice con una semplice decisione all'inizio di una fork if / else, che poi determina l'entrata in uno o l'altro dei blocchi di codice.

Ad esempio, il successo di DoSomething () determina se stampare "Sì" o "No" nella console. Entrambe sono cose "normali" da fare per il programma. Potresti scrivere in questo modo:

try
{
   DoSomething();
   Console.WriteLine("Yes");
}
catch(Exception)
{
   Console.WriteLine("No");
}

... ma ci sono un paio di problemi; primo, dal punto di vista del design, non è ovvio senza commenti che DoSomething () sia una decisione presa. In secondo luogo, DoSomething () potrebbe non essere l'unica cosa nel blocco try che potrebbe generare un'eccezione. Supponiamo che un altro sviluppatore abbia aggiunto del codice aggiuntivo che è necessario eseguire quando DoSomething ha esito positivo:

try
{
   DoSomething();
   Console.WriteLine("Yes");
   DoSomethingElse();
}
catch(Exception)
{
   Console.WriteLine("No");
}

Ora, hai uno scenario in cui DoSomethingElse () potrebbe fallire dopo che DoSomething () ha avuto successo, e il risultato per l'utente finale da un input sarebbe molto confuso:

Yes
No

Invece, considera questo:

if(TryDoSomething()) //a "non-throwing" variant that returns success or failure
{
   Console.WriteLine("Yes");
   DoSomethingElse();
}
else
{
   Console.WriteLine("No");
}

Il concetto che DoSomething è il criterio di una decisione tra due percorsi di esecuzione "normali" è molto più chiaro nel codice precedente. E il tuo flusso logico desiderato viene applicato indipendentemente da cosa; Se DoSomething () ha esito positivo, l'utente non vedrà mai "No" visualizzato sullo schermo indipendentemente da cosa faccia DoSomethingElse ().

Nella maggior parte degli altri casi, il try-catch è preferito, per uno o più dei seguenti motivi:

  • try-catch consente di utilizzare il valore di ritorno di un metodo per ciò che è stato progettato concettualmente per fare; restituisce il risultato dell'operazione, invece di un codice di stato che indica se un risultato valido è contenuto in un parametro di output o di riferimento che è il prodotto REAL del metodo.
  • try-catch consente una gestione degli errori più orientata agli oggetti, come accennato, evitando l'uso di "numeri magici"; non devi sapere che il codice di ritorno -2 di questo metodo è un errore che un argomento era nullo, ti basta prendere un ArgumentNullException. E puoi prendere la stessa ArgumentNullException generata da qualsiasi metodo che si preoccupa se uno dei suoi parametri passati è stato nullo, invece di dover sapere che questo metodo restituirà -2 e un altro metodo restituisce -3 nella stessa situazione.
  • Con questo token, try-catch aiuta a standardizzare il comportamento previsto in un particolare caso eccezionale. Se si passa un parametro nullo a un metodo che richiede che tale parametro non sia nullo, si otterrà un'eccezione ArgumentNullException, ed è di solito una cattiva pratica lanciare qualsiasi altra eccezione o assumere un caso base che equipaggerà null con un valore discreto. / li>
  • try-catch non si limita ai metodi di chiamata; puoi "provare" una possibile operazione di divisione per zero in linea nel tuo codice e catturare l'eccezione generata dal runtime nel caso si verifichi.
  • try-catch non è limitato a un metodo alla volta; puoi, con un codice aggiuntivo minimo, gestire le eccezioni da una serie di metodi diversi e interrompere l'elaborazione, senza dover esaminare il codice di ritorno più recente di ogni metodo che chiami.
risposta data 26.03.2012 - 22:45
fonte
6

Considera che, in caso di if/else , eseguirai quel controllo ogni volta, indipendentemente dal fatto che l'esecuzione abbia avuto successo o meno. Pertanto, se il caso di errore si verifica raramente o praticamente mai in circostanze normali, la gestione delle eccezioni è molto più efficiente, poiché in caso di esecuzione riuscita non si stanno valutando condizioni aggiuntive.

Un altro vantaggio delle eccezioni rispetto al controllo in caso di sistemi multithreading o distribuiti sta evitando condizioni di gara derivanti da TOCTTOU (tempo di controllo al momento dell'uso) problema . Prima si controlla la condizione, quindi si esegue il codice presumendo che la condizione sia valida. Senza garantire l'atomicità con blocchi / mutex o così, al momento dell'esecuzione della condizione del codice potrebbe essere già stato modificato da un altro thread / processo.

    
risposta data 26.03.2012 - 14:50
fonte
6

Ho usato eccezioni per la loro capacità di trasportare informazioni. In C ++, un codice di ritorno di una funzione è solitamente un singolo valore. Al contrario, puoi generare eccezioni di qualsiasi tipo di classe contenente tutte le informazioni che desideri.

Ho usato un'eccezione per restituire il pezzo specifico di una query che non è stata trovata. Sembrava il male minore di aggiungere un puntatore o un riferimento per lo più inutilizzato a ogni chiamata di funzione in uno stack di chiamate 7-deep.

    
risposta data 26.03.2012 - 19:05
fonte
3

Se exampleObject.DoSomething(); NON DOVREBBE MAI causare un problema, usa le eccezioni.

Se esistono casi aziendali validi e previsti in cui exampleObject.DoSomething() causerà un errore, utilizza if / then / else e utilizza i commenti per descrivere i casi in cui ciò potrebbe causare un problema.

Esempio:

Thing thing = findThingInDatabase();

//if a thing was found in the database (we EXPECT thing to not be found sometimes, hence if stmt as opposed to exception)
if (thing != null){
    thing.doStuff();
}
    
risposta data 26.03.2012 - 14:47
fonte
3

Un'altra cosa da considerare è che quando si verificano eccezioni c'è una penalizzazione delle prestazioni o un costo associato a tale eccezione.

Ad esempio, esegui X / Y in un ciclo.

Un modo è usare un'istruzione if. Esempio:

if (Y != 0) 
{ 
   result = X/Y;
}

Un altro modo è usare un try / catch.

try
{
  result = X/Y;
}
catch (DevideByZeroException zeroEx)
{
  //Log Error
}

Se esegui questo ciclo in un ciclo (milioni di volte) troverai che l'istruzione if viene eseguita molto più velocemente rispetto al try catch quando Y = 0.

Quindi, quando ti chiedi di scrivere il codice, colpirò questa condizione molto. In questo esempio, con quale frequenza sarà Y zero? 1% o 50% delle volte? Se il 50% usa un if. Se solo occasionalmente provi. Se ci sono 500.000 excpetions che si verificano, allora questa non è veramente un'eccezione alla regola, è la regola. Vuoi evitarlo.

Ecco perché tendo a pensare che le eccezioni riguardino casi che non accadono frequentemente. Se si hanno casi d'uso diffusi gestiti tramite logica di eccezione, è probabile che questi vengano convertiti per utilizzare le istruzioni logiche (ifs) rispetto alle eccezioni. E come bonus anche il tuo codice aumenterà di velocità.

    
risposta data 26.03.2012 - 21:23
fonte
1
X x;
try
{
     x = exampleObject.DoSomething ();
}
catch (ThisCanGoWrongException ex)

Se DoSomething restituisce qualcosa, non può nel frattempo restituire un errore, eccetto che c'è spazio nello Spazio risultato. Un'istanza ben nota di questo fenomeno è una libreria C, che legge un byte e restituisce un int. Mentre i byte sono considerati senza segno, il caso di errore è / era un -1.

Ma ora devi fare un brutto cast da fare.

Che cosa fai, se lo spazio dei risultati non ti dà spazio per i risultati, che significa "non valido"? Bene, puoi usare un Wrapper, che restituisce, invece di un T uno o [Errore, T]. Il risultato giusto di solito significa un risultato che va bene, e sinistra è lasciato a significare un tipo di errore.

Invece dell'errore come tipo speciale di valore, l'utente della tua biblioteca è - in lingue tipizzate staticamente - informato in anticipo, che c'è qualcosa che potrebbe andare storto.

Al contrario, per

  String ips = getIP ("programmers.se.com");
  // returns "err.or.in.DNS" in errorcase - but programmer 
  // forgot to check 
  for (String b : ips.split (".")) {
       Integer.parseInt (b); // crash here

A volte, i metodi vengono richiamati per il loro effetto collaterale e il valore di ritorno non viene affatto controllato. Quindi un errore può passare inosservato.

  user.setAge (-42); 

Forse la classe restituisce un "falso" o un messaggio "Età non valida, deve essere > = 0". Ma molte lingue permettono di ignorare il risultato restituito. Un'eccezione sarebbe preferibile qui.

Un problema con le eccezioni e il risultato avvolto è che a volte ingombrano il codice e rendono più difficile seguire il caso sano. Forse vedremo miglioramenti in futuro - Java sta cercando ad esempio di semplificare la gestione delle eccezioni di recente. Gli IDE potrebbero probabilmente nascondere o piegare il codice di eccezione su richiesta.

In alcuni casi, potrebbe essere ragionevole restituire un oggetto Null speciale. Pensa a cercare un utente speciale nel database. Se qualcosa va storto, puoi restituire null.

    println (null.getName ()); // crash: NPE

Una soluzione spesso vista è quella di restituire una lista con un elemento nel caso normale e valori zero nel caso del difetto.

    users = db.get ("Zappa");
    for (User u : users) {
         u.doSomething ();

Funzionerà con uno o nessun elemento e Lingue come Haskell o Scala hanno una classe di scopo speciale per questo, un Forse o un'opzione.

L'opzione è una classe parametrizzata. Un'opzione [T] può essere Some (T) o None. L'utente deve comunque controllare il risultato, ma non può dimenticarlo facilmente.

Questo è simile al risultato di una lista vuota, e potrebbe non essere appropriato ovunque, ma spesso lo è.

    
risposta data 26.03.2012 - 20:05
fonte
0

Usi le eccezioni per le situazioni eccezionali in cui, se il blocco non fosse in grado di gestire, come la connessione a un database, se il database non è disponibile, cosa farai? ti connetteresti ad un altro database? o semplicemente interrompere l'operazione? e altre situazioni come queste sono quelle in cui dovresti usare le eccezioni.

Ma dovresti sempre tenere a mente che non usare eccezioni, come avere un indice indice di errore fuori limite, allora userai un'eccezione per questo.

    
risposta data 26.03.2012 - 19:48
fonte

Leggi altre domande sui tag