Per alcuni linguaggi (ad esempio C ++) la perdita di risorse non dovrebbe essere un motivo
Il C ++ è basato su RAII.
Se hai del codice che potrebbe fallire, restituire o lanciare (cioè, il codice più normale), allora dovresti avere il tuo puntatore avvolto in un puntatore intelligente (supponendo che tu abbia un'ottima ragione per non avere il tuo oggetto creato sullo stack).
I codici di ritorno sono più dettagliati
Sono prolissi e tendono a svilupparsi in qualcosa di simile:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
Alla fine, il tuo codice è una raccolta di istruzioni identificate (ho visto questo tipo di codice nel codice di produzione).
Questo codice potrebbe essere tradotto in:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Che separa in modo pulito il codice e l'elaborazione degli errori, il che può essere una buona cosa.
I codici di ritorno sono più fragili
Se non qualche oscuro avvertimento da un compilatore (vedere il commento di "phjr"), possono essere facilmente ignorati.
Con gli esempi precedenti, supponi che qualcuno dimentichi di gestire il suo possibile errore (questo accade ...). L'errore viene ignorato quando "restituito" e probabilmente esploderà in seguito (cioè un puntatore NULL). Lo stesso problema non si verificherà eccezionalmente.
L'errore non verrà ignorato. A volte, però, vuoi che non esploda ... Quindi devi scegliere con attenzione.
A volte i codici di ritorno devono essere tradotti
Supponiamo di avere le seguenti funzioni:
- doSomething, che può restituire un int chiamato NOT_FOUND_ERROR
- doSomethingElse, che può restituire un bool "false" (per non riuscito)
- doSomethingElseAgain, che può restituire un oggetto Error (con entrambe le variabili __LINE__, __FILE__ e metà dello stack.
- doTryToDoSomethingWithAllThisMess che, beh ... Usa le funzioni precedenti e restituisce un codice di errore di tipo ...
Qual è il tipo di ritorno di doTryToDoSomethingWithAllThisMess se una delle sue funzioni chiamate fallisce?
I codici di ritorno non sono una soluzione universale
Gli operatori non possono restituire un codice di errore. Anche i costruttori C ++ non possono.
Codici di ritorno significa che non puoi concatenare espressioni
Il corollario del punto precedente. E se volessi scrivere:
CMyType o = add(a, multiply(b, c)) ;
Non posso, perché il valore di ritorno è già utilizzato (e talvolta non può essere modificato). Quindi il valore restituito diventa il primo parametro, inviato come riferimento ... Oppure no.
Le eccezioni vengono digitate
Puoi inviare classi differenti per ogni tipo di eccezione. Le eccezioni sulle risorse (cioè la memoria insufficiente) dovrebbero essere leggere, ma qualsiasi altra cosa potrebbe essere pesante quanto necessario (mi piace l'eccezione Java che mi dà l'intero stack).
Ogni cattura può quindi essere specializzata.
Non usare mai la cattura (...) senza rilanciare
Di solito, non dovresti nascondere un errore. Se non rilanci, almeno, registra l'errore in un file, apri una finestra di messaggio, qualunque cosa ...
Le eccezioni sono ... NUKE
Il problema con l'eccezione è che un uso eccessivo produrrà codice pieno di tentativi / catture. Ma il problema è altrove: chi prova / cattura il suo codice usando il contenitore STL? Tuttavia, quei contenitori possono inviare un'eccezione.
Ovviamente, in C ++, non lasciare mai che un'eccezione esca da un distruttore.
Le eccezioni sono ... sincrone
Assicurati di catturarli prima che tirino fuori il tuo thread in ginocchio o si propagino all'interno del loop dei messaggi di Windows.
La soluzione potrebbe essere mescolarli?
Quindi immagino che la soluzione sia lanciare quando qualcosa non dovrebbe accadere. E quando può succedere qualcosa, utilizza un codice di ritorno o un parametro per consentire all'utente di reagire.
Quindi, l'unica domanda è "cosa è qualcosa che non dovrebbe accadere?"
Dipende dal contratto della tua funzione. Se la funzione accetta un puntatore, ma specifica che il puntatore deve essere diverso da NULL, allora va bene lanciare un'eccezione quando l'utente invia un puntatore NULL (la domanda è, in C ++, quando l'autore della funzione non usa invece riferimenti di suggerimenti, ma ...)
Un'altra soluzione sarebbe mostrare l'errore
A volte, il tuo problema è che non vuoi errori. Usare eccezioni o codici di ritorno di errore è interessante, ma ... vuoi saperlo.
Nel mio lavoro usiamo una sorta di "Assert". A seconda dei valori di un file di configurazione, indipendentemente dalle opzioni di compilazione di debug / rilascio:
- registra l'errore
- apri una casella di messaggi con un "Ehi, hai un problema"
- apri una casella di messaggi con un "Ehi, hai un problema, vuoi eseguire il debug"
Sia nello sviluppo che nel test, questo consente all'utente di individuare il problema esattamente quando viene rilevato e non dopo (quando un codice si preoccupa del valore restituito o all'interno di un catch).
È facile da aggiungere al codice legacy. Per esempio:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
conduce una sorta di codice simile a:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(Ho macro simili che sono attive solo su debug).
Nota che in produzione il file di configurazione non esiste, quindi il client non vede mai il risultato di questa macro ... Ma è facile attivarlo quando serve.
Conclusione
Quando codifichi usando i codici di ritorno, ti stai preparando al fallimento e speri che la tua fortezza dei test sia abbastanza sicura.
Quando si codifica utilizzando l'eccezione, si sa che il codice può fallire e di solito si inserisce il blocco del counterfire nella posizione strategica scelta nel codice. Ma di solito, il tuo codice riguarda più "cosa deve fare" che "ciò che temo accadrà".
Ma quando scrivi del codice, devi usare il miglior strumento a tua disposizione e, a volte, è "Non nascondere mai un errore e mostralo il prima possibile". La macro di cui ho parlato sopra segue questa filosofia.