2009-07-03 7 views
6

Al diseñar una API C para configurar una biblioteca/utilidad, tengo un compañero de trabajo que prefiere agrupar todos los parámetros de configuración en una llamada de función. Por ejemplo:C configuración API

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) 

Una similares "llegar" existe la función de muchos parámetros .. La principal ventaja de este método es que si alguien añade una nueva opción, por ejemplo, "bool send_on_event3", a continuación, debido a que el prototipo se ha cambiado se ven obligados a actualizar cada lugar donde se usa esta llamada de función (suponiendo que haya múltiples lugares donde las personas llaman esto).

prefiero algo en la línea de:

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

donde se acaba de cambiar los elementos de la estructura, según sea necesario. Pero ... si alguien actualiza la estructura de email_config "no obliga a las personas a actualizar todos los lugares donde se usa (aunque a menudo queremos ...). Además, mi compañero de trabajo se queja de que si alguien intenta inicializarlo "email_config" a mano, luego si las cosas se agregan luego esos nuevos campos serán sin inicializar sin advertencias.

¿Hay algún consenso fuerte sobre qué método es el preferido? ¿O tal vez hay otra alternativa que me falta?

Respuesta

11

Una estructura es mejor que una lista larga. La lista larga es difícil de mantener, ya que nadie recuerda el orden exacto.

Puede crear un constructor que llene esta estructura con entradas seguras (por defecto y/o no válidas). Siempre (probablemente en la función de acceso) comprobar valores inválidos hará que sea fácil detectar errores en la inicialización.

Puede ocultar un número mágico en esta estructura. El primer campo es CONFIG_MAGIC que debe ser igual a una constante que haya definido. Establece este campo en el constructor y espera estar configurado en todo momento. Esto evita que alguien simplemente malloc() ing la estructura y la inicialice a mano. Tal programador necesitaría aprender acerca de esta constante CONFIG_MAGIC y es más que probable que encuentre y use el constructor apropiado.

+0

+1. Excelente consejo – DevSolar

+1

+1 básicamente es OOP - más fácil de administrar cambios, mantener la implementación con el estado - constructor/destructor para inicializar la limpieza – stefanB

+0

¡Gracias! Me gusta especialmente la idea de CONFIG_MAGIC. – Will

0

¿Cómo es posible tener que actualizar la llamada a la función en todos los lugares? Por supuesto, debe reconstruir todo con los nuevos encabezados actualizados, pero lo ideal es que tenga que hacer el menor número posible de cambios en el código vez que se cambia la configuración.

0

El acercamiento de "numerosos parámetros" tiene una gran desventaja de que tiene que alterar una gran cantidad de código cada vez que se introduce un nuevo parámetro solo por el hecho de pasar ese parámetro por la pila de llamadas. Con una estructura solo necesita cambiar los lugares que realmente usan los parámetros. La necesidad de modificar a menudo toneladas de código puede provocar un error de siembra por sí solo y eso haría que las ventajas de las comprobaciones en tiempo de compilación de todos los parámetros sean mayores.

3

Utilizaría la siguiente manera, que es una extensión de su camino.

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. 
} 

De esta manera, puede agregar 'instaladores' a pedido, cada vez que agrega un campo nuevo. Sin permitir que el usuario se meta con la estructura misma.

+0

Preferiría no hacer estas tareas de administración de memoria. –

+0

bueno, tienes razón :) – AraK

0

me gustaría utilizar

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) 

Tener un event_type como un entero significa que puede agregar un nuevo evento sin cambiar la firma. Un void* extra da una estructura opcional para cada tipo de evento.

Si utiliza struct, es posible que los cambios no sean siempre compatibles con binarios.

3

Las listas de parámetros largos no son legibles, en particular si se trata de una lista de bools. Cada vez que se llega a una llamada a la función que tiene este aspecto:


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

usted tiene que mirar en la documentación para ver lo que este patrón de dígitos es bueno para. Y si está utilizando la función, desea utilizarla en la mayoría de los casos con algunos valores predeterminados correctos, es decir. terminas copiando esta línea de otro lugar.

Si solo tiene valores booleanos, usaría banderas, que puede combinar con ORing. Aquí está un ejemplo posible:


    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); 

Ahora usted puede llamar a la función como esta:

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

Este código es fácil de leer, que no necesita saber todas las opciones para entenderlo. Y este código es fácil de escribir, porque el orden de los "parámetros" (en realidad, el orden de los indicadores) no es importante. Y esta función es fácil de ampliar, simplemente puede agregar más indicadores, digamos FLAG_SEND_ON_EVENT2 y no necesita cambiar ninguna llamada de función, siempre que quiera cambiar su comportamiento.