Dovresti usare entrambi. Le affermazioni sono per la tua comodità come sviluppatore. Le eccezioni rilevano cose che ti sei perso o che non ti aspettavi durante il runtime.
Mi sono appassionato alle funzioni di segnalazione degli errori di glib invece delle semplici vecchie affermazioni. Si comportano come dichiarazioni di asserzione ma invece di interrompere il programma, restituiscono semplicemente un valore e lasciano che il programma continui. Funziona sorprendentemente bene e come bonus puoi vedere cosa succede al resto del tuo programma quando una funzione non restituisce "quello che dovrebbe". Se si blocca, sai che il tuo controllo degli errori è lento da qualche altra parte lungo la strada.
Nel mio ultimo progetto, ho utilizzato questo stile di funzioni per implementare il controllo delle precondizioni e, se una di esse falliva, stampavo una traccia dello stack nel file di registro ma continuavo a funzionare. Mi ha fatto risparmiare un sacco di tempo per il debug quando altre persone avrebbero riscontrato un problema durante l'esecuzione della mia build di debug.
#ifdef DEBUG
#define RETURN_IF_FAIL(expr) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
::print_stack_trace(2); \
return; \
}; } while(0)
#define RETURN_VAL_IF_FAIL(expr, val) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
::print_stack_trace(2); \
return val; \
}; } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif
Se avessi bisogno del controllo in runtime degli argomenti, lo farei:
char *doSomething(char *ptr)
{
RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails.
// Goes away when debug off.
if( ptr != NULL )
{
...
}
return ptr;
}