Convenzioni per eccezioni o codici di errore


118

Ieri stavo avendo un acceso dibattito con un collega su quale sarebbe il metodo di segnalazione degli errori preferito. Principalmente stavamo discutendo dell'utilizzo di eccezioni o codici di errore per la segnalazione di errori tra livelli o moduli dell'applicazione.

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

Risposte:


81

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

Il comportamento predefinito di un'eccezione è di srotolare lo stack e fermare il programma, se sto scrivendo uno script e scelgo una chiave che non è in un dizionario è probabilmente un errore, e voglio che il programma si fermi e mi permetta sapere tutto su questo.

Se, tuttavia, sto scrivendo un pezzo di codice di cui devo conoscere il comportamento in ogni possibile situazione, allora voglio codici di errore. Altrimenti devo conoscere ogni eccezione che può essere generata da ogni riga nella mia funzione per sapere cosa farà (leggi L'eccezione che ha messo a terra una compagnia aerea per avere un'idea di quanto sia complicato). È noioso e difficile scrivere codice che reagisca in modo appropriato a ogni situazione (comprese quelle infelici), ma questo perché scrivere codice senza errori è noioso e difficile, non perché stai passando codici di errore.

Sia Raymond Chen che Joel hanno avanzato alcuni argomenti eloquenti contro l'utilizzo delle eccezioni per tutto.


5
+1 per sottolineare che il contesto ha qualcosa a che fare con la strategia di gestione degli errori.
alx9r

2
@ Tom, Buoni punti, ma le eccezioni sono garantite. Come possiamo garantire che i codici di errore vengano rilevati e non ignorati silenziosamente a causa di errori?
Pacerier

13
Quindi il tuo unico argomento contro le eccezioni è che qualcosa di brutto può accadere quando ti dimentichi di catturare un'eccezione ma non consideri la situazione altrettanto probabile di dimenticare di controllare il valore restituito per gli errori? Per non parlare del fatto che si ottiene una traccia dello stack quando si dimentica di rilevare un'eccezione mentre non si ottiene nulla quando si dimentica di controllare un codice di errore.
Esailija

3
C ++ 17 introduce l' nodiscardattributo che darà un avviso al compilatore se il valore di ritorno di una funzione non è memorizzato. Aiuta un po 'a rilevare il controllo del codice di errore dimenticato. Esempio: godbolt.org/g/6i6E0B
Zitrax

5
Il commento di @ Esailija è esattamente il punto qui. L'argomento contro le eccezioni qui prende un'ipotetica API basata sul codice di errore in cui tutti i codici di errore sono documentati e un ipotetico programmatore che legge la documentazione, identifica correttamente tutti i casi di errore che sono logicamente possibili nella sua applicazione e scrive il codice per gestirli , quindi confronta lo scenario con un'API e un programmatore ipotetici basati su eccezioni in cui per qualche motivo uno di questi passaggi va storto ... anche se è altrettanto facile (probabilmente più facile ) ottenere tutti quei passaggi correttamente in un'API basata su eccezioni.
Mark Amery

62

Normalmente preferisco le eccezioni, perché hanno più informazioni contestuali e possono trasmettere (se usate 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é devi mantenere un catalogo con tutti i codici di errore e quindi attivare 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 di un errore o meno, è più semplice controllare (ad esempio, un codice di errore HRESULT maggiore o uguale a 0 è successo e meno di zero è un fallimento). Possono essere omessi inavvertitamente perché non esiste alcuna forzatura programmatica che lo sviluppatore controllerà per i codici di errore. D'altra parte, non puoi ignorare le eccezioni.

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


4
"i codici di errore sono più leggeri delle eccezioni" dipende da cosa misuri e come misuri. È abbastanza facile creare test che dimostrino che le API basate sulle eccezioni possono essere molto più veloci.
Mooing Duck

1
@smink, Good points, ma come affrontiamo il sovraccarico delle eccezioni? I codici di errore non sono solo leggeri, sono sostanzialmente privi di peso ; le eccezioni non sono solo di peso medio, sono oggetti pesanti contenenti informazioni sullo stack e cose varie che non usiamo comunque.
Pacerier

3
@Pacerier: stai solo prendendo in considerazione i test in cui vengono generate eccezioni. Hai ragione al 100% sul fatto che lanciare un'eccezione C ++ è significativamente più lento della restituzione di un codice di errore. Giù le mani, nessun dibattito. Dove siamo facciamo differenziamo, è l'altro 99,999% del codice. Con le eccezioni, non dobbiamo controllare il codice di errore restituito tra ogni istruzione, rendendo quel codice più veloce dell'1-50% (o meno, a seconda del compilatore). Ciò significa che il codice completo può essere più veloce o più lento, a seconda di come viene scritto il codice e della frequenza con cui vengono generate le eccezioni.
Mooing Duck

1
@ Pacerier: con questo test artificiale che ho appena scritto, il codice basato sulle eccezioni è veloce quanto i codici di errore in MSVC e Clang, sebbene non in GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (tempi in basso). Sembra che i flag GCC che ho usato abbiano generato eccezioni insolitamente lente rispetto agli altri compilatori. Sono di parte, quindi per favore critica il mio test e sentiti libero di provare un'altra variante.
Mooing Duck

4
@Mooing Duck, se hai testato contemporaneamente sia i codici di errore che le eccezioni, devi aver abilitato le eccezioni. In tal caso, i risultati suggerirebbero che l'utilizzo di codici di errore con un sovraccarico di gestione delle eccezioni non è più lento rispetto all'utilizzo delle sole eccezioni. I test del codice di errore dovrebbero essere eseguiti con le eccezioni disabilitate per ottenere risultati significativi.
Mika Haarahiltunen

24

Preferisco le eccezioni perché

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

2
Il tuo quarto punto non è giusto: uno stato di errore quando convertito in un oggetto, può anche contenere codice per verificare se il file esiste o è bloccato, ecc. È semplicemente una variazione di stackoverflow.com/a/3157182/632951
Pacerier

1
"interrompono il flusso della logica". Le eccezioni hanno più o meno gli effetti digoto
peterchaula

22

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 come affrontarlo è avere un gestore di catture vuoto (sigh).


17

Eccezioni sui codici di errore, senza dubbio. Ottieni molti degli stessi vantaggi dalle eccezioni come dai codici di errore, ma anche molto di più, senza le carenze dei codici di errore. L'unico impatto sulle eccezioni è che è leggermente più sovraccarico; ma al giorno d'oggi, quel sovraccarico dovrebbe essere considerato trascurabile per quasi tutte le applicazioni.

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

Ci sono alcuni buoni collegamenti in quelli che possono darti ulteriori letture.


16

Non mischerei mai i due modelli ... è troppo difficile convertire da uno all'altro mentre ti sposti da una parte dello stack che utilizza codici di errore, a un pezzo più alto che utilizza eccezioni.

Le eccezioni sono per "tutto ciò che arresta o inibisce il metodo o la subroutine dal fare ciò che gli hai chiesto di fare" ... NON restituire messaggi su irregolarità o circostanze insolite, o lo stato del sistema, ecc. Usa valori di ritorno o ref (o out) parametri per quello.

Le eccezioni consentono di scrivere (e utilizzare) metodi con semantica che dipendono dalla funzione del metodo, ovvero un metodo che restituisce un oggetto Employee o un List of Employees 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 quelli che devono restituire qualcos'altro da utilizzare dal codice chiamante, è necessario passare una variabile di riferimento per essere popolata con quei dati e testare il valore restituito per il codice di errore e gestirlo, su ogni chiamata di funzione o metodo.

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

Se si codifica in modo che ogni metodo esegua una e una sola cosa semplice, è necessario generare un'eccezione ogni volta che il metodo non può raggiungere l'obiettivo 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 flusso standard del percorso del codice "normale" può essere dedicato strettamente al caso in cui il metodo SIA in grado di realizzare ciò che volevi che facesse ... E poi il codice da pulire, o gestire il circostanze "eccezionali" quando accade qualcosa di brutto che impedisce il completamento del metodo con successo possono essere separate dal codice normale. Inoltre, se non puoi gestire l'eccezione nel punto in cui si è verificata e devi passarla nello stack a un'interfaccia utente (o peggio, attraverso il cavo da un componente di livello intermedio a un'interfaccia utente), quindi con il modello di eccezione,


Bella risposta! Soluzione pronta all'uso appena in tempo!
Cristian E.

11

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

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

Quindi usa l'eccezione ma usale in modo saggio. E saranno tuoi amici.

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


1
yap C lascia alcune abitudini in tutti noi;)
Jorge Ferreira

11

Potrebbero esserci alcune situazioni in cui utilizzare le eccezioni in modo pulito, chiaro e corretto è complicato, ma la maggior parte delle volte le eccezioni sono la scelta più ovvia. Il più grande vantaggio della gestione delle eccezioni rispetto ai codici di errore è che modifica il flusso di esecuzione, il che è importante per due motivi.

Quando si verifica un'eccezione, l'applicazione non segue più il suo percorso di esecuzione "normale". Il primo motivo per cui questo è così importante è che, a meno che l'autore del codice non si spinga veramente a fare il cattivo, il programma si fermerà e non continuerà a fare cose imprevedibili. Se un codice di errore non viene controllato e le azioni appropriate non vengono intraprese in risposta a un codice di errore errato, il programma continuerà a fare quello che sta facendo e chissà quale sarà il risultato di quell'azione. Ci sono molte situazioni in cui avere il programma che fa "qualsiasi cosa" potrebbe risultare molto costoso. Si consideri un programma che recuperi le informazioni sulle prestazioni per vari strumenti finanziari che un'azienda vende e fornisce tali informazioni a broker / grossisti. Se qualcosa va storto e il programma continua, potrebbe inviare dati sulle prestazioni errati a broker e grossisti. Non so di nessun altro, ma non voglio essere quello seduto in un ufficio di vicepresidente a spiegare perché il mio codice ha causato all'azienda un'ammenda di 7 cifre. La consegna di un messaggio di errore ai clienti è generalmente preferibile alla fornitura di dati errati che potrebbero sembrare "reali", e quest'ultima situazione è molto più facile da affrontare con un approccio molto meno aggressivo come i codici di errore.

Il secondo motivo per cui mi piacciono le eccezioni e la loro interruzione della normale esecuzione è che rende molto, molto più facile mantenere la logica delle "cose ​​normali che stanno accadendo" separata dalla logica del "qualcosa è 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 anche altre piccole cose sulle eccezioni che sono carine. Avere un sacco di logica condizionale per tenere traccia del fatto che uno dei metodi chiamati in una funzione abbia restituito un codice di errore e restituire quel codice di errore più in alto è un sacco di boiler plate. In effetti, è molta piastra della caldaia che può andare storta. Ho molta più fiducia nel sistema delle eccezioni della maggior parte delle lingue di quanto non abbia un ratto nido di affermazioni se-altrimenti-se-altro 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 topi.


8

Dovresti usare entrambi. L'importante è decidere quando usarli .

Ci sono alcuni scenari in cui le eccezioni sono la scelta 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 far apparire 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 inaspettate e non gestibili .

  2. Tuttavia, riguardo alla situazione 1 (in cui accade qualcosa di inaspettato e non gestibile, semplicemente non vuoi registrarlo), le eccezioni possono essere utili perché potresti aggiungere informazioni contestuali . Ad esempio, se ottengo un'eccezione SqlException nei miei helper dati di livello inferiore, voglio rilevare quell'errore nel livello basso (dove conosco il comando SQL che ha causato l'errore) in modo da poter acquisire tali informazioni e rilanciare con informazioni aggiuntive . Si prega di notare la parola magica qui: rigirare e non ingoiare . La prima regola per gestire le eccezioni: non ingoiare eccezioni . Inoltre, nota che il mio catch interno non ha bisogno di registrare nulla perché il catch esterno avrà l'intera traccia dello stack e potrebbe registrarlo.

  3. In alcune situazioni hai una sequenza di comandi e, se qualcuno di essi fallisce , dovresti pulire / smaltire le risorse (*), indipendentemente dal fatto che si tratti di una situazione irrecuperabile (che dovrebbe essere lanciata) o di una situazione recuperabile (nel qual caso puoi gestire localmente o nel codice del chiamante ma non servono eccezioni). Ovviamente è molto più facile mettere tutti quei comandi in un unico tentativo, invece di testare i codici di errore dopo ogni metodo, e pulire / eliminare nel blocco finalmente. Si prega di notare che se si desidera che l'errore si sporga (che è probabilmente quello che si desidera), non è nemmeno necessario catturarlo: è sufficiente utilizzare finalmente per ripulire / smaltire - si dovrebbe usare solo catch / retrow se lo si desidera per aggiungere informazioni contestuali (vedere punto 2).

    Un esempio potrebbe essere una sequenza di istruzioni SQL all'interno di un blocco di transazione. Di nuovo, anche questa è una situazione "non gestibile", anche se decidi di prenderla presto (trattala localmente invece di ribollire fino in cima) è ancora una situazione fatale in cui il miglior risultato è abortire tutto o almeno abortire un grande parte del processo.
    (*) Questo è come on error gotoquello che usavamo nel vecchio Visual Basic

  4. Nei costruttori puoi solo generare eccezioni.

Detto questo, in tutte le altre situazioni in cui stai restituendo alcune informazioni su cui il chiamante PUO / DOVREBBE intraprendere un'azione , l'uso dei codici di ritorno è probabilmente un'alternativa migliore. Questo include tutti gli "errori" previsti , perché probabilmente dovrebbero essere gestiti dal chiamante immediato e difficilmente avranno bisogno di essere ribolliti di troppi livelli nello stack.

Ovviamente è sempre possibile trattare gli errori attesi come eccezioni e quindi intercettare immediatamente un livello superiore, ed è anche possibile racchiudere ogni riga di codice in un try catch e intraprendere azioni per ogni possibile errore. IMO, questo è un cattivo design, non solo perché è molto più prolisso, ma soprattutto perché le possibili eccezioni che potrebbero essere lanciate non sono ovvie senza leggere il codice sorgente - e le eccezioni potrebbero essere lanciate 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 flusso , perché sarebbe difficile da capire e mantenere per gli altri. Può diventare persino difficile capire tutti i possibili flussi di codice per i test.
Anche in questo caso: per una corretta pulizia / eliminazione è possibile 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. Una cattiva gestione delle eccezioni è facile in entrambi i metodi. Ma scrivere un buon programma basato sul codice di errore è ancora molto più semplice 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 bozze di try-catch.

La seconda critica più popolare sui codici di ritorno è che "è difficile creare bolle", ma questo perché le persone non capiscono che le eccezioni sono per situazioni non recuperabili, mentre i codici di errore non lo sono.

La scelta tra eccezioni e codici di errore è un'area grigia. È anche possibile che sia necessario ottenere un codice di errore da un metodo aziendale riutilizzabile, quindi decidere di racchiuderlo in un'eccezione (possibilmente aggiungendo informazioni) e lasciarlo gonfiare. Ma è un errore di progettazione presumere che TUTTI gli errori debbano essere lanciati come eccezioni.

Riassumendo:

  • Mi piace usare le eccezioni quando ho una situazione inaspettata, in cui non c'è molto da fare e di solito vogliamo interrompere un grosso blocco di codice o anche l'intera operazione o programma. Questo è come il vecchio "in caso di errore goto".

  • Mi piace utilizzare i codici di ritorno quando ho previsto situazioni in cui il codice del chiamante può / dovrebbe intraprendere un'azione. Ciò include la maggior parte dei metodi aziendali, API, convalide e così via.

Questa differenza tra eccezioni e codici di errore è uno dei principi di progettazione del linguaggio GO, che utilizza "panico" per situazioni impreviste fatali, mentre le situazioni previste regolari vengono restituite come errori.

Tuttavia, riguardo a GO, consente anche più valori di ritorno , il che è qualcosa che aiuta molto nell'utilizzo dei codici di ritorno, poiché è possibile restituire contemporaneamente un errore e qualcos'altro. Su C # / Java possiamo ottenerlo senza parametri, tuple o (i miei preferiti) generici, che combinati con le enumerazioni possono fornire chiari codici di errore al chiamante:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.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 possibile ritorno nel mio metodo, posso anche controllare tutti i chiamanti se coprono quel nuovo valore in un'istruzione switch, ad esempio. Non puoi davvero farlo con le eccezioni. Quando utilizzi i codici di ritorno, di solito conoscerai in anticipo tutti i possibili errori e li verificherai. Con le eccezioni di solito non sai cosa potrebbe accadere. Il wrapping di enumerazioni all'interno di eccezioni (invece di Generics) è un'alternativa (purché sia ​​chiaro il tipo di eccezioni che ogni metodo genererà), ma IMO è ancora un cattivo design.


4

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

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


4

Potrei essere seduto sulla staccionata 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 hai affatto eccezioni.

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

Il codice di base su cui lavoro è principalmente C ++ e usiamo codici di errore quasi ovunque, ma c'è un modulo che solleva eccezioni per qualsiasi errore, inclusi quelli molto non eccezionali, e tutto il codice che usa quel modulo è piuttosto orribile. Ma potrebbe essere solo perché abbiamo misto eccezioni e codici di errore. Il codice che utilizza costantemente i codici di errore è molto più facile da lavorare. Se il nostro codice usasse costantemente eccezioni, forse non sarebbe così male. Mescolare i due non sembra funzionare così bene.


4

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

Tuttavia lascio eccezioni per circostanze eccezionali. Se mi aspetto che si verifichi spesso un certo errore verificherò che l'operazione vada a buon fine prima di eseguirla, oppure chiamo una versione della funzione che utilizza invece codici di errore (Mi piace TryParse())


3

Le firme del metodo dovrebbero comunicarti cosa fa il metodo. Qualcosa come il lungo errorCode = getErrorCode (); potrebbe andare bene, ma lungo errorCode = fetchRecord (); è fonte di confusione.


3

Il mio approccio è che possiamo usare entrambi, cioè codici di eccezioni ed errori allo stesso tempo.

Sono abituato a definire diversi tipi di eccezioni (es: DataValidationException o ProcessInterruptExcepion) e all'interno di ogni eccezione definisco una descrizione più dettagliata di ogni 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 le eccezioni per definire gli errori di categoria e i codici di errore per definire informazioni più dettagliate sul problema.


2
ehm ... throw new DataValidationException("The input is too small")? Uno dei vantaggi delle eccezioni è consentire informazioni dettagliate.
Eva

2

Le eccezioni sono per circostanze eccezionali , ovvero quando non fanno parte del normale flusso del codice.

È abbastanza legittimo mescolare eccezioni e 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, controllare 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 codici di errore al posto delle eccezioni, e questi sono già stati adeguatamente coperti (a parte altri ovvi vincoli come il supporto del compilatore).

Ma andando nella direzione opposta, l'utilizzo di Exceptions ti consente di creare astrazioni di livello ancora più elevato per la gestione degli errori, che possono rendere il tuo codice ancora più espressivo e naturale. Consiglio vivamente di leggere questo eccellente, ma sottovalutato, articolo dell'esperto di C ++ Andrei Alexandrescu sull'argomento di ciò che lui chiama "Enforcements": http://www.ddj.com/cpp/184403864 . Sebbene sia un articolo C ++, i principi sono generalmente applicabili e ho tradotto il concetto di applicazione in C # con successo.


2

Innanzitutto, sono d'accordo con la risposta di Tom che per le cose di alto livello usano le eccezioni e per le cose di basso livello usano i codici di errore, a patto che non sia SOA (Service Oriented Architecture).

In SOA, dove i metodi possono essere chiamati su macchine diverse, le eccezioni potrebbero non essere passate in rete, invece, utilizziamo risposte di successo / fallimento con una struttura come di seguito (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

E usa in questo modo:

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

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


1

Preferirei le eccezioni per tutti i casi di errore, tranne quando un errore è un risultato prevedibile privo di bug di una funzione che restituisce un tipo di dati primitivo. Ad esempio, la ricerca dell'indice di una sottostringa all'interno di una stringa più grande restituisce solitamente -1 se non viene trovata, invece di sollevare un'eccezione NotFoundException.

La restituzione di puntatori non validi che potrebbero essere dereferenziati (ad esempio, causando NullPointerException in Java) non è accettabile.

L'utilizzo di più codici di errore numerici diversi (-1, -2) come valori di ritorno per la stessa funzione è di solito un cattivo stile, poiché i client potrebbero eseguire un controllo "== -1" invece di "<0".

Una cosa da tenere a mente qui è l'evoluzione delle API nel tempo. Una buona API consente di modificare ed estendere il comportamento in caso di errore in diversi modi senza interrompere i client. Ad esempio, se un handle di errore client ha verificato 4 casi di errore e si aggiunge un quinto valore di errore alla funzione, il gestore client potrebbe non verificarlo e interrompersi. Se si sollevano le eccezioni, in genere sarà più facile per i client migrare a una versione più recente di una libreria.

Un'altra cosa da considerare è quando si lavora in team, dove tracciare una linea chiara affinché tutti gli sviluppatori possano prendere una tale decisione. Ad esempio "Eccezioni per materiale di alto livello, codici di errore per materiale di basso livello" è molto soggettivo.

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


1

Se lavori in un grande progetto, non puoi usare solo eccezioni o solo codici di errore. In casi diversi è necessario utilizzare approcci diversi.

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

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


0

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


0

Penso che dipenda anche dal fatto che tu abbia davvero bisogno di informazioni come la traccia dello stack dal risultato. Se sì, scegli sicuramente Eccezione che fornisce all'oggetto pieno molte informazioni sul problema. Tuttavia, se sei interessato solo al risultato e non ti interessa perché quel risultato, vai per il codice di errore.

Ad esempio, quando si elabora un file e si affronta IOException, il client potrebbe essere interessato a sapere da dove è stato attivato, all'apertura del file o all'analisi del file, ecc. Quindi è meglio restituire IOException o la sua specifica sottoclasse. Tuttavia, in uno scenario come il tuo metodo di accesso e vuoi sapere che ha avuto successo o meno, qui o restituisci solo booleano o per mostrare il messaggio corretto, restituisci il codice di errore. Qui il cliente non è interessato a sapere quale parte della logica ha causato quel codice di errore. Sa solo se le sue credenziali non sono valide o il blocco dell'account ecc.

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


0

La mia regola generale è:

  • In una funzione potrebbe comparire un solo errore: utilizzare il codice errore (come parametro della funzione)
  • Potrebbe apparire più di un errore specifico: lancia un'eccezione

-1

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


3
Ehm, no. Vedere il paradigma GetLastError () di win32. Non lo sto difendendo, sto solo facendo capire che sei sbagliato.
Tim

4
In effetti ci sono molti altri modi per farlo. Un altro modo è restituire un oggetto che contiene il codice di errore e il valore di ritorno reale. Ancora un altro modo è eseguire il passaggio di riferimento.
Pacerier
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.