2009-07-03 13 views
6

Durante la progettazione di un'API C per la configurazione di una libreria/utilità, ho un collega che preferisce memorizzare tutti i parametri di configurazione in 1 chiamata di funzione. Per esempio:API di configurazione C

int set_diagnostic_email_config(char *to_address, 
           bool include_timestamp, 
           bool send_for_crashes, 
           bool send_daily_status, 
           bool send_on_event1, 
           bool send_on_event2) 

Un simile "ottenere" la funzione esiste con molti parametri .. Il principale vantaggio di questo metodo è che se qualcuno aggiunge una nuova opzione, ad esempio, "bool send_on_event3", poi perché il prototipo si è cambiato sono costretti ad aggiornare ogni posto in cui viene utilizzata questa chiamata di funzione (supponendo che ci siano più posti in cui la gente lo chiama).

preferisco qualcosa sulla falsariga di:

int get_diagnostic_email_config(struct email_config *p_config); 
int set_diagnostic_email_config(struct email_config *p_config); 

in cui basta cambiare gli elementi di struttura, se necessario. Ma ... se qualcuno aggiorna la struttura di email_config "non costringe le persone ad aggiornare tutti i punti in cui viene utilizzata (anche se spesso vogliamo ...). Inoltre il mio collega lamenta che se qualcuno tenta di inizializzarsi "email_config" a mano, poi se le cose si aggiunse più tardi poi quei nuovi campi verrà inizializzata senza avvisi.

c'è qualche forte consenso su cui si preferisce il metodo? o forse c'è un'altra alternativa che mi manca?

risposta

11

Una struttura è migliore di una lunga lista. La lunga lista è difficile da mantenere, poiché nessuno ricorda l'ordine esatto.

È possibile creare un costruttore che riempie questa struttura con voci sicure (predefinite e/o non valide). Sempre (probabilmente nella funzione accessor) il controllo di valori non validi renderà più facile individuare errori nell'inizializzazione.

È possibile nascondere un numero magico in questa struttura. Il primo campo è CONFIG_MAGIC che deve essere uguale a una costante definita dall'utente. Si imposta questo campo nel costruttore e si prevede di essere impostato in ogni momento. Questo evita a qualcuno solo malloc() di costruire la struttura e inizializzarla a mano. Tale programmatore dovrebbe conoscere questa costante CONFIG_MAGIC ed è più che probabile trovare e utilizzare il costruttore appropriato.

+0

+1. Ottimo consiglio – DevSolar

+1

+1 fondamentalmente è OOP - più facile da gestire le modifiche, mantenere l'implementazione con lo stato - costruttore/distruttore per inizializzare la pulizia – stefanB

+0

Grazie! Mi piace soprattutto l'idea CONFIG_MAGIC. – Will

0

Come è necessario aggiornare la chiamata di funzione in tutte le località una cosa buona? Naturalmente, è necessario ricostruire tutto con le nuove intestazioni aggiornate, ma idealmente si vorrà dover apportare il minor numero possibile di modifiche nel codice ogni ora viene modificata la configurazione

0

L'approar di "numerosi parametri" ha un grande disadvantadge che è necessario modificare un sacco di codice ogni volta che viene introdotto un nuovo parametro solo per il passaggio di tale parametro nello stack delle chiamate. Con una struttura hai solo bisogno di cambiare le posizioni che effettivamente usano i parametri. La necessità di modificare spesso tonnellate di codice può causare l'individuazione degli errori da sola e ciò supererebbe i vantaggi dei controlli in fase di compilazione di tutti i parametri presenti.

3

Vorrei usare il seguente modo che è un'estensione per la tua strada.

struct INFO 
{ 
    char *to_address; 
    bool include_timestamp; 
    bool send_for_crashes; 
    bool send_daily_status; 
    bool send_on_event1; 
    bool send_on_event2; 
}; 

struct INFO *createINFO() 
{ 
    // initialize to defaults. 

    // return a pointer to the new created struct. 
} 

void include_timestamp(struct INFO *info, bool vInclude_timestamp) 
{ 
    // set the field. 
} 

// add the required setters... 

void destroyINFO(struct INFO *info) 
{ 
    // destroy the struct. 
} 

In questo modo, è possibile aggiungere 'setter' su richiesta, quando si aggiunge un nuovo campo. Senza permettere all'utente di pasticciare con la struttura stessa.

+0

Preferirei non eseguire queste attività di gestione della memoria. –

+0

bene, sei corretto :) – AraK

0

userei

int set_diagnostic_email_config(char *to_address, 
           bool include_timestamp, 
           int event_type) { 
    switch (event_type) { 
     case 1: 
     ... 
    } 
} 

o

int set_diagnostic_email_config(char *to_address, 
           bool include_timestamp, 
           int event_type, void *details) 

Avere un event_type come un intero significa che è possibile aggiungere un nuovo evento senza cambiare la firma. Un ulteriore void* fornisce una struttura opzionale per ogni tipo di evento.

Se si utilizza struct, le modifiche potrebbero non essere sempre binarie compatibili.

3

Gli elenchi di parametri lunghi non sono leggibili, in particolare se si tratta di un elenco di bool. Ogni volta che si arriva ad una chiamata di funzione che assomiglia a questo:


    set_diagnostic_email_config("[email protected]", 0, 1, 0, 1, 0, 1, 0, 0, 0); 

si deve esaminare la documentazione per vedere che cosa questo modello cifra è buono per. E se si utilizza la funzione, si desidera utilizzare nella maggior parte dei casi con alcune impostazioni predefinite sane, vale a dire. finisci col copiare questa linea da qualcun altro.

Se si hanno solo valori booleani, utilizzerei i flag, che è possibile combinare con ORing. Ecco un possibile esempio:


    typedef enum { 
     FLAG_DEFAULT = 0, 
     FLAG_INCLUDE_TIMESTAMP = 0x1, 
     FLAG_SEND_FOR_CRASHES = 0x2, 
     FLAG_SEND_DAILY_STATUS = 0x4, 
     FLAG_SEND_ON_EVENT1 = 0x8 
    } Email_Flags; 

    int set_diagnostic_email_config(const char *address, unsigned int flags); 

Ora è possibile chiamare la funzione in questo modo:

 
    set_diagnostic_email_config("[email protected]", FLAG_SEND_DAILY_STATUS | FLAG_SEND_ON_EVENT1); 

Questo codice è di facile lettura, non c'è bisogno di conoscere ogni opzione per capirlo. E questo codice è facile da scrivere, perché l'ordine dei "paramters" (in realtà l'ordine delle bandiere) non è importante. E questa funzione è facile da estendere, puoi semplicemente aggiungere più flag, diciamo FLAG_SEND_ON_EVENT2 e non è necessario modificare alcuna chiamata di funzione, purché si desideri modificarne il comportamento.