2015-08-22 12 views
6

Betrachten Sie diese Funktion:Wie Code aufblasen in C von Fehlerbehandlung reduzieren und debuggt

int get_result(int *result) { 
    int err = 0; 
    int number = 0;   

    if (result == NULL) { 
     printf("error: null input\n"); 
     return -1; 
    } 

    err = get_number(&number); 

    if (err != 0) { 
     printf("error calling get_number: err = %d\n", err); 
     return err; 
    } 

    err = calculate_result(number, result); 

    if (err != 0) { 
     printf("error calling get_result: err = %d\n", err); 
     return err; 
    } 

    return err; 
} 

Die eigentliche Arbeit in dieser Funktion nur 3 Zeilen erfordert (Anzahl Variable deklariert, ruft GET_NUMBER(), dann calculate_result nennen ()). Der Code zur Fehlerüberprüfung/-verarbeitung erweitert diese Funktion jedoch auf 17 Zeilen (Geben oder Nehmen, je nachdem, wie Sie die Zeilen zählen).

In einem größeren Maßstab, mit vielen Aufrufen und mehreren Fehlerprüfungen, blähen wir die Funktion vollständig auf und machen sie unlesbar und sehr schwer zu verstehen.

Was sind Möglichkeiten, um diese Blähungen in C-Code zu umgehen und die Lesbarkeit der Kernoperation einer Funktion beizubehalten (ohne wesentlichen Fehlerbehandlungscode zu opfern)?

+1

Warum verwenden Sie 'errno' als Typname? – EOF

+0

gut. Wenn Sie nicht nach stderr drucken und die geschweiften Klammern schneiden, sparen Sie 4-6 Zeilen. 'c' ist so. Eigentlich sind alle Sprachen so, man kann in Java argumentieren, dass man auch einen einfachen Aufruf zu einem try-catch-Block erweitert. Also, Sie programmieren, was Sie brauchen. – HuStmpHrrr

+0

Sie können, tatsächlich mit Makro speichern Sie einige Arbeit, sagen '#define SAFE_CALL (Ausdruck) {errno err = Ausdruck; if (err! = 0) gib zurück; } '. Das wäre nicht schlimm, wenn Sie die Verwendung in Ihrem Code in einem vernünftigen Rahmen einschränken würden. – HuStmpHrrr

Antwort

1

Dies ist der Hauptgrund für Ausnahmen, aber ich muss zugeben, ich bin kein Freund der Einführung von Ausnahmen in einer Sprache mit expliziten Speicherverwaltung, so dass diese Antwort möglicherweise voreingenommen ist. Dennoch gibt es einige gängige Strategien, um die Geschäftslogik von der Fehlerbehandlung in zu trennen.

  1. Verwenden Ausnahmen - haben einen Blick auf longjmp()/setjmp() für sie selbst in umzusetzen. Ich würde davon abraten.
  2. Wenn im Fehlerfall eine Bereinigung durchgeführt werden muss, setzen Sie sie am Ende der Funktion und goto dort. (ja, wirklich!)
  3. Um einen Rückgabewert zu prüfen und eine Nachricht an stderr zu drucken, versuchen Sie, häufige Fälle herauszufiltern und Makros wie z. in Ihrem Fall:

    #define CHECK_RETVAL(val, action) do { \ 
        if (val < 0) { \ 
         fprintf(stderr, "error calling " action ": %s\n", strerror((val))); \ 
         goto error_cleanup; \ 
        }} while (0) 
    
0

Wenn Linie Länge wenig Interesse für Sie ist, können Sie die folgende Funktion schreiben:

int checkforError(int errorCode, const char* message) 
{ 
    if (errorCode != 0) 
    { 
     printf("%s: err = %d\n", err", message, errorCode); 
     return 0; 
    } 

    return 1; 
} 

und verwenden Sie es zwei letzten ifs so schrumpfen:

checkforError(err = get_number(&number), "error calling get_number") && checkforError(err = calculate_result(number, result), "error calling get_result"); 
return err; 

Da die erste if wenig mit anderen Fällen gemeinsam hat, gibt es keinen Grund,unterzubringendazu.

Kurzschluss hat die Reihenfolge der Auswertung garantiert, es ist also kein undefiniertes Verhalten. Siehe Logical comparisons: Is left-to-right evaluation guaranteed?

3

Willkommen in der Welt der Produktion Qualitätscode. Es gibt einige Makro- und Factoring-Tricks, die die Fehlerprüfung weniger ausführlich machen können. Ausnahmen sind ein anderer Mechanismus. Aber die Hauptanpassung ist Sichtweise. Sie denken an "die echte Arbeit" als etwas, das von der Fehlerbehandlung getrennt ist. Es ist nicht. Der Umgang mit allen möglichen Bedingungen ist die Essenz der Softwareentwicklung. Um es aus Ihrem System herauszuholen, erklären Sie "die Hauptarbeit" in den Kommentaren, schreiben Sie dann den echten Algorithmus mit extern auferlegter Bedingung, die Front und Mitte behandelt.

0

Die einzige Situation, in der Fehlerbehandlungscode unnötig ist, ist, wenn es redundant ist. Das Vermeiden von redundantem Fehlerbehandlungscode ist genau das Gleiche wie das Vermeiden von redundantem Code im Allgemeinen. Die einzige Frage ist, wie groß der Umfang für den Fehlerbehandlungscode sein kann. Generell gilt, dass möglichst viel gemeinsames Verhalten in einen möglichst großen Umfang einbezogen wird.

Zum Beispiel malloc Versagen der Regel tödlich ist so, anstatt jeden Rückgabewert überprüfen, können Sie die Funktion wickeln ...

void* fmalloc(size_t n) { 

    void* const m = malloc(n); 

    if (!m) 
    on_fatal("out of memory"); 

    return m; 
} 

Wenn das Verhalten nur an die aufrufende Funktion scoped werden kann, können Sie ein goto ...

int on_file(fd, rm, wm) { 

    if (read(fd, rm, 8) < 0) 
    goto err; 

    if (write(fd, wm, 8) < 0) 
    goto err; 

    return 0; 

    err: 
    on_error("on_file error"); 
    return -1; 

} 

Für Dinge, die parametrisiert werden kann, parametrieren sie.

Hier ist etwas example Code, den ich benutze. Im Allgemeinen unterscheidet sich das Reduzieren von Fehlerbehandlungscode nicht nur von der Gruppierung gemeinsamen Verhaltens.