2015-03-17 6 views
8

Sto sviluppando un progetto di grandi dimensioni in C con altri membri del team e abbiamo un disaccordo su come lo stile di errori "questo non può mai accadere" dovrebbe essere gestito. Con questo intendo casi di errore che il codice attualmente non può mai raggiungere, ma qualcuno può modificare il codice e dopo la modifica è possibile che venga raggiunto il caso di errore.Come deve essere gestito lo stile di errori "questo mai accaduto" in C?

Ad esempio, supponiamo di avere una funzione che restituisce 0 in caso di successo o -EINVAL se ha un argomento NULL. Quello che faccio è naturalmente:

int err; 
struct myctx ctx; 
err = callFunction(&ctx); 
if (err != 0) 
{ 
    abort(); 
} 

o anche:

int err; 
struct myctx ctx; 
err = callFunction(&ctx); 
if (err == -EINVAL) 
{ 
    abort(); 
} 
else if (err != 0) 
{ 
    abort(); 
} 

... a distinguere tra i due casi di -EINVAL e il codice di errore sconosciuto.

I vantaggi di questo approccio di gestione degli errori sono che è semplice programmare (poche righe di codice) e lascia un file principale che può essere utilizzato per eseguire rapidamente il debug della situazione.

Tuttavia, alcuni membri del team non sono d'accordo con questo approccio di gestione degli errori perché sto facendo un'uscita non pulita e mentre il codice attualmente non può mai raggiungere la riga abort() è possibile che in futuro qualcuno modifichi il codice e raggiunga la linea abort() quindi. Secondo loro, dovrei provare a fare un'uscita controllata e stampare il motivo per l'utente. Quindi, credo che invece di questo devo fare:

int err; 
struct myctx ctx; 
err = callFunction(&ctx); 
if (err == -EINVAL) 
{ 
    fprintf(stderr, "Argument was NULL in file %s line %d\n", __FILE__, __LINE__); 
    exit(1); 
} 
else if (err != 0) 
{ 
    fprintf(stderr, "Error was %d in file %s line %d\n", err, __FILE__, __LINE__); 
    exit(1); 
} 

A mio parere, questa seconda strategia di gestione degli errori è peggio, perché non lascia alle spalle un componente del sistema che può essere utilizzato per eseguire il debug di questa situazione. Inoltre, risulta in codice che ha più linee di codice.

Quindi, la domanda è: come deve essere gestito lo stile di errori "questo non può mai accadere in C"? Devo fare una interruzione o un'uscita controllata con un messaggio di errore descrittivo?

+1

Mentre mi piace la domanda, sembra essere piuttosto basata. – martin

+1

Quest'ultimo è migliore - non si ottiene un core dump ma si ottiene una traccia che dice esattamente dove si è verificato il problema, e l'utente ottiene un'esperienza (leggermente) migliore (è possibile migliorare il messaggio di errore, ma un core dump non appare mai Potresti scrivere il core dump anche usando abort invece di exit, il che sarebbe meglio a tutto tondo, oppure potresti utilizzare la lib di coredumper di google per scrivere il dump e poi uscire (anche se, perché non interrompere in questo caso - dead è morto se si esce o si interrompe) – gbjbaanb

+0

Grazie! Non sapevo che esiste una libreria di coredumper che consente di generare un core dump da un'applicazione in esecuzione. Ero consapevole del gcore, però, ma è un comando separato e non una libreria che l'applicazione potrebbe utilizzare per generare un coredump di se stesso – juhist

risposta

4

In linea di principio, è necessario gestire sempre gli errori e generare la descrizione più esplicativa possibile per l'utente. Questa è la cosa giusta da fare, quello che chiamerebbero honte in Giappone.

Tuttavia, in pratica, il tuo inspiegabile abort() nel primo esempio è ciò che la maggior parte di tutti fa perché è più veloce da scrivere. La conseguenza di ciò è che la maggior parte delle applicazioni, quando falliscono, escono miseramente. Ad esempio, quando Microsoft Word si arresta in modo anomalo, il messaggio all'utente è "Word ha riscontrato un problema e ora uscirà". o qualcosa del genere. Molte applicazioni non danno alcun messaggio ma semplicemente terminano.

La corretta programmazione consiste nel codificare completamente il percorso negativo . Non solo ogni errore possibile genera un messaggio di testo significativo, ma quando l'errore ritorna e percola all'indietro attraverso la catena di chiamate, ogni funzione di chiamata dovrebbe aggiungere il suo contesto e tutti i parametri rilevanti nella parte anteriore del messaggio. Così il messaggio di fine per l'utente dovrebbe essere qualcosa di simile:

Durante il tentativo di inizializzare: durante il tentativo di caricare la configurazione file alla [c: \ user \ Data \ configurazione.ini]: Impossibile leggere linea 453: 'NumRedirects' valore parametro non valido (fuori limite): -43

In questo messaggio di errore ciò che vediamo è ogni funzione in quanto è ricevere l'errore inserisce il suo contesto in l'inizio di una stringa seguita da due punti e quindi restituisce l'errore alla funzione che lo ha chiamato. Il risultato netto mostra tutte le informazioni importanti (il file letto, la riga che è stata analizzata, il parametro che è stato caricato, il valore non valido fornito per il parametro). Questo spiega completamente lo all'utente (o sviluppatore) quale fosse il problema.

2

Penso che questo sia ciò che le affermazioni servono. Il valore restituito viene verificato a meno che il codice non sia stato compilato con NDEBUG definito.

#include <assert.h> 
... 
int err; 
struct myctx ctx; 
err = callFunction(&ctx); 
assert(err != -EINVAL); 
assert(err == 0); 

L'idea è che i bug di solito portano alla inaspettato. Inoltre, affermare affermazioni aumentare la leggibilità del codice. Quando l'asserzione fallisce, il messaggio di errore automatico aiuta il programmatore a identificare l'asserzione fallita. La versione di rilascio può essere costruita con -DNDEBUG per ridurre il numero di filiali.

Problemi correlati