Le variabili di errore sono un modello anti-pattern o un buon design?


44

Al fine di gestire diversi possibili errori che non dovrebbero interrompere l'esecuzione, ho una errorvariabile che i clienti possono controllare e utilizzare per generare eccezioni. È un anti-pattern? C'è un modo migliore per gestirlo? Per un esempio di ciò in azione puoi vedere l' API mysqli di PHP . Supponiamo che i problemi di visibilità (accessori, ambito pubblico e privato, la variabile in una classe o globale?) Siano gestiti correttamente.


6
Questo per cosa try/ catchesiste. Inoltre, puoi mettere il tuo try/ catchmolto più in alto nello stack in una posizione più appropriata per gestirlo (consentendo una maggiore separazione dei problemi).
jpmc26,

Qualcosa da tenere a mente: se si intende utilizzare la gestione basata sulle eccezioni e si ottiene un'eccezione, non si desidera mostrare troppe informazioni all'utente. Utilizzare un gestore errori come Elmah o Raygun.io per intercettarlo e mostrare all'utente un messaggio di errore generico. Non mostrare MAI una traccia dello stack o un messaggio di errore specifico all'utente, perché rivelano informazioni sul funzionamento interno dell'app, che possono essere abusate.
Nzall,

4
@Nate Il tuo consiglio è applicabile solo per applicazioni critiche per la sicurezza in cui l'utente non è completamente attendibile. I messaggi di errore vaghi sono essi stessi un anti-schema. Così sta inviando segnalazioni di errori sulla rete senza il consenso esplicito dell'utente.
piedar

3
@piedar Ho creato una domanda separata in cui questo può essere discusso più liberamente: programmers.stackexchange.com/questions/245255/…
Nzall

26
Un principio generale nella progettazione delle API che ti porta abbastanza lontano è quello di guardare sempre cosa sta facendo PHP, e quindi fare esattamente l'opposto.
Philipp,

Risposte:


65

Se una lingua supporta intrinsecamente le eccezioni, è preferibile generare eccezioni e i client possono intercettare l'eccezione se non desiderano che ciò comporti un errore. In effetti, i client del tuo codice si aspettano eccezioni e si imbatteranno in molti bug perché non controlleranno i valori di ritorno.

Ci sono molti vantaggi nell'utilizzare le eccezioni se hai una scelta.

messaggi

Le eccezioni contengono messaggi di errore leggibili dall'utente che possono essere utilizzati dagli sviluppatori per il debug o persino visualizzati dagli utenti, se desiderato. Se il codice che consuma non è in grado di gestire l'eccezione, può sempre registrarlo in modo che gli sviluppatori possano esaminare i registri senza dover interrompere qualsiasi altra traccia per capire quale fosse il valore restituito e mapparlo in una tabella per capire quale fosse il vera eccezione.

Con i valori di ritorno, non è possibile fornire facilmente ulteriori informazioni. Alcune lingue supporteranno l'esecuzione di chiamate di metodo per ottenere l'ultimo messaggio di errore, quindi questa preoccupazione è un po 'attenuata, ma ciò richiede al chiamante di effettuare chiamate extra e talvolta richiede l'accesso a un "oggetto speciale" che contiene queste informazioni.

Nel caso di messaggi di eccezione, fornisco il maggior numero possibile di contesti, come:

Non è stato possibile recuperare una politica del nome "pippo" per l'utente "bar", a cui si fa riferimento nel profilo dell'utente.

Confronta questo con un codice di ritorno -85. Quale preferiresti?

Chiama stack

Le eccezioni di solito hanno anche stack di chiamate dettagliati che aiutano a eseguire il debug del codice sempre più velocemente e possono anche essere registrati dal codice chiamante se lo si desidera. Ciò consente agli sviluppatori di individuare il problema in genere sulla riga esatta, quindi è molto potente. Ancora una volta, confrontalo con un file di registro con valori di ritorno (come -85, 101, 0, ecc.), Quale preferiresti?

Fallito approccio di parte veloce

Se un metodo viene chiamato da qualche parte che fallisce, genererà un'eccezione. Il codice chiamante deve sopprimere esplicitamente l'eccezione o fallirà. Ho scoperto che questo è davvero sorprendente perché durante lo sviluppo e i test (e persino in produzione) il codice fallisce rapidamente, costringendo gli sviluppatori a risolverlo. Nel caso di valori di ritorno, se manca un controllo per un valore di ritorno, l'errore viene silenziosamente ignorato e il bug emerge in qualche posto inaspettato, di solito con un costo molto più alto per il debug e la correzione.

Eccezioni per il wrapping e il wrapping

Le eccezioni possono essere racchiuse in altre eccezioni e quindi scartate se necessario. Ad esempio, il tuo codice potrebbe lanciare ArgumentNullExceptionquale codice chiamante potrebbe essere racchiuso in a UnableToRetrievePolicyExceptionperché l'operazione non è riuscita nel codice chiamante. Mentre all'utente potrebbe essere mostrato un messaggio simile all'esempio che ho fornito sopra, alcuni codici diagnostici potrebbero scartare l'eccezione e scoprire che si ArgumentNullExceptionera verificato il problema, il che significa che si tratta di un errore di codifica nel codice del consumatore. Ciò potrebbe quindi generare un avviso in modo che lo sviluppatore possa correggere il codice. Tali scenari avanzati non sono facili da implementare con i valori di ritorno.

Semplicità del codice

Questo è un po 'più difficile da spiegare, ma ho imparato attraverso questa codifica sia con valori di ritorno che con eccezioni. Il codice che è stato scritto utilizzando i valori di ritorno di solito effettua una chiamata e quindi presenta una serie di controlli su quale sia il valore di ritorno. In alcuni casi, chiamerebbe un altro metodo e ora avrà un'altra serie di controlli per i valori restituiti da quel metodo. Con le eccezioni, la gestione delle eccezioni è molto più semplice nella maggior parte se non in tutti i casi. Hai i blocchi try / catch / finally, con il runtime che fa del suo meglio per eseguire il codice nei blocchi finally per la pulizia. Perfino i blocchi nidificati try / catch / finally sono relativamente più facili da seguire e mantenere rispetto ai nidificati if / else e i valori di ritorno associati da più metodi.

Conclusione

Se la piattaforma che stai usando supporta le eccezioni (specialmente Java o .NET), dovresti assolutamente supporre che non ci sia altro modo se non quello di lanciare eccezioni perché queste piattaforme hanno linee guida per lanciare eccezioni, e i tuoi clienti si aspetteranno così. Se stavo usando la tua libreria, non mi prenderò la briga di controllare i valori di ritorno perché mi aspetto che vengano generate eccezioni, ecco come sta il mondo in queste piattaforme.

Tuttavia, se fosse C ++, sarebbe un po 'più difficile determinare perché esiste già una base di codice di grandi dimensioni con codici di ritorno e un gran numero di sviluppatori è ottimizzato per restituire valori al contrario di eccezioni (ad esempio Windows è pieno di HRESULT) . Inoltre, in molte applicazioni, può anche essere un problema di prestazioni (o almeno percepito).


5
Windows restituisce i valori HRESULT dalle funzioni C ++ per mantenere la compatibilità C nelle sue API pubbliche (e perché inizi a entrare in un mondo di male cercando di scongiurare eccezioni oltre i confini). Non seguire ciecamente il modello del sistema operativo se stai scrivendo un'applicazione.
Cody Grey,

2
L'unica cosa che aggiungerei a questo è la menzione di un accoppiamento più lento. Le eccezioni consentono di gestire molte situazioni impreviste nel luogo più appropriato. Ad esempio, in un'app Web, si desidera restituire un valore 500 per qualsiasi eccezione per cui il codice non è stato preparato, anziché arrestare in modo anomalo l'app Web. Quindi hai bisogno di una sorta di cattura tutto nella parte superiore del tuo codice (o nel tuo framework). Una situazione simile esiste nelle GUI desktop. Ma puoi anche inserire gestori meno generici in vari punti del codice per gestire una varietà di situazioni di errore in modo appropriato per il processo in corso.
jpmc26,

2
@TrentonMaki Se stai parlando di errori dei costruttori C ++, la risposta migliore è qui: parashift.com/c++-faq-lite/ctors-can-throw.html . In breve, lancia un'eccezione, ma ricordati di eliminare prima le potenziali perdite. Non sono a conoscenza di altre lingue in cui lanciare direttamente da un costruttore è una brutta cosa. Penso che la maggior parte degli utenti di un'API preferisca rilevare le eccezioni piuttosto che verificare i codici di errore.
Ogre Salmo33

3
"Tali scenari avanzati non sono facili da implementare con i valori di ritorno". Certo che puoi! Tutto quello che devi fare è creare una ErrorStateReturnVariablesuperclasse e una delle sue proprietà è InnerErrorState(che è un'istanza di ErrorStateReturnVariable), che l'implementazione delle sottoclassi può impostare per mostrare una catena di errori ... oh, aspetta. : p
Brian S,

5
Solo perché una lingua supporta le eccezioni non le rende una panacea. Le eccezioni introducono percorsi di esecuzione nascosti e quindi i loro effetti devono essere adeguatamente controllati; provare / catturare è facile da aggiungere, ma ottenere il giusto recupero è difficile , ...
Matthieu M.

21

Le variabili di errore sono una reliquia di linguaggi come C, dove le eccezioni non erano disponibili. Oggi, dovresti evitarli tranne quando stai scrivendo una libreria che è potenzialmente utilizzata da un programma C (o da un linguaggio simile senza gestione delle eccezioni).

Naturalmente, se hai un tipo di errore che potrebbe essere meglio classificato come "avvertimento" (= la tua libreria può fornire un risultato valido e il chiamante può ignorare l'avvertimento se ritiene che non sia importante), quindi un indicatore di stato nel modulo di una variabile può avere senso anche in lingue con eccezioni. Ma attenzione. I chiamanti della libreria tendono a ignorare tali avvisi anche se non dovrebbero. Quindi pensaci due volte prima di introdurre un tale costrutto nella tua libreria.


1
Grazie per la spiegazione! La tua risposta + Omer Iqbal ha risposto alla mia domanda.
Mikayla Maki,

Un altro modo di gestire gli "avvisi" è quello di lanciare un'eccezione per impostazione predefinita e disporre di una sorta di flag opzionale per impedire il lancio dell'eccezione.
Cyanfish,

1
@Cyanfish: sì, ma bisogna stare attenti a non sovrascrivere tali cose, specialmente quando si creano librerie. Meglio fornire un meccanismo di avvertimento semplice e funzionante di 2, 3 o più.
Doc Brown,

Un'eccezione dovrebbe essere lanciata solo quando si è verificato un errore, uno scenario sconosciuto o irrecuperabile - circostanze eccezionali . Dovresti aspettarti un impatto sulle prestazioni quando costruisci eccezioni
Gusdor,

@Gusdor: assolutamente, ecco perché un tipico "avvertimento" non dovrebbe generare un'eccezione per impostazione predefinita. Ma questo dipende anche un po 'dal livello di astrazione. A volte un servizio o una biblioteca semplicemente non possono decidere se un evento insolito debba essere trattato come un'eccezione dal punto di vista dei chiamanti. Personalmente, in tali situazioni preferisco la lib solo per impostare un indicatore di avviso (nessuna eccezione), lasciare che il chiamante testi quel flag e lanci un'eccezione se lo ritiene appropriato. Questo è quello che avevo in mente quando ho scritto sopra "è meglio fornire un meccanismo di avvertimento in una biblioteca".
Doc Brown,

20

Esistono diversi modi per segnalare un errore:

  • una variabile di errore da verificare: C , Go , ...
  • un'eccezione: Java , C # , ...
  • un gestore "condition": Lisp (solo?), ...
  • un ritorno polimorfico: Haskell , ML , Rust , ...

Il problema della variabile di errore è che è facile dimenticare di controllare.

Il problema delle eccezioni è che si creano percorsi nascosti di esecuzioni e, sebbene try / catch sia facile da scrivere, garantire un corretto recupero nella clausola catch è davvero difficile da realizzare (nessun supporto da parte di sistemi / compilatori).

Il problema dei gestori di condizioni è che non si compongono bene: se si dispone dell'esecuzione di codice dinamico (funzioni virtuali), è impossibile prevedere quali condizioni debbano essere gestite. Inoltre, se la stessa condizione può essere sollevata in più punti, non si può dire che una soluzione uniforme può essere applicata ogni volta e diventa rapidamente disordinata.

I ritorni polimorfici ( Either a bin Haskell) sono la mia soluzione preferita finora:

  • esplicito: nessun percorso nascosto di esecuzione
  • esplicito: completamente documentato nella firma del tipo di funzione (senza sorprese)
  • difficile da ignorare: devi trovare una corrispondenza del modello per ottenere il risultato desiderato e gestire il caso di errore

L'unico problema è che possono potenzialmente condurre a controlli eccessivi; le lingue che le usano hanno modi di dire per concatenare le chiamate delle funzioni che le usano, ma può ancora richiedere un po 'più di digitazione / disordine. In Haskell questo sarebbe monadi ; tuttavia, questo è molto più spaventoso di quanto sembri, vedi Programmazione orientata alle ferrovie .


1
Buona risposta! Speravo di poter ottenere un elenco di modi per gestire gli errori.
Mikayla Maki,

Arrghhhh! Quante volte ho visto questo "Il problema della variabile di errore è che è facile dimenticare di controllare". Il problema con le eccezioni è che è facile dimenticare di prenderlo. Quindi l'applicazione si arresta in modo anomalo. Il tuo capo non vuole pagare per risolverlo ma i tuoi clienti smettono di usare la tua app perché sono frustrati dagli arresti anomali. Tutto a causa di qualcosa che non avrebbe influito sull'esecuzione del programma se i codici di errore fossero stati restituiti e ignorati. L'unica volta che ho visto persone ignorare i codici di errore è quando non importava molto. Il codice più in alto nella catena sa che qualcosa è andato storto.
Dunk

1
@Dunk: non penso che la mia risposta sia neanche una scusa per le eccezioni; tuttavia, potrebbe dipendere dal tipo di codice che scrivi. La mia esperienza di lavoro personale tende a favorire i sistemi che falliscono in presenza di errori perché la corruzione silenziosa dei dati è peggiore (e non rilevata) e i dati su cui lavoro sono preziosi per il cliente (ovviamente, significano anche correzioni urgenti).
Matthieu M.

Non è una questione di corruzione silenziosa dei dati. Si tratta di comprendere la tua app e sapere quando e dove è necessario verificare se un'operazione ha avuto esito positivo o meno. In molti casi, prendere quella determinazione e gestirla può essere ritardato. Non dovrebbe essere compito di qualcun altro dirmi quando devo gestire un'operazione fallita, richiesta da un'eccezione. È la mia app, so quando e dove voglio gestire eventuali problemi rilevanti. Se le persone scrivono app in grado di corrompere i dati, è un lavoro davvero scarso. Scrivere app che si arrestano in modo anomalo (cosa che vedo molto) fa anche un lavoro scadente.
Dunk

12

Penso che sia terribile. Attualmente sto eseguendo il refactoring di un'app Java che utilizza valori di ritorno anziché eccezioni. Anche se potresti non lavorare affatto con Java, penso che ciò valga comunque.

Si finisce con un codice come questo:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

O questo:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Preferirei che le azioni generassero eccezioni, quindi si finisce con qualcosa del tipo:

x.doActionA();
x.doActionB();

Puoi racchiuderlo in un try-catch e ottenere il messaggio dall'eccezione, oppure puoi scegliere di ignorare l'eccezione, ad esempio quando stai eliminando qualcosa che potrebbe essere già scomparso. Conserva anche la traccia dello stack, se ne hai uno. Anche i metodi stessi diventano più facili. Invece di gestire le eccezioni stesse, lanciano semplicemente ciò che è andato storto.

Codice attuale (orribile):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Nuovo e migliorato:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

La traccia Strack viene preservata e il messaggio è disponibile nell'eccezione, anziché nell'inutile "Qualcosa è andato storto!".

Ovviamente puoi fornire messaggi di errore migliori e dovresti. Ma questo post è qui perché l'attuale codice a cui sto lavorando è un problema e non dovresti fare lo stesso.


1
Tuttavia, ci sono situazioni in cui il tuo "nuovo e migliorato" perderebbe il contesto in cui inizialmente si verifica l'eccezione. Ad esempio nella "versione attuale (orribile)" di doActionA (), la clausola catch avrebbe accesso alle variabili di istanza e ad altre informazioni dall'oggetto che racchiude per fornire un messaggio più utile.
Informato il

1
Questo è vero, ma al momento non accade, qui. E puoi sempre prendere l'eccezione in doActionA e racchiuderla in un'altra eccezione con un messaggio di stato. Quindi avrai ancora la traccia dello stack e il messaggio utile. throw new Exception("Something went wrong with " + instanceVar, ex);
mjjink,

Sono d'accordo, potrebbe non accadere nella tua situazione. Ma non puoi "sempre" inserire le informazioni in doActionA (). Perché? Il chiamante di doActionA () potrebbe essere l'unico a contenere le informazioni che è necessario includere.
Informato il

2
Quindi, chiedi al chiamante di includerlo quando gestisce l'eccezione. Lo stesso vale per la domanda originale, però. Non c'è niente che puoi fare ora che non puoi fare con le eccezioni e porta a un codice più pulito. Preferisco le eccezioni rispetto ai booleani restituiti o ai messaggi di errore.
mjjink,

Si presume in genere un falso presupposto che un'eccezione che si verifica all'interno di una delle "someOperation" possa essere gestita e ripulita allo stesso modo. Quello che succede nella vita reale è che devi catturare e gestire le eccezioni per ogni operazione. Quindi, non lanci solo un'eccezione come nel tuo esempio. Inoltre, l'utilizzo delle eccezioni finisce per creare un gruppo di blocchi try-catch nidificati o una serie di blocchi try-catch. Spesso rende il codice molto meno leggibile. Non sono contrario alle eccezioni, ma utilizzo lo strumento appropriato per la situazione specifica. Le eccezioni sono solo 1 strumento.
Dunk

5

"Al fine di gestire diversi possibili errori che si verificano, ciò non dovrebbe arrestare l'esecuzione",

Se intendi che gli errori non devono interrompere l'esecuzione della funzione corrente, ma dovrebbero essere segnalati in qualche modo al chiamante, allora hai alcune opzioni che non sono state menzionate. Questo caso è davvero più un avvertimento che un errore. Lanciare / restituire non è un'opzione in quanto termina la funzione corrente. Un singolo parametro di errore o return consente solo al massimo uno di questi errori.

Due modelli che ho usato sono:

  • Una raccolta di errori / avvisi, passata o mantenuta come variabile membro. Al quale aggiungi elementi e continui a elaborarli. Personalmente non mi piace molto questo approccio perché sento che non dà potere al chiamante.

  • Passare un oggetto gestore errori / avvisi (o impostarlo come variabile membro). E ogni errore chiama una funzione membro del gestore. In questo modo il chiamante può decidere cosa fare con tali errori non terminanti.

Ciò che passi a queste raccolte / gestori dovrebbe contenere un contesto sufficiente per la gestione "corretta" dell'errore - Una stringa è di solito troppo piccola, passandone qualche istanza di Eccezione è spesso sensata - ma a volte disapprovata (come un abuso di Eccezioni) .

Il codice tipico che utilizza un gestore errori potrebbe essere simile al seguente

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}

3
+1 per aver notato che l'utente desidera un avviso, piuttosto che un errore. Potrebbe valere la pena menzionare il warningspacchetto di Python , che fornisce un altro schema per questo problema.
James_pic,

Grazie per la magnifica risposta! Questo è più di quello che volevo vedere, ulteriori schemi per la gestione degli errori in cui il tradizionale tentativo / cattura potrebbe non essere sufficiente.
Mikayla Maki,

Potrebbe essere utile menzionare che un oggetto di callback dell'errore passato può generare un'eccezione quando si verificano determinati errori o avvisi - in effetti, questo potrebbe essere il comportamento predefinito - ma può anche essere utile ai mezzi con cui può chiedere la funzione chiamante per fare qualcosa. Ad esempio, un metodo di "gestione dell'errore di analisi" potrebbe fornire al chiamante un valore che l'analisi deve essere considerata come restituita.
supercat,

5

Spesso non c'è niente di sbagliato nell'usare questo modello o quel modello, purché tu usi il modello che tutti usano. Nello sviluppo di Objective-C , il modello preferito è passare un puntatore in cui il metodo chiamato può depositare un oggetto NSError. Le eccezioni sono riservate agli errori di programmazione e portano a un arresto anomalo (a meno che non si disponga di programmatori Java o .NET che scrivono la loro prima app per iPhone). E questo funziona abbastanza bene.


4

La domanda ha già una risposta, ma non posso fare a meno.

Non puoi davvero aspettarti che Eccezione fornisca una soluzione per tutti i casi d'uso. Martellare qualcuno?

Ci sono casi in cui le eccezioni non sono la fine di tutte e sono tutte, ad esempio, se un metodo riceve una richiesta ed è responsabile della convalida di tutti i campi passati, e non solo il primo, devi pensare che dovrebbe essere possibile indica la causa dell'errore per più di un campo. Dovrebbe essere possibile anche indicare se la natura della convalida impedisce all'utente di andare oltre. Un esempio potrebbe essere una password non sicura. È possibile mostrare un messaggio all'utente che indica che la password inserita non è molto forte, ma che è abbastanza forte.

Si potrebbe sostenere che tutte queste convalide potrebbero essere lanciate come un'eccezione alla fine del modulo di convalida, ma sarebbero codici di errore in tutto tranne che nel nome.

Quindi la lezione qui è: le eccezioni hanno il loro posto, così come i codici di errore. Scegli saggiamente.


Penso che questo implichi piuttosto un cattivo design. Un metodo che accetta argomenti dovrebbe essere in grado di gestirli o no - niente in mezzo. Il tuo esempio dovrebbe avere una Validator(interfaccia) iniettata nel metodo in questione (o l'oggetto dietro di esso). A seconda dell'iniezione, Validatoril metodo procederà con password errate o meno. Il codice circostante potrebbe quindi provare a WeakValidatorse l'utente lo richiedesse dopo, ad esempio, a è WeakPasswordExceptionstato lanciato dal tentativo iniziale StrongValidator.
jhr

Ah, ma non ho detto che non potrebbe essere un'interfaccia o un validatore. Non ho nemmeno menzionato JSR303. E, se leggi attentamente, mi sono assicurato di non dire una password debole, piuttosto, che non era molto forte. Una password debole sarebbe un motivo per interrompere il flusso e chiedere all'utente una password più forte.
Alexandre Santos,

E cosa faresti con una password medio-forte-ma-non-veramente debole? Interrompi il flusso e mostri all'utente un messaggio che indica che la password inserita non è molto forte. Quindi avere un MiddlyStrongValidatoro qualcosa del genere. E se ciò non ha realmente interrotto il flusso, è Validatornecessario che sia stato chiamato in anticipo, ovvero prima di procedere al flusso mentre l'utente sta ancora inserendo la propria password (o simile). Ma poi la validazione non faceva parte del metodo in questione in primo luogo. :) Probabilmente una questione di gusto, dopo tutto ...
JHR

@jhr Nei validatori che ho scritto, in genere creerò un AggregateException(o un analogo ValidationException) e inserirò eccezioni specifiche per ogni problema di validazione in InnerExceptions. Ad esempio, potrebbe essere BadPasswordException: "La password dell'utente è inferiore alla lunghezza minima di 6" oppure MandatoryFieldMissingException: "Il nome deve essere fornito per l'utente" ecc. Ciò non equivale a codici di errore. Tutti questi messaggi possono essere visualizzati all'utente in un modo che possano capire, e se NullReferenceExceptioninvece è stato lanciato un, allora abbiamo ottenuto un bug.
Omer Iqbal,

4

Ci sono casi d'uso in cui i codici di errore sono preferibili alle eccezioni.

Se il codice può continuare nonostante l'errore, ma deve essere segnalato, un'eccezione è una scelta errata perché le eccezioni interrompono il flusso. Ad esempio, se stai leggendo in un file di dati e scopri che contiene alcuni dati non terminali di dati errati, potrebbe essere meglio leggere nel resto del file e segnalare l'errore piuttosto che fallire del tutto.

Le altre risposte hanno spiegato perché le eccezioni dovrebbero essere preferite ai codici di errore in generale.


Se un avviso deve essere registrato o qualcosa del genere, così sia. Ma se un errore "ha bisogno di essere segnalato", con il quale presumo tu intenda riferire all'utente, non c'è modo di garantire che il codice circostante legga il tuo codice di ritorno e lo riporti effettivamente.
jhr

No, intendo segnalarlo al chiamante. È responsabilità del chiamante decidere se l'utente deve conoscere l'errore o meno, proprio come con un'eccezione.
Jack Aidley,

1
@jhr: quando c'è mai alcuna garanzia di qualcosa? Il contratto per una classe può specificare che i clienti hanno determinate responsabilità; se i clienti rispettano il contratto, faranno le cose richieste dal contratto. In caso contrario, eventuali conseguenze saranno dovute al codice client. Se si desidera evitare errori accidentali da parte del client e avere il controllo sul tipo restituito da un metodo di serializzazione, si potrebbe includere un flag "possibile corruzione non riconosciuta" e non consentire ai client di leggere i dati da esso senza chiamare un AcknowledgePossibleCorruptionmetodo. .
Supercat

1
... ma avere una classe di oggetti per contenere informazioni sui problemi può essere più utile che generare eccezioni o restituire un codice di errore pass-fail. Spetterebbe all'applicazione utilizzare tali informazioni in modo adeguato (ad es. Durante il caricamento del file "Foo", informare l'utente che i dati potrebbero non essere affidabili e richiedere all'utente di scegliere un nuovo nome durante il salvataggio).
supercat

1
C'è una garanzia se si usano le eccezioni: se non le prendi, si lanciano più in alto - nel peggiore dei casi fino all'interfaccia utente. Nessuna garanzia del genere se si utilizzano codici di ritorno che nessuno legge. Certo, segui l'API se vuoi usarlo. Sono d'accordo! Ma c'è spazio per l'errore, sfortunatamente ...
jhr

2

Non c'è assolutamente nulla di sbagliato nel non usare le eccezioni quando le eccezioni non sono adatte.

Quando l'esecuzione del codice non deve essere interrotta (ad es. Agendo sull'input dell'utente che può contenere più errori, come un programma da compilare o un modulo da elaborare), trovo che raccogliere errori in variabili di errore come has_errorsed error_messagessia davvero molto più elegante del lancio un'eccezione al primo errore. Permette di trovare tutti gli errori nell'input dell'utente senza forzare l'utente a reinoltrare inutilmente.


Interpretazione interessante della domanda. Penso che il problema con la mia domanda e la mia comprensione sia una terminologia poco chiara. Quello che descrivi non è eccezionale, ma è un errore. Come dovremmo chiamarlo?
Mikayla Maki,

1

In alcuni linguaggi di programmazione dinamici è possibile utilizzare entrambi i valori di errore e la gestione delle eccezioni . Questo viene fatto restituendo l' oggetto eccezione non generata al posto del normale valore restituito, che può essere verificato come un valore di errore, ma genera un'eccezione se non viene controllato.

In Perl 6 viene eseguito tramite fail, che se withing no fatal;scope restituisce uno speciale Failureoggetto eccezione non gettato .

In Perl 5 puoi usare Contextual :: Return puoi farlo con return FAIL.


-1

A meno che non ci sia qualcosa di molto specifico, penso che avere una variabile di errore per la convalida sia una cattiva idea. Lo scopo sembra riguardare il risparmio del tempo impiegato per la convalida (puoi semplicemente restituire il valore della variabile)

Ma se cambi qualcosa, devi comunque ricalcolare quel valore. Non posso dire altro sull'arresto e sul lancio delle eccezioni.

EDIT: Non mi rendevo conto che questa è una questione di paradigma software anziché un caso specifico.

Consentitemi di chiarire ulteriormente i miei punti in un mio caso particolare in cui la mia risposta avrebbe senso

  1. Ho una collezione di oggetti entità
  2. Ho servizi Web in stile procedurale che funzionano con questi oggetti entità

Esistono due tipi di errori:

  1. Gli errori durante l'elaborazione si verificano nel livello di servizio
  2. Gli errori perché ci sono incoerenze negli oggetti entità

Nel livello di servizio, non c'è altra scelta che utilizzare l'oggetto Result come wrapper che è l'equivalenza della variabile di errore. Simulare un'eccezione tramite una chiamata di servizio su protocollo come http è possibile, ma sicuramente non è una buona cosa da fare. Non sto parlando di questo tipo di errore e non pensavo che questo fosse il tipo di errore che viene posto in questa domanda.

Stavo pensando al secondo tipo di errore. E la mia risposta riguarda questo secondo tipo di errori. Negli oggetti entità, ci sono scelte per noi, alcune sono

  • usando una variabile di validazione
  • genera un'eccezione immediatamente quando un campo è impostato in modo errato dal setter

L'uso della variabile di convalida equivale a disporre di un singolo metodo di convalida per ciascun oggetto entità. In particolare, l'utente può impostare il valore in modo da mantenere il setter come setter puramente, nessun effetto collaterale (questa è spesso una buona pratica) o si può incorporare la validazione in ciascun setter e quindi salvare il risultato in una variabile di validazione. Il vantaggio sarebbe di risparmiare tempo, il risultato della convalida viene memorizzato nella variabile di convalida in modo tale che quando l'utente chiama validazione () più volte, non è necessario effettuare convalida multipla.

La cosa migliore da fare in questo caso è utilizzare un singolo metodo di convalida senza nemmeno utilizzare alcuna convalida per memorizzare l'errore di convalida. Questo aiuta a mantenere il setter come solo setter.


Capisco di cosa stai parlando. Approccio interessante Sono contento che faccia parte del set di risposte.
Mikayla Maki,
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.