Devo sempre restituire un codice di errore dalle funzioni C?

6

Ho il seguente codice in molti posti in una grande applicazione:

if (datastruct.data_ok &&
    cur_time > datastruct.start_time && cur_time < datastruct.end_time)
{
  do_something();
}

Ora volevo eliminare il codice ripetuto definendo una funzione time_ok (...) che semplificherebbe il codice ripetuto a:

if (time_ok(&datastruct, cur_time))
{
  do_something();
}

A mio parere, si tratta di un refactoring ben fatto e dovrebbe quindi essere fatto. Se la definizione della condizione cambia in futuro, la manutenzione del codice diventa più semplice poiché è necessario modificare solo una posizione.

Tuttavia, diversi membri del team hanno notato che è possibile abusare della funzione assegnando un argomento NULL, che arresta il programma. Si sono anche lamentati del fatto che il valore restituito è un booleano e non un codice di errore e tutte le funzioni dovrebbero restituire un codice di errore.

Quindi, il refactoring sarebbe secondo loro essere fatto nel modo seguente:

bool time_ok_condition;
int err;
err = time_ok(&datastruct, &time_ok_condition, cur_time);
if (err != 0)
{
  fprintf(stderr, "Error %d at file %s line %d", err, __FILE__, __LINE__);
  exit(1);
}
if (time_ok_condition)
{
  do_something();
}

Tuttavia, a mio parere, ciò si traduce in un codice eccessivamente lungo e se io davvero devo fare tutto questo, preferirei avere il codice originale non refactored.

Ho inventato un modo per aggirare questa regola usando una macro:

if (TIME_OK(datastruct, cur_time))
{
  do_something();
}

... che non può mai mandare in crash il programma su argomenti NULL dato che la macro assume il nome della struttura e non un puntatore alla struttura come argomento. Tuttavia, non mi piace particolarmente questo approccio all'utilizzo di una macro perché la macro valuta i suoi argomenti più volte.

Ho notato che sebbene C sia stato sviluppato per le chiamate di sistema Unix e Unix tradizionalmente restituiscono i codici di errore, le funzioni standard della libreria C come strstr, strchr, strspn non restituiscono un codice di errore e quindi gestiscono argomenti NULL arrestando il programma.

Ora, è una domanda completamente diversa se il programma debba arrestarsi in modo anomalo con Coredump o stampare un messaggio di errore significativo per l'errore teorico che non si verifica mai in questo programma (vedere la domanda Come dovrebbe" questo non può mai accadere "lo stile degli errori in C viene gestito? on Overflow dello stack).

Tuttavia, questa domanda riguarda la possibilità di restituire qualcosa di diverso da un codice di errore. In questo caso molto specifico, una risposta positiva richiederebbe l'uso della strategia "crash with coredump" poiché l'intervallo di un ritorno booleano non può rappresentare errori, ma non significa necessariamente che una strategia di "crash con coredump" sia ottimale in tutti i casi .

Chi è corretto? Le funzioni C dovrebbero sempre restituire un codice di errore come le chiamate di sistema Unix, o dovrebbe essere consentito scrivere funzioni che restituiscono qualcos'altro se c'è una ragione giustificata come la semplicità del codice?

    
posta juhist 17.03.2015 - 10:34
fonte

2 risposte

4

Il requisito per un codice di errore deriva dal fatto che la nuova funzione ora può avere 3 risultati anziché 2: la struttura temporale è ok, non è ok, o l'argomento passato è NULL. Il contratto che hai in mente per la tua funzione è "non passare un valore NULL, altrimenti il programma si bloccherà o mostrerà un comportamento indefinito", mentre i tuoi colleghi credono che i programmi diventino più sicuri senza tali funzioni.

Secondo a questo precedente post PSE , e la risposta più alta, il punto di vista dei tuoi colleghi è estremamente pedante, e nel contesto che hai in mente la funzione verrà chiamata con qualcosa di estremamente improbabile che sia NULL. Quindi probabilmente sarà sicuro (forse più sicuro!) Non gestire NULL con il ritorno di un codice di errore (finché il programma si bloccherà o uscirà immediatamente con un messaggio di errore, quindi non nasconderà alcun errore grave).

Ovviamente, quando la tua funzione "time_ok" diventerà parte di una libreria, che potrebbe essere riutilizzata in molti contesti diversi (e forse in un contesto in cui chiamare exit non è una buona opzione), allora è davvero meglio restituire un codice di errore per l'argomento NULL per consentire al chiamante di decidere come gestire il problema.

    
risposta data 17.03.2015 - 12:08
fonte
1

Tutte le funzioni dovrebbero restituire un codice di errore?

Non riesco a vedere una buona ragione per cambiare una funzione come

int Add( int lhs, int rhs ) {...}

a

// Out parameter: result
error_t Add( int lhs, int rhs, int * result ) {...}

specialmente ora che stai affrontando la possibilità che il risultato sia nullo.

Nel tuo esempio specifico, userei qualcosa di simile al primo modulo senza il puntatore, lasciando al compilatore la decisione tra inlining per salvare la copia rispetto all'aumento della dimensione del codice (per qualcosa di così piccolo mi aspetto che scelga sempre inlining, ma è in una posizione migliore per giudicare)

bool time_ok( data_t datastruct, time_t cur_time ) {
    return datastruct.data_ok && 
           ( cur_time > datastruct.start_time ) && 
           ( cur_time < datastruct.end_time )
}

dove data_t e time_t sono i tipi di datastruct e cur_time

Questo dipende dal fatto che time_ok è una Funzione Totale (cioè non un Partial Function ), equivalentemente che per tutto data_t e per tutto time_t esiste un bool definito che può essere restituito da time_ok. Questo è più facile da dimostrare in casi come questo, dove l'unica cosa che si verifica è che i valori vengono confrontati e combinati.

    
risposta data 09.10.2015 - 03:41
fonte

Leggi altre domande sui tag