6 Stimmen

C-Konfigurations-API

Beim Entwurf einer C-API für die Konfiguration einer Bibliothek/eines Dienstprogramms zieht es ein Kollege von mir vor, alle Konfigurationsparameter in einem Funktionsaufruf zusammenzufassen. Zum Beispiel:

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 )

Eine ähnliche "get"-Funktion existiert mit vielen Parametern. Der Hauptvorteil dieser Methode ist, dass, wenn jemand eine neue Option hinzufügt, z. B. "bool send_on_event3", dann, weil sich der Prototyp geändert hat, Sie gezwungen sind, jeden Ort zu aktualisieren, an dem dieser Funktionsaufruf verwendet wird (unter der Annahme, dass es mehrere Orte gibt, an denen Leute diese Funktion aufrufen).

Ich bevorzuge eine Formulierung, die in etwa so lautet:

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

wo Sie einfach die Strukturelemente nach Bedarf ändern. Aber... wenn jemand die email_config"-Struktur aktualisiert, zwingt das die Leute nicht dazu, alle Stellen zu aktualisieren, an denen sie verwendet wird (obwohl wir das oft wollen). Außerdem beschwert sich mein Kollege, dass, wenn jemand einfach versucht, "email_config" von Hand zu initialisieren, diese neuen Felder ohne Warnungen nicht initialisiert werden, wenn später etwas hinzugefügt wird.

Gibt es einen Konsens darüber, welche Methode vorzuziehen ist, oder gibt es vielleicht eine andere Alternative, die ich übersehe?

11voto

Eine Struktur ist besser als eine lange Liste. Eine lange Liste ist schwer zu pflegen, da sich niemand die genaue Reihenfolge merken kann.

Sie können einen Konstruktor erstellen, der diese Struktur mit sicheren (Standard- und/oder ungültigen) Einträgen füllt. Wenn Sie immer (wahrscheinlich in der Accessor-Funktion) auf ungültige Werte prüfen, können Sie Fehler bei der Initialisierung leicht erkennen.

Sie können eine magische Zahl in dieser Struktur verstecken. Das erste Feld ist CONFIG_MAGIC, das gleich einer von Ihnen definierten Konstante sein muss. Sie setzen dieses Feld im Konstruktor und erwarten, dass es immer gesetzt ist. Damit vermeiden Sie, dass jemand die Struktur einfach malloc()ing und von Hand initialisiert. Ein solcher Programmierer müsste sich über die Konstante CONFIG_MAGIC informieren und wird höchstwahrscheinlich den richtigen Konstruktor finden und verwenden.

3voto

Khaled Alshaya Punkte 90854

Ich würde die folgende Methode anwenden, die eine Erweiterung Ihrer Methode ist.

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

Auf diese Weise können Sie bei Bedarf "Setter" hinzufügen, wenn Sie ein neues Feld hinzufügen. Ohne dass der Benutzer die Struktur selbst verändern kann.

3voto

quinmars Punkte 10775

Lange Parameterlisten sind nicht lesbar, insbesondere wenn es sich um eine Liste von bools handelt. Jedes Mal, wenn Sie zu einem Funktionsaufruf kommen, der wie folgt aussieht:

    set_diagnostic_email_config("bla@foo.com", 0, 1, 0, 1, 0, 1, 0, 0, 0);

Sie müssen in der Dokumentation nachsehen, wofür dieses Zahlenmuster gut ist. Und wenn Sie die Funktion verwenden, möchten Sie in den meisten Fällen mit einigen vernünftigen Vorgaben verwenden, dh. Sie am Ende in Kopieren dieser Zeile von irgendwo anders.

Wenn Sie nur boolesche Werte haben, würde ich Flaggen verwenden, die Sie mit ORing kombinieren können. Hier ist ein mögliches Beispiel:

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

Jetzt können Sie die Funktion wie folgt aufrufen:

    set_diagnostic_email_config("bla@foo.com", FLAG_SEND_DAILY_STATUS | FLAG_SEND_ON_EVENT1);

Dieser Code ist leicht zu lesen, man muss nicht jede Option kennen, um ihn zu verstehen. Und dieser Code ist leicht zu schreiben, weil die Reihenfolge der "Parameter" (eigentlich die Reihenfolge der Flags) nicht wichtig ist. Und diese Funktion ist leicht zu erweitern, man kann einfach weitere Flags hinzufügen, z.B. FLAG_SEND_ON_EVENT2 und Sie brauchen keinen Funktionsaufruf zu ändern, solange Sie sein Verhalten ändern wollen.

0voto

Tal Pressman Punkte 7113

Wie kann es gut sein, dass der Funktionsaufruf an allen Stellen aktualisiert werden muss? Natürlich sollten Sie alles mit den neuen aktualisierten Kopfzeilen neu erstellen, aber im Idealfall möchten Sie bei jeder Änderung der Konfiguration so wenig wie möglich am Code ändern müssen.

0voto

sharptooth Punkte 162790

Der Ansatz der "zahlreichen Parameter" hat den großen Nachteil, dass man jedes Mal, wenn ein neuer Parameter eingeführt wird, eine Menge Code ändern muss, nur um diesen Parameter auf dem Aufrufstapel weiterzugeben. Bei einer Struktur müssen Sie nur die Stellen ändern, die die Parameter tatsächlich verwenden. Die Notwendigkeit, oft tonnenweise Code zu ändern, kann allein schon zur Fehlerverursachung führen, und das würde die Vorteile der Kompilierzeitüberprüfung, ob alle Parameter vorhanden sind, aufwiegen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X