Eccezioni o codici di errore

Ieri stavo vivendo un acceso dibattito con un collega su quale sarebbe il metodo di segnalazione degli errori preferito. Principalmente stavamo discutendo l’uso di eccezioni o codici di errore per segnalare errori tra livelli applicativi o moduli.

Quali regole usi per decidere se generare eccezioni o restituire codici di errore per la segnalazione degli errori?

In roba di alto livello, eccezioni; in roba di basso livello, codici di errore.

Il comportamento predefinito di un’eccezione è di scaricare lo stack e interrompere il programma, se sto scrivendo uno script e vado a cercare una chiave che non è in un dizionario è probabilmente un errore, e voglio che il programma si fermi e lasciami sapere tutto di questo

Se, tuttavia, sto scrivendo un pezzo di codice che devo conoscere il comportamento di in ogni ansible situazione, quindi voglio i codici di errore. Altrimenti, devo sapere ogni eccezione che può essere generata da ogni riga della mia funzione per sapere cosa farà (leggi l’eccezione che ha fondato una compagnia aerea per farti un’idea di quanto sia complicato). È noioso e difficile da scrivere codice che reagisce in modo appropriato ad ogni situazione (compresi quelli infelici), ma è perché scrivere codice senza errori è noioso e difficile, non perché si passino codici di errore.

Sia Raymond Chen e Joel hanno fatto alcuni argomenti eloquenti contro l’utilizzo di eccezioni per tutto.

Normalmente preferisco le eccezioni, perché hanno più informazioni contestuali e possono trasmettere (se usato correttamente) l’errore al programmatore in modo più chiaro.

D’altra parte, i codici di errore sono più leggeri delle eccezioni ma sono più difficili da mantenere. Il controllo degli errori può essere inavvertitamente omesso. I codici di errore sono più difficili da mantenere perché è necessario conservare un catalogo con tutti i codici di errore e quindi triggersre il risultato per vedere quale errore è stato generato. Gli intervalli di errore possono essere di aiuto qui, perché se l’unica cosa che ci interessa è se siamo in presenza o meno di un errore, è più semplice da controllare (ad esempio, un codice di errore HRESULT maggiore o uguale a 0 è successo e meno di zero è un fallimento). Possono inavvertitamente essere omessi perché non c’è forzatura programmatica che lo sviluppatore verifichi per i codici di errore. D’altra parte, non puoi ignorare le eccezioni.

Per riassumere preferisco le eccezioni rispetto ai codici di errore in quasi tutte le situazioni.

Preferisco le eccezioni perché

  • interrompono il stream della logica
  • traggono vantaggio dalla gerarchia di classi che offre più funzionalità / funzionalità
  • se usato correttamente può rappresentare una vasta gamma di errori (ad esempio un InvalidMethodCallException è anche una LogicException, in quanto entrambi si verificano quando c’è un bug nel codice che dovrebbe essere rilevabile prima del runtime), e
  • possono essere usati per migliorare l’errore (cioè una definizione della class FileReadException può quindi contenere codice per verificare se il file esiste, o è bloccato, ecc.)

I codici di errore possono essere ignorati (e spesso lo sono!) Dai chiamanti delle tue funzioni. Le eccezioni almeno li costringono ad affrontare l’errore in qualche modo. Anche se la loro versione di trattare con esso è di avere un gestore catch vuoto (sospiro).

Eccezioni su codici di errore, senza dubbio a riguardo. Ottenete molti degli stessi vantaggi dalle eccezioni come fate con i codici di errore, ma anche molto di più, senza i difetti dei codici di errore. Le uniche eccezioni sono che è leggermente più alto; ma in questo giorno ed età, quel sovraccarico dovrebbe essere considerato trascurabile per quasi tutte le applicazioni.

Ecco alcuni articoli che discutono, confrontano e confrontano le due tecniche:

  • Gestione delle eccezioni orientata agli oggetti in Perl
  • Eccezioni vs. ritorni di stato

Ci sono alcuni buoni collegamenti in quelli che possono darti ulteriore lettura.

Non mischiare mai i due modelli … è troppo difficile convertirli da uno all’altro mentre ci si sposta da una parte della pila che utilizza i codici di errore, a un pezzo più alto che utilizza le eccezioni.

Le eccezioni sono per “qualsiasi cosa che interrompa o impedisca al metodo o alla subroutine di fare ciò che gli è stato chiesto di fare” … NON restituire messaggi relativi a irregolarità o circostanze insolite, o allo stato del sistema, ecc. Usa valori di ritorno o ref (o fuori) i parametri per quello.

Le eccezioni permettono ai metodi di essere scritti (e utilizzati) con la semantica che dipende dalla funzione del metodo, cioè un metodo che restituisce un object Employee o Elenco di dipendenti può essere digitato per fare proprio questo, e puoi utilizzarlo chiamando.

 Employee EmpOfMonth = GetEmployeeOfTheMonth(); 

Con i codici di errore, tutti i metodi restituiscono un codice di errore, quindi, per coloro che devono restituire qualcos’altro da utilizzare dal codice chiamante, è necessario passare una variabile di riferimento da compilare con tali dati e verificare il valore restituito per il codice di errore, e gestirlo, su ogni funzione o chiamata di metodo.

 Employee EmpOfMonth; if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR) // code to Handle the error here 

Se si codifica in modo che ogni metodo esegua una sola e semplice cosa, si dovrebbe generare un’eccezione ogni volta che il metodo non riesce a raggiungere l’objective desiderato del metodo. Le eccezioni sono molto più ricche e più facili da usare in questo modo rispetto ai codici di errore. Il tuo codice è molto più pulito – Il stream standard del percorso di codice “normale” può essere dedicato rigorosamente al caso in cui il metodo È in grado di realizzare ciò che volevi che facesse … E poi il codice per ripulire, o gestire il circostanze “eccezionali” quando accade qualcosa di brutto che impedisce al metodo di completare con successo può essere distriggersto dal normale codice. Inoltre, se non è ansible gestire l’eccezione in cui si è verificato e deve passare lo stack a un’interfaccia utente (o, peggio, attraverso il filo da un componente di livello medio a un’interfaccia utente), quindi con il modello di eccezione, si non è necessario codificare tutti i metodi intermedi nello stack per riconoscere e passare l’eccezione sullo stack … Il modello di eccezione lo fa automaticamente per te …. Con i codici di errore, questo pezzo del puzzle può diventare molto rapidamente molto oneroso .

Ci possono essere alcune situazioni in cui l’utilizzo di eccezioni in modo pulito, chiaro e corretto è complicato, ma la stragrande maggioranza delle eccezioni di tempo è la scelta più ovvia. La maggiore gestione delle eccezioni dei benefici rispetto ai codici di errore è che modifica il stream di esecuzione, il che è importante per due motivi.

Quando si verifica un’eccezione, l’applicazione non segue più il suo percorso di esecuzione “normale”. La prima ragione per cui questo è così importante è che, a meno che l’autore del codice non vada per il verso giusto e sia veramente cattivo, il programma si fermerà e non continuerà a fare cose imprevedibili. Se un codice di errore non viene controllato e non vengono intraprese azioni appropriate in risposta a un codice di errore errato, il programma continuerà a fare ciò che sta facendo e chissà quale sarà il risultato di tale azione. Ci sono molte situazioni in cui avere il programma ‘qualunque cosa’ potrebbe finire per essere molto costoso. Considera un programma che recuperi le informazioni sulle prestazioni per vari strumenti finanziari venduti da un’azienda e fornisce tali informazioni a broker / grossisti. Se qualcosa va storto e il programma continua, potrebbe inviare dati sulle prestazioni errate ai broker e ai grossisti. Non conosco nessun altro, ma non voglio essere l’unico seduto in un ufficio VP a spiegare perché il mio codice ha portato la società a ottenere 7 cifre di multe regolamentari. Fornire un messaggio di errore ai clienti è generalmente preferibile per fornire dati errati che potrebbero sembrare “reali”, e quest’ultima situazione è molto più facile imbattersi in un approccio molto meno aggressivo come i codici di errore.

La seconda ragione per cui mi piacciono le eccezioni e la loro interruzione della normale esecuzione è che rende molto, molto più facile mantenere la logica della “cosa normale sta accadendo” separata dalla “logica di qualcosa che è andato storto”. Per me, questo:

 try { // Normal things are happening logic catch (// A problem) { // Something went wrong logic } 

… è preferibile a questo:

 // Some normal stuff logic if (errorCode means error) { // Some stuff went wrong logic } // Some normal stuff logic if (errorCode means error) { // Some stuff went wrong logic } // Some normal stuff logic if (errorCode means error) { // Some stuff went wrong logic } 

Ci sono altre piccole cose sulle eccezioni che sono pure belle. Avere un po ‘di logica condizionale per tenere traccia di se uno qualsiasi dei metodi chiamati in una funzione ha avuto un codice di errore restituito, e restituire quel codice di errore più in alto è un sacco di piastra della caldaia. In effetti, molti piatti di caldaia possono andare male. Ho molta più fiducia nel sistema di eccezioni della maggior parte delle lingue di quanto non faccia un nido di ratti delle affermazioni if-else-if-else che Fred ha scritto “Fresh-out-of-college”, e ho molte cose migliori da fare con il mio tempo rispetto alla revisione del codice detto nido di ratto.

In passato mi sono unito al campo errorcode (ho fatto troppa programmazione in C). Ma ora ho visto la luce.

Sì, le eccezioni sono un po ‘onerose per il sistema. Ma semplificano il codice, riducendo il numero di errori (e WTF).

Quindi usa l’eccezione ma usali saggiamente. E saranno tuoi amici.

Come nota a margine. Ho imparato a documentare quale eccezione può essere generata da quale metodo. Sfortunatamente questo non è richiesto dalla maggior parte delle lingue. Ma aumenta le possibilità di gestire le giuste eccezioni al giusto livello.

Potrei essere seduto sul recinto qui, ma …

  1. Dipende dalla lingua
  2. Qualunque modello tu scelga, sii coerente su come lo usi.

In Python, l’uso delle eccezioni è una pratica standard e sono abbastanza felice di definire le mie eccezioni. In C non si hanno eccezioni.

In C ++ (almeno nell’STL), le eccezioni vengono solitamente lanciate solo per errori veramente eccezionali (non li vedo praticamente mai da soli). Non vedo alcun motivo per fare qualcosa di diverso nel mio codice. Sì, è facile ignorare i valori di ritorno, ma C ++ non ti obbliga a rilevare le eccezioni. Penso che devi solo prendere l’abitudine di farlo.

La base di codice su cui lavoro è in gran parte C ++ e utilizziamo i codici di errore quasi ovunque, ma c’è un modulo che solleva eccezioni per qualsiasi errore, compresi quelli non eccezionali, e tutto il codice che usa quel modulo è piuttosto orribile. Ma potrebbe essere solo perché abbiamo combinato eccezioni e codici di errore. Il codice che utilizza in modo coerente i codici di errore è molto più semplice da utilizzare. Se il nostro codice utilizzava sistematicamente delle eccezioni, forse non sarebbe così male. Mescolare i due non sembra funzionare così bene.

Dal momento che lavoro con C ++ e ho RAII per renderli sicuri da usare, utilizzo le eccezioni quasi esclusivamente. Estrae la gestione degli errori dal normale stream del programma e rende l’intento più chiaro.

Però lasciamo eccezioni per circostanze eccezionali. Se mi aspetto che un determinato errore si verifichi molto, controllerò che l’operazione abbia esito positivo prima di eseguirla, o chiamo invece una versione della funzione che utilizza i codici di errore (come TryParse() )

Dovresti usare entrambi. Il punto è decidere quando usare ciascuno di essi .

Esistono alcuni scenari in cui le eccezioni sono la scelta più ovvia :

  1. In alcune situazioni non puoi fare nulla con il codice di errore , e devi solo gestirlo in un livello superiore nello stack di chiamate , di solito basta registrare l’errore, mostrare qualcosa all’utente o chiudere il programma. In questi casi, i codici di errore richiederebbero di chiarire manualmente i codici di errore livello per livello, il che è ovviamente molto più facile da fare con le eccezioni. Il punto è che questo è per situazioni impreviste e non gestibili .

  2. Per quanto riguarda la situazione 1 (dove accade qualcosa di inaspettato e non gestibile, non è sufficiente registrarlo), le eccezioni possono essere utili perché è ansible aggiungere informazioni contestuali . Ad esempio, se ottengo una SqlException nei miei helper di dati di livello inferiore, voglio rilevare quell’errore nel livello basso (dove conosco il comando SQL che ha causato l’errore), così posso acquisire tali informazioni e ripeterle con informazioni aggiuntive . Si prega di notare la parola magica qui: ripiegare e non ingoiare . La prima regola della gestione delle eccezioni: non ingoiare le eccezioni . Inoltre, si noti che il mio fermo interno non ha bisogno di registrare nulla perché il fermo esterno avrà l’intera traccia dello stack e potrebbe registrarlo.

  3. In alcune situazioni hai una sequenza di comandi, e se qualcuno di questi fallisce dovresti pulire / disporre delle risorse (*), indipendentemente dal fatto che si tratti di una situazione irrecuperabile (che dovrebbe essere generata) o di una situazione recuperabile (nel qual caso puoi gestisci localmente o nel codice del chiamante ma non hai bisogno di eccezioni). Ovviamente è molto più facile mettere tutti quei comandi in una singola prova, invece di testare i codici di errore dopo ogni metodo, e ripulire / disporre nel blocco finally. Tieni presente che se vuoi che l’errore diventi un errore (che è probabilmente quello che vuoi), non hai nemmeno bisogno di prenderlo – basta usare finalmente per ripulire / disporre – dovresti usare solo catch / retrow se vuoi per aggiungere informazioni contestuali (vedi punto 2).

    Un esempio potrebbe essere una sequenza di istruzioni SQL all’interno di un blocco di transazione. Di nuovo, anche questa situazione “non gestibile”, anche se si decide di prenderla presto (trattarla localmente invece di gorgogliare fino in cima) è comunque una situazione fatale da cui il miglior risultato è quello di interrompere tutto o almeno interrompere una grande parte del processo.
    (*) Questo è come l’ on error goto che abbiamo usato nel vecchio Visual Basic

  4. Nei costruttori puoi solo lanciare eccezioni.

Detto questo, in tutte le altre situazioni in cui si restituiscono alcune informazioni su cui il chiamante può / DOVREBBE fare qualcosa , usare i codici di ritorno è probabilmente un’alternativa migliore. Questo include tutti gli “errori” previsti , perché probabilmente dovrebbero essere gestiti dal chiamante immediato, e difficilmente dovrà essere gorgogliato su troppi livelli in cima allo stack.

Ovviamente è sempre ansible trattare gli errori previsti come eccezioni e catturare immediatamente un livello sopra, ed è anche ansible includere ogni riga di codice in un tentativo di cattura e intraprendere azioni per ogni ansible errore. IMO, questo è un cattivo design, non solo perché è molto più dettagliato, ma specialmente perché le possibili eccezioni che potrebbero essere lanciate non sono ovvie senza leggere il codice sorgente – e le eccezioni potrebbero essere generate da qualsiasi metodo profondo, creando gotos invisibili . Rompono la struttura del codice creando più punti di uscita invisibili che rendono il codice difficile da leggere e ispezionare. In altre parole, non dovresti mai usare eccezioni come controllo del stream , perché sarebbe difficile per gli altri capire e mantenere. Può essere persino difficile capire tutti i possibili flussi di codice per i test.
Ancora: per una corretta pulizia / smaltimento è ansible utilizzare try-finally senza prendere nulla .

La critica più popolare sui codici di ritorno è che “qualcuno potrebbe ignorare i codici di errore, ma nello stesso senso qualcuno può anche ingoiare le eccezioni.” La gestione delle eccezioni è facile in entrambi i metodi, ma scrivere un buon programma basato su codice di errore è ancora più facile piuttosto che scrivere un programma basato su eccezioni e se uno per qualsiasi motivo decide di ignorare tutti gli errori (il vecchio on error resume next ), puoi farlo facilmente con i codici di ritorno e non puoi farlo senza un sacco di tentativi di cattura boilerplate.

La seconda critica più popolare sui codici di ritorno è che “è difficile farli scoppiare”, ma è perché le persone non capiscono che le eccezioni sono per situazioni non recuperabili, mentre i codici di errore non lo sono.

La decisione tra le eccezioni e i codici di errore è un’area grigia. È anche ansible che tu debba ottenere un codice di errore da un metodo di business riutilizzabile, e quindi decidi di includerlo in un’eccezione (possibilmente aggiungendo informazioni) e lasciarlo scoppiare. Ma è un errore di progettazione assumere che TUTTI gli errori dovrebbero essere generati come eccezioni.

Riassumendo:

  • Mi piace usare le eccezioni quando ho una situazione inaspettata, in cui non c’è molto da fare, e di solito vogliamo abortire un grande blocco di codice o anche l’intera operazione o programma. Questo è come il vecchio “su errore goto”.

  • Mi piace usare i codici di ritorno quando mi aspetto delle situazioni in cui il codice del chiamante può / dovrebbe agire. Ciò include la maggior parte dei metodi di business, API, convalide e così via.

Questa differenza tra eccezioni e codici di errore è uno dei principi di progettazione del linguaggio GO, che utilizza il “panico” per situazioni inattese fatali, mentre le normali situazioni previste vengono restituite come errori.

Ancora su GO, consente anche più valori di ritorno , il che è di grande aiuto nell’utilizzo dei codici di ritorno, poiché è ansible restituire simultaneamente un errore e qualcos’altro. In C # / Java possiamo ottenere ciò senza parametri, tuple o (i miei preferiti) generici, che combinati con le enumerazioni possono fornire chiari codici di errore al chiamante:

 public MethodResult CreateOrder(CreateOrderOptions options) { .... return MethodResult.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area"); ... return MethodResult.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order); } var result = CreateOrder(options); if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK) // do something else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS) order = result.Entity; // etc... 

Se aggiungo un nuovo ansible ritorno nel mio metodo, posso anche controllare tutti i chiamanti se stanno coprendo quel nuovo valore in un’istruzione switch, ad esempio. Non puoi davvero farlo con le eccezioni. Quando si utilizzano i codici di ritorno, di solito si conoscono in anticipo tutti i possibili errori e si verifica per loro. Con le eccezioni di solito non sai cosa potrebbe accadere. L’enumerazione delle enum all’interno delle eccezioni (al posto di Generics) è un’alternativa (a patto che sia chiaro il tipo di eccezioni che ogni metodo genererà), ma IMO è ancora una ctriggers progettazione.

Le firme dei metodi dovrebbero comunicare a te come funziona il metodo. Qualcosa come long errorCode = getErrorCode (); potrebbe andare bene, ma long errorCode = fetchRecord (); è confuso.

Il mio ragionamento sarebbe se si sta scrivendo un driver di basso livello che ha davvero bisogno di prestazioni, quindi utilizzare i codici di errore. Ma se stai usando quel codice in un’applicazione di livello superiore e può gestire un po ‘di overhead, allora avvolgi quel codice con un’interfaccia che controlla quei codici di errore e solleva le eccezioni.

In tutti gli altri casi, le eccezioni sono probabilmente la strada da percorrere.

Il mio approccio è che possiamo usare entrambi i codici, ad esempio Eccezioni ed Errori allo stesso tempo.

Sono abituato a definire diversi tipi di eccezioni (es: DataValidationException o ProcessInterruptExcepion) e all’interno di ciascuna eccezione definire una descrizione più dettagliata di ciascun problema.

Un semplice esempio in Java:

 public class DataValidationException extends Exception { private DataValidation error; /** * */ DataValidationException(DataValidation dataValidation) { super(); this.error = dataValidation; } } enum DataValidation{ TOO_SMALL(1,"The input is too small"), TOO_LARGE(2,"The input is too large"); private DataValidation(int code, String input) { this.input = input; this.code = code; } private String input; private int code; } 

In questo modo utilizzo Eccezioni per definire errori di categoria e codici di errore per definire informazioni più dettagliate sul problema.

Le eccezioni sono per circostanze eccezionali , cioè quando non fanno parte del normale stream del codice.

È legittimo mescolare le eccezioni ei codici di errore, dove i codici di errore rappresentano lo stato di qualcosa, piuttosto che un errore nell’esecuzione del codice di per sé (ad esempio, controllando il codice di ritorno da un processo figlio).

Ma quando si verifica una circostanza eccezionale, credo che le eccezioni siano il modello più espressivo.

Ci sono casi in cui potresti preferire o dover utilizzare i codici di errore al posto di Eccezioni, e questi sono già stati adeguatamente trattati (oltre ad altri ovvi vincoli come il supporto del compilatore).

Ma andando nella direzione opposta, l’uso di Exceptions consente di creare astrazioni di livello ancora più elevato per la gestione degli errori, che può rendere il codice ancora più espressivo e naturale. Consiglio vivamente di leggere questo eccellente articolo, ancora sottovalutato, dall’esperto di C ++ Andrei Alexandrescu sul tema di ciò che lui chiama “Enforcements”: http://www.ddj.com/cpp/184403864 . Sebbene si tratti di un articolo del C ++, i principi sono generalmente applicabili e ho tradotto il concetto di enforcements in C # in modo abbastanza efficace.

Innanzitutto, sono d’accordo con la risposta di Tom che per roba di alto livello si utilizzano eccezioni e che per roba di basso livello si utilizzano codici di errore, purché non si tratti di Service Oriented Architecture (SOA).

In SOA, dove i metodi possono essere chiamati su macchine diverse, le eccezioni non possono essere passate sul filo, invece, usiamo le risposte di successo / fallimento con una struttura come sotto (C #):

 public class ServiceResponse { public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage); public string ErrorMessage { get; set; } } public class ServiceResponse : ServiceResponse { public TResult Result { get; set; } } 

E usare in questo modo:

 public async Task> GetUserName(Guid userId) { var response = await this.GetUser(userId); if (!response.IsSuccess) return new ServiceResponse { ErrorMessage = $"Failed to get user." }; return new ServiceResponse { Result = user.Name }; } 

Quando vengono utilizzati in modo coerente nelle risposte del servizio, crea un modello molto piacevole di gestione dei successi / errori nell’applicazione. Ciò consente una gestione più semplice degli errori nelle chiamate asincrone all’interno dei servizi e tra tutti i servizi.

Preferirei Eccezioni per tutti i casi di errore, tranne quando un errore è un risultato esente da bug prevedibile di una funzione che restituisce un tipo di dati primitivo. Ad esempio, se si trova l’indice di una sottostringa all’interno di una stringa più grande, di solito restituisce -1 se non trovato, invece di generare una NotFoundException.

Restituire i puntatori non validi che potrebbero essere dereferenziati (ad es. Causare NullPointerException in Java) non è accettabile.

L’utilizzo di più codici di errore numerici (-1, -2) come valori di ritorno per la stessa funzione è in genere di stile negativo, poiché i client potrebbero eseguire un controllo “== -1” anziché “<0".

Una cosa da tenere a mente qui è l’evoluzione delle API nel tempo. Una buona API consente di modificare ed estendere il comportamento di errore in diversi modi senza rompere i client. Ad esempio, se un handle di errore del client ha verificato 4 casi di errore e si aggiunge un quinto valore di errore alla propria funzione, il gestore client potrebbe non testarlo e interromperlo. Se si sollevano eccezioni, in genere ciò faciliterà la migrazione dei client verso una versione più recente di una libreria.

Un’altra cosa da considerare è quando si lavora in una squadra, dove tracciare una linea chiara per tutti gli sviluppatori per prendere una tale decisione. Ad esempio, “Eccezioni per roba di alto livello, codici di errore per roba di basso livello” è molto soggettiva.

In ogni caso, dove è ansible più di un semplice tipo di errore, il codice sorgente non dovrebbe mai usare il valore letterale numerico per restituire un codice di errore o per gestirlo (return -7, se x == -7 …), ma sempre una costante nominata (restituisce NO_SUCH_FOO, se x == NO_SUCH_FOO).

Se lavori con progetti di grandi dimensioni, non puoi utilizzare solo eccezioni o solo codici di errore. In diversi casi è necessario utilizzare approcci diversi.

Ad esempio, decidi di utilizzare solo le eccezioni. Ma una volta deciso di utilizzare l’elaborazione degli eventi asincroni. È una ctriggers idea utilizzare le eccezioni per la gestione degli errori in queste situazioni. Ma usare codici di errore ovunque nell’applicazione è noioso.

Quindi la mia opinione è che sia normale usare contemporaneamente sia le eccezioni che i codici di errore.

Per la maggior parte delle applicazioni, le eccezioni sono migliori. L’eccezione è quando il software deve comunicare con altri dispositivi. Il dominio su cui lavoro sono i controlli industriali. Qui i codici di errore sono preferiti e attesi. Quindi la mia risposta è che dipende dalla situazione.

Penso che dipenda anche dal fatto che tu abbia davvero bisogno di informazioni come lo stack trace dal risultato. In caso affermativo, si fa sicuramente per Exception che fornisce l’object pieno di molte informazioni sul problema. Tuttavia, se sei solo interessato al risultato e non ti interessa perché quel risultato, allora, vai per il codice di errore.

Ad esempio, quando si elabora il file e si trova IOException, il client potrebbe essere interessato a sapere da dove è stato triggersto, aprire il file o analizzare il file, ecc. Quindi è meglio restituire IOException o la sua sottoclass specifica. Tuttavia, uno scenario come te ha un metodo di login e vuoi sapere se ha avuto successo o meno, o puoi solo tornare booleano o mostrare il messaggio corretto, restituire il codice di errore. Qui il cliente non è interessato a sapere quale parte della logica ha causato quel codice di errore. Sa solo se la sua credenziale non è valida o il blocco dell’account, ecc.

Un altro caso di utilizzo a cui riesco a pensare è quando i dati viaggiano sulla rete. Il metodo remoto può restituire solo il codice di errore anziché Eccezione per ridurre al minimo il trasferimento dei dati.

La mia regola generale è:

  • In una funzione potrebbe apparire un solo errore: utilizzare il codice di errore (come parametro della funzione)
  • Potrebbe essere visualizzato più di un errore specifico: generare un’eccezione

Anche i codici di errore non funzionano quando il tuo metodo restituisce qualcosa di diverso da un valore numerico …