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

73

Aus dem Code Complete- Buch stammt das folgende Zitat:

"Setzen Sie den Normalfall nach dem ifanstatt nach dem else"

Was bedeutet, dass Ausnahmen / Abweichungen vom Standardpfad in den elseFall gestellt werden sollten.

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

Welche Regel soll ich beachten?

jao
quelle
15
@gnat nicht duplizieren
BЈовић
16
die beiden schließen sich nicht aus, es gibt keine Mehrdeutigkeit.
20.
6
Ich bin mir nicht sicher, ob das Code-Complete-Zitat ein so toller Rat ist. Vermutlich ist es ein Versuch, die Lesbarkeit zu verbessern, aber es gibt sicherlich Situationen, in denen ungewöhnliche Fälle zuerst im Text angezeigt werden sollen. Beachten Sie, dass dies keine Antwort ist. Die vorhandenen Antworten erklären bereits die Verwirrung, aus der Ihre Frage stammt.
Eamon Nerbonne
14
Früh abstürzen, hart abstürzen! Wenn einer Ihrer ifZweige zurückkehrt, verwenden Sie ihn zuerst. Und vermeiden Sie den elseRest des Codes, den Sie bereits zurückgegeben haben, wenn die Vorbedingungen fehlgeschlagen sind. Code ist einfacher zu lesen, weniger Einrückungen ...
CodeAngry
5
Bin ich der Meinung, dass dies nichts mit Lesbarkeit zu tun hat, sondern wahrscheinlich nur ein irrtümlicher Versuch, die statische Verzweigungsvorhersage zu optimieren?
Mehrdad

Antworten:

189

Bei "Früh abstürzen" geht es nicht darum, welche Codezeile früher in Textform kommt. Es weist Sie an, Fehler im frühestmöglichen Verarbeitungsschritt zu erkennen , damit Sie nicht versehentlich Entscheidungen treffen und Berechnungen auf der Grundlage eines bereits fehlerhaften Zustands durchführen.

In einem if/ else-Konstrukt wird nur einer der Blöcke ausgeführt, daher kann weder von einem "früheren" noch von einem "späteren" Schritt gesprochen werden. Wie man sie bestellt, ist daher eine Frage der Lesbarkeit, und "frühzeitig scheitern" geht nicht in die Entscheidung ein.

Kilian Foth
quelle
2
Ihr allgemeiner Standpunkt ist großartig, aber ich sehe ein Problem. Wenn Sie (if / else if / else) haben, wird der im "else if" bewertete Ausdruck nach dem Ausdruck in der "if" -Anweisung ausgewertet. Wie Sie hervorheben, ist das wichtigste Kriterium die Lesbarkeit gegenüber der konzeptionellen Verarbeitungseinheit. Es wird nur ein Codeblock ausgeführt, es können jedoch mehrere Bedingungen ausgewertet werden.
Encaitar
7
@Encaitar, dieser Grad an Granularität ist weitaus geringer als normalerweise beabsichtigt, wenn der Ausdruck "frühzeitig fehlschlagen" verwendet wird.
Riwalk
2
@Encaitar Das ist die Kunst des Programmierens. Bei mehreren Überprüfungen ist die Idee, die wahrscheinlichste zuerst zu testen. Diese Informationen sind jedoch möglicherweise nicht zur Entwurfszeit, sondern bereits in der Optimierungsphase bekannt. Beachten Sie jedoch die vorzeitige Optimierung.
BPugh
Faire Kommentare. Dies war eine gute Antwort und ich wollte nur versuchen, sie für zukünftige
Zwecke zu verbessern
Skriptsprachen wie JavaScript, Python, Perl, PHP, Bash usw. sind Ausnahmen, da sie linear interpretiert werden. In kleinen if/elseKonstrukten spielt es wahrscheinlich keine Rolle. Aber diejenigen, die in einer Schleife oder mit vielen Anweisungen in jedem Block aufgerufen werden, können schneller ausgeführt werden, wenn die häufigste Bedingung zuerst vorliegt.
DocSalvager
116

Wenn Ihre elseAnweisung nur Fehlercode enthält, sollte dieser 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 kein Widerspruch. Bei der einen geht es um die Ausführungsreihenfolge , bei der anderen um die Codereihenfolge .

Jack Aidley
quelle
4
Es ist ausdrücklich zu erwähnen, dass der Ratschlag, einen normalen Fluss in den Block nach ifund einen außergewöhnlichen Fluss in den Block nach zu setzen else, nicht gilt, wenn Sie keinen haben else! Guard-Anweisungen wie diese sind die bevorzugte Form zur Behandlung von Fehlerbedingungen in den meisten Codierungsstilen.
Jules
+1 das ist ein guter Punkt und gibt tatsächlich eine Antwort auf die eigentliche Frage, wie man Sachen mit Fehlerbedingungen bestellt.
Asche999
Auf jeden Fall viel übersichtlicher und leichter zu pflegen. So mache ich das gerne.
John
27

Sie sollten beiden folgen.

Der Hinweis "Crash early" / fail early bedeutet, dass Sie Ihre Eingaben so schnell wie möglich auf mögliche Fehler prüfen sollten.
Wenn Ihre Methode beispielsweise eine Größe oder Anzahl akzeptiert, von der angenommen wird, dass sie positiv ist (> 0), bedeutet dies, dass Sie zu Beginn der Methode auf diese Bedingung testen, anstatt darauf zu warten, dass der Algorithmus Unsinn erzeugt Ergebnisse.

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

Bart van Ingen Schenau
quelle
1
Können Sie den Zweigvorhersage-Teil näher erläutern? Ich würde nicht erwarten, dass Code, der eher in den if-Fall geht, schneller ausgeführt wird als Code, der eher in den else-Fall geht. Ich meine, das ist der springende Punkt bei der Branchenvorhersage, nicht wahr?
Roman Reiner
@ user136712: In modernen (schnellen) Prozessoren werden Anweisungen abgerufen, bevor die vorherige Anweisung die Verarbeitung abgeschlossen hat. Die Verzweigungsvorhersage wird verwendet, um die Wahrscheinlichkeit zu erhöhen, dass die beim Ausführen einer bedingten Verzweigung abgerufenen Anweisungen auch die richtigen auszuführenden Anweisungen sind.
Bart van Ingen Schenau
2
Ich weiß, was eine Verzweigungsvorhersage ist. Wenn ich deinen Beitrag richtig gelesen habe, sagst du, dass er if(cond){/*more likely code*/}else{/*less likely code*/}schneller abläuft als if(!cond){/*less likely code*/}else{/*more likely code*/}aufgrund der Verzweigungsvorhersage. Ich würde denken, dass die Verzweigungsvorhersage weder auf ifdie elseAussage noch auf die Aussage voreingenommen ist und nur die Geschichte berücksichtigt. Wenn elsees also wahrscheinlicher ist, dass etwas passiert, sollte es in der Lage sein, dies genau so gut vorherzusagen. Ist diese Annahme falsch?
Roman Reiner
18

Ich denke , @JackAidley hat es bereits gesagt , aber lassen Sie mich es so formulieren:

ohne Ausnahmen (zB C)

Im normalen Code-Fluss haben Sie:

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

In dem Fall "Fehler früh" lautet Ihr Code plötzlich:

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

Wenn Sie diese Muster erkennen - ein returnin einem else(oder auch if) Block, überarbeiten sie sofort so dass der Code in Frage stellt nicht einen hat elseBlock:

/* 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;

Dies vermeidet zu tiefes Verschachteln und erfüllt den Fall „Früh ausbrechen“ (hilft dabei, den Verstand und den Code-Fluss sauber zu halten) und verletzt nicht das „wahrscheinlichere ifTeil in das Teil einfügen “, da es einfach keinen elseTeil gibt .

C und aufräumen

Inspiriert von einer Antwort auf eine ähnliche Frage (die das falsch verstanden hat), gehen Sie wie folgt vor, um mit C aufzuräumen. Sie können dort einen oder zwei Austrittspunkte verwenden, hier einen für zwei Austrittspunkte:

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 Exit-Punkt zusammenfassen, wenn weniger Aufräumarbeiten erforderlich sind:

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 gotoist völlig in Ordnung, wenn Sie damit umgehen können; Der Ratschlag, nicht zu verwenden, gotorichtet sich an Personen, die noch nicht selbst entscheiden können, ob eine Verwendung gut, akzeptabel, schlecht, Spaghetti-Code oder etwas anderes ist.

Ausnahmen

Das oben Gesagte handelt von Sprachen ohne Ausnahmen, die ich selbst sehr bevorzuge (ich kann die explizite Fehlerbehandlung viel besser und mit viel weniger Überraschung anwenden). Igli 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 trotz Ausnahmen

Sie können die meisten frühen returns durch das Auslösen einer Ausnahme ersetzen . Allerdings , Ihr normaler Programmablauf, dh jeden Code Fluss in dem das Programm nicht begegnen, na ja, eine Ausnahme ... eine Fehlerbedingung, etc. zu jagen, wird nicht alle Ausnahmen erhöhen.

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 auch komisch aussehen („diese Java ™ -Programmierer weisen uns immer darauf hin, dass diese Ausnahmen normal sind“) und kann das Debuggen behindern (z. B. die IDE anweisen, bei jeder Ausnahme einfach zu brechen). Ausnahmen erfordern häufig, dass die Laufzeitumgebung den Stapel abwickelt, um Rückverfolgungen usw. zu erzeugen. Es gibt wahrscheinlich weitere Gründe dafür.

Das läuft auf Folgendes 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, vereinbaren Sie dies frühzeitig. Wenn Sie eine Bibliothek von Grund auf neu schreiben, denken Sie an Ihre Konsumenten. (Verwenden Sie es niemals abort()in einer Bibliothek.) Aber was auch immer Sie tun, lassen Sie als Faustregel keine Ausnahme auslösen, wenn der Betrieb danach normal (mehr oder weniger) fortgesetzt wird.

Allgemeine Hinweise Ausnahmen

Versuchen Sie zunächst, alle programminternen Ausnahmen zu verwenden, die vom gesamten Entwicklerteam vereinbart wurden. Grundsätzlich planen sie. Verwenden Sie sie nicht im Überfluss. Manchmal ist sogar in C ++, Java ™, Python eine Fehlerrückgabe besser. Manchmal ist es nicht so; benutze sie mit Gedanken.

Mirabilos
quelle
Im Allgemeinen betrachte ich frühe Retouren als Code-Geruch. Ich würde stattdessen eine Ausnahme auslösen, wenn der folgende Code fehlschlagen würde, weil eine Vorbedingung nicht erfüllt ist. Sagen Sie es einfach
DanMan
1
@DanMan: Mein Artikel wurde mit dem Gedanken an C geschrieben ... Ich verwende normalerweise keine Ausnahmen. Aber ich habe den Artikel um einen (hoppla, es wurde ziemlich lang) Vorschlag erweitert. Ausnahmen; übrigens hatten wir gestern die gleiche
frage
Verwenden Sie geschweifte Klammern auch bei einzeiligen ifs und fors. Sie wollen nicht, dass ein anderer goto fail;in der Identität verborgen bleibt.
Bruno Kim
1
@BrunoKim: Dies hängt vollständig von der Stil- / Codierungskonvention des Projekts ab, mit dem Sie arbeiten. Ich arbeite mit BSD, wo dies verpönt ist (mehr optische Unordnung und Verlust des vertikalen Raums); Bei $ dayjob platziere ich sie jedoch so, wie wir es vereinbart haben (weniger schwierig für Neulinge, weniger Fehlerwahrscheinlichkeit usw., wie Sie sagten).
Mirabilos
3

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

Setzen Sie die Schecks an den Anfang, damit der lesende Code nicht die gesamte Methode überspringen muss, sondern immer von oben nach unten scannen muss.

Piotr Perak
quelle
2

(@mirabilos Antwort ist ausgezeichnet, aber hier ist, wie ich über die Frage denke, um zu der gleichen Schlussfolgerung zu gelangen :)

Ich denke daran, dass ich (oder jemand anderes) später den Code meiner Funktion liest. Wenn ich die erste Zeile lese, kann ich keine Annahmen über meine Eingabe treffen (außer denjenigen, die ich sowieso nicht überprüfe). Mein Gedanke ist also: "Okay, ich weiß, dass ich Dinge mit meinen Argumenten anstellen werde. Aber zuerst wollen wir sie bereinigen." Ich sehe den Normalfall nicht als etwas Bedingtes, 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. */
}
einpoklum
quelle
-3

Diese Art der Bedingungsreihenfolge hängt von der Kritik des betreffenden Codeabschnitts und davon ab, ob Standardeinstellungen verwendet werden können.

Mit anderen Worten:

A. Kritischer Abschnitt und keine Standardeinstellungen => Frühzeitig fehlschlagen

B. unkritischer Abschnitt und Standardwerte => Standardwerte im anderen Teil verwenden

C. Zwischenfälle => je nach Bedarf einzeln entscheiden

Nikos M.
quelle
ist das nur deine meinung oder kannst du es irgendwie erklären / stützen?
gnat
Wie genau ist dies nicht gesichert, da jede Option (ohne viele Worte) erklärt, warum sie verwendet wird?
Nikos M.
Ich würde das nicht gerne sagen, aber die Abstimmungen in (meiner) Antwort sind aus dem Kontext :). Dies ist die Frage, die OP gestellt hat, ob Sie eine alternative Antwort haben, ist eine ganz andere Frage
Nikos M.
Ich kann die Erklärung hier ehrlich gesagt nicht sehen. Sagen wir , wenn jemand eine andere Meinung schreibt, wie "Kritischer Abschnitt und keine Vorgaben => Nicht vorzeitig scheitern" , wie würde diese Antwort dem Leser helfen, zwei gegensätzliche Meinungen zu finden? Überlegen Sie , ob Sie es in eine bessere Form bringen möchten, um den Richtlinien zur Beantwortung zu entsprechen.
gnat
ok ich verstehe, das könnte in der Tat eine andere Erklärung sein, aber zumindest verstehst du das Wort "kritischer Abschnitt" und "keine Vorgaben", was bedeuten kann, dass eine Strategie zum frühen Scheitern ansteht, und dies ist in der Tat eine Erweiterung, wenn auch eine minimalistische
Nikos M.