it-swarm.com.de

Soll ich dem normalen Weg folgen oder früh scheitern?

Aus dem Buch Code Complete stammt das folgende Zitat:

"Setzen Sie den Normalfall nach dem if und nicht nach dem else."

Dies bedeutet, dass Ausnahmen/Abweichungen vom Standardpfad in den Fall else eingefügt werden sollten.

Aber The Pragmatic Programmer lehrt uns, "früh abzustürzen" (S. 120).

Welche Regel soll ich befolgen?

72
jao

Bei "Früh abstürzen" geht es nicht darum, welche Codezeile in Textform früher kommt. Sie werden aufgefordert, Fehler zu erkennen im frühestmöglichen Verarbeitungsschritt, damit Sie nicht versehentlich Entscheidungen und Berechnungen treffen, die auf einem bereits fehlerhaften Zustand basieren.

In einem if/else -Konstrukt wird nur einer der Blöcke ausgeführt, sodass keiner als "früher" oder "später" bezeichnet werden kann. Wie man sie bestellt, ist daher eine Frage der Lesbarkeit, und "früh scheitern" geht nicht in die Entscheidung ein.

188
Kilian Foth

Wenn Ihre else -Anweisung nur Fehlercode enthält, sollte sie höchstwahrscheinlich nicht vorhanden sein.

Anstatt dies zu tun:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

mach das

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

Sie möchten Ihren Code nicht tief verschachteln, nur um die Fehlerprüfung einzuschließen.

Und wie alle anderen bereits gesagt haben, sind die beiden Ratschläge nicht widersprüchlich. Bei einem geht es um Ausführungsreihenfolge, bei dem anderen um Codereihenfolge.

115
Jack Aidley

Sie sollten beiden folgen.

Der Hinweis "Früh abstürzen"/"früh ausfallen" bedeutet, dass Sie Ihre Eingaben so schnell wie möglich auf mögliche Fehler testen sollten.
Wenn Ihre Methode beispielsweise eine Größe oder Anzahl akzeptiert, die positiv sein soll (> 0), bedeutet der frühzeitige Fehlschlag, dass Sie diese Bedingung gleich zu Beginn Ihrer Methode testen, anstatt darauf zu warten der Algorithmus, um Unsinn Ergebnisse zu erzeugen.

Der Ratschlag, den Normalfall an die erste Stelle zu setzen, bedeutet, dass, wenn Sie auf eine Bedingung testen, der wahrscheinlichste Pfad an erster Stelle stehen sollte. Dies hilft bei der Leistung (da die Verzweigungsvorhersage des Prozessors häufiger stimmt) und der Lesbarkeit, da Sie keine Codeblöcke überspringen müssen, um herauszufinden, was die Funktion im Normalfall tut.
Dieser Rat gilt nicht wirklich, wenn Sie auf eine Vorbedingung testen und sofort aussteigen (mithilfe von Asserts oder if (!precondition) throw -Konstrukten), da beim Lesen des Codes keine Fehlerbehandlung übersprungen werden muss.

Ich denke @JackAidley sagte das Wesentliche schon , aber lassen Sie mich es so formulieren:

ohne Ausnahmen (z. B. C)

Im regulären Code-Fluss haben Sie:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

Im Fall "Fehler früh aus" lautet Ihr Code plötzlich:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

Wenn Sie dieses Muster erkennen - ein return in einem else (oder sogar if) -Block, überarbeiten Sie es sofort, damit der betreffende Code funktioniert ) nicht haben einen else Block:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

In der echten Welt…

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

Dadurch wird vermieden, dass zu tief verschachtelt wird und erfüllt den Fall „Früh ausbrechen“ (hilft, den Geist zu halten - und den Codefluss - sauber zu halten) und nicht gegen das "das wahrscheinlichere Ding in den if Teil setzen" verstoßen, weil es einfach keinen else Teil gibt.

C und Bereinigung

Inspiriert von einer Antwort auf eine ähnliche Frage (die dies falsch verstanden hat), führen Sie hier eine Bereinigung mit C durch. Sie können dort einen oder zwei Ausstiegspunkte verwenden, hier einen für zwei Ausstiegspunkte:

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

Sie können sie zu einem Austrittspunkt zusammenfassen, wenn weniger Bereinigung erforderlich ist:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

Diese Verwendung von goto ist vollkommen in Ordnung, wenn Sie damit umgehen können; Der Rat, sich von goto fernzuhalten, richtet sich an Personen, die noch nicht selbst entscheiden können, ob eine Verwendung gut, akzeptabel, schlecht, Spaghetti-Code oder etwas anderes ist.

Ausnahmen

Das obige spricht über Sprachen ohne Ausnahmen, die ich selbst sehr bevorzuge (ich kann die explizite Fehlerbehandlung viel besser und mit viel weniger Überraschung verwenden). Um igli zu zitieren:

<igli> exceptions: a truly awful implementation of quite a Nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

Aber hier ist ein Vorschlag, wie Sie es in einer Sprache mit Ausnahmen gut machen und wann Sie sie gut verwenden möchten:

fehlerrückgabe angesichts von Ausnahmen

Sie können die meisten frühen returns durch das Auslösen einer Ausnahme ersetzen. Jedoch , Ihr normaler Programmablauf, dh jeder Codefluss, in dem die Programm ist nicht auf eine Ausnahme gestoßen ... eine Fehlerbedingung oder so etwas, soll keine Ausnahmen auslösen.

Das bedeutet, dass…

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

… Ist okay, aber…

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

… ist nicht. Grundsätzlich ist eine Ausnahme kein Kontrollflusselement . Dies lässt Operations Sie auch komisch aussehen („diese Java ™ -Programmierer sagen uns immer, dass diese Ausnahmen normal sind“) und kann das Debuggen behindern (z. B. sagen Sie der IDE, dass sie nur eine Ausnahme unterbrechen soll). Ausnahmen erfordern häufig, dass die Laufzeitumgebung den Stapel abwickelt, um Tracebacks usw. zu erzeugen. Es gibt wahrscheinlich weitere Gründe dagegen.

Dies läuft darauf hinaus: Verwenden Sie in einer Sprache, die Ausnahmen unterstützt, alles, was der vorhandenen Logik und dem vorhandenen Stil entspricht und sich natürlich anfühlt. Wenn Sie etwas von Grund auf neu schreiben, sollten Sie dies frühzeitig vereinbaren. Denken Sie beim Schreiben einer Bibliothek von Grund auf an Ihre Kunden. (Verwenden Sie abort() niemals in einer Bibliothek…) Was auch immer Sie tun, lassen Sie als Faustregel keine Ausnahme auslösen, wenn der Vorgang danach (mehr oder weniger) normal fortgesetzt wird.

allgemeine Ratschläge wrt. Ausnahmen

Versuchen Sie zunächst, die programminterne Nutzung der vom gesamten Entwicklerteam vereinbarten Ausnahmen zu erreichen. Planen Sie sie grundsätzlich. Verwenden Sie sie nicht im Überfluss. Manchmal ist sogar in C++, Java ™, Python eine Fehlerrückgabe besser. Manchmal ist es nicht; benutze sie mit Gedanken.

18
mirabilos

Meiner Meinung nach ist 'Guard Condition' eine der besten und einfachsten Möglichkeiten, Code lesbar zu machen. Ich hasse es wirklich, wenn ich if am Anfang der Methode sehe und den Code else nicht sehe, weil er nicht auf dem Bildschirm angezeigt wird. Ich muss nach unten scrollen, um throw new Exception Zu sehen.

Setzen Sie die Schecks an den Anfang, damit die Person, die den Code liest, nicht über die gesamte Methode springen muss, um ihn zu lesen, sondern ihn immer von oben nach unten scannen muss.

3
Piotr Perak

(@mirabilos ' Antwort ist ausgezeichnet, aber so denke ich über die Frage nach, um zu dem gleichen Ergebnis zu gelangen :)

Ich denke daran, dass ich (oder jemand anderes) später den Code meiner Funktion lese. Wenn ich die erste Zeile lese, kann ich keine Annahmen über meine Eingabe treffen (außer denen, die ich sowieso nicht überprüfen werde). Mein Gedanke ist also: "Ok, ich weiß, dass ich Dinge mit meinen Argumenten machen werde. Aber lassen Sie uns sie zuerst aufräumen" - dh Kontrollpfade töten, auf denen sie nicht meinen Wünschen entsprechen. "Aber gleichzeitig Ich sehe den Normalfall nicht als etwas, das konditioniert ist, ich möchte betonen, dass er normal ist.

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}
2
einpoklum