if-Anweisung - Kurzschlussbewertung vs. Lesbarkeit

90

Manchmal kann eine ifAnweisung ziemlich kompliziert oder lang sein. Aus Gründen der Lesbarkeit ist es daher besser, komplizierte Aufrufe vor dem zu extrahieren if.

zB das:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

das mögen

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(vorausgesetzt, das Beispiel ist nicht so schlecht, es dient nur zur Veranschaulichung. Stellen Sie sich andere Aufrufe mit mehreren Argumenten usw. vor.)

Aber mit dieser Extraktion habe ich die Kurzschlussbewertung (SCE) verloren.

  1. Verliere ich wirklich jedes Mal SCE? Gibt es ein Szenario, in dem der Compiler "es optimieren" und dennoch SCE bereitstellen darf?
  2. Gibt es Möglichkeiten, die verbesserte Lesbarkeit des zweiten Snippets beizubehalten, ohne SCE zu verlieren?
relaxxx
quelle
20
Die Praxis zeigt, dass die meisten Antworten zur Leistung, die Sie hier oder an anderen Stellen sehen, in den meisten Fällen falsch sind (4 falsch 1 richtig). Mein Rat ist immer, ein Profiling zu erstellen und es selbst zu überprüfen. Sie vermeiden "vorzeitige Optimierung" und lernen neue Dinge.
Marek R
25
@MarekR geht es nicht nur um Leistung, es geht um mögliche Nebenwirkungen in OtherCunctionCall ...
relaxxx
3
@ David Wenn Sie auf andere Websites verweisen, ist es oft hilfreich darauf hinzuweisen, dass Cross-Posting verpönt ist
Mücke
7
Wenn die Lesbarkeit Ihr Hauptanliegen ist, rufen Sie keine Funktionen mit Nebenwirkungen innerhalb einer if-Bedingung auf
Morgen
3
Potenzielle enge Wähler: Lesen Sie die Frage erneut. Teil (1) ist nicht meinungsbasiert, während Teil (2) leicht aufhören kann, meinungsbasiert zu sein, indem eine Bearbeitung vorgenommen wird, bei der der Verweis auf vermeintliche "Best Practice" entfernt wird, wie ich es gerade tue.
Duplode

Antworten:

119

Eine natürliche Lösung würde so aussehen:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Dies hat den Vorteil, dass es leicht zu verstehen ist, auf alle Fälle anwendbar ist und ein Kurzschlussverhalten aufweist.


Dies war meine anfängliche Lösung: Ein gutes Muster in Methodenaufrufen und for-Schleifenkörpern ist das Folgende:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

Man erhält die gleichen guten Leistungsvorteile der Kurzschlussbewertung, aber der Code sieht besser lesbar aus.

Horia Coman
quelle
4
@relaxxx: Ich verstehe, aber "mehr zu tun nach dem if" ist auch ein Zeichen dafür, dass Ihre Funktion oder Methode zu groß ist und in kleinere aufgeteilt werden sollte. Es ist nicht immer der beste Weg, aber sehr oft!
nperson325681
2
Dies verstößt gegen das Prinzip der weißen Liste
JoulinRouge
13
@JoulinRouge: Interessant, ich hatte noch nie von diesem Prinzip gehört. Ich selbst bevorzuge diesen "Kurzschluss" -Ansatz wegen der Vorteile für die Lesbarkeit: Er reduziert Einrückungen und eliminiert die Möglichkeit, dass etwas nach dem eingerückten Block auftritt.
Matthieu M.
2
Ist es besser lesbar? Nennen Sie b2richtig und Sie würden bekommen someConditionAndSomeotherConditionIsTrue, nicht super aussagekräftig. Außerdem muss ich während dieser Übung eine Reihe von Variablen auf meinem mentalen Stapel behalten (und tbh, bis ich aufhöre, in diesem Bereich zu arbeiten). Ich würde mich für die SJuan76Lösung Nummer 2 entscheiden oder einfach das Ganze in eine Funktion einbauen.
Nathan Cooper
2
Ich habe nicht alle Kommentare gelesen, aber nach einer schnellen Suche fand ich keinen großen Vorteil des ersten Code-Snippets, nämlich das Debuggen. Wenn Sie Inhalte direkt in die if-Anweisung einfügen, anstatt sie zuvor einer Variablen zuzuweisen, und dann stattdessen die Variable verwenden, wird das Debuggen schwieriger als nötig. Durch die Verwendung von Variablen können Werte auch semantisch gruppiert werden, was die Lesbarkeit erhöht.
Rbaleksandar
31

Ich neige dazu, Bedingungen in mehrere Zeilen aufzuteilen, dh:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Selbst wenn Sie mit mehreren Operatoren (&&) arbeiten, müssen Sie nur die Einrückung für jedes Klammerpaar vorantreiben. SCE setzt immer noch ein - es müssen keine Variablen verwendet werden. Das Schreiben von Code auf diese Weise machte es mir schon seit Jahren viel besser lesbar. Komplexeres Beispiel:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {
AmigoJack
quelle
28

Wenn Sie lange Ketten von Bedingungen haben und einige der Kurzschlüsse beibehalten möchten, können Sie temporäre Variablen verwenden, um mehrere Bedingungen zu kombinieren. Wenn Sie Ihr Beispiel nehmen, wäre es möglich, z

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Wenn Sie einen C ++ 11-fähigen Compiler haben, können Sie Lambda-Ausdrücke verwenden , um Ausdrücke zu Funktionen zu kombinieren, ähnlich wie oben:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }
Ein Programmierer
quelle
21

1) Ja, Sie haben keine SCE mehr. Sonst hättest du das

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

funktioniert auf die eine oder andere Weise, je nachdem, ob es ifspäter eine Aussage gibt. Viel zu komplex.

2) Dies basiert auf Meinungen, aber für einigermaßen komplexe Ausdrücke können Sie Folgendes tun:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Wenn es viel zu komplex ist, besteht die offensichtliche Lösung darin, eine Funktion zu erstellen, die den Ausdruck auswertet und aufruft.

SJuan76
quelle
21

Sie können auch verwenden:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

und SCE wird funktionieren.

Aber es ist nicht viel lesbarer als zum Beispiel:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )
KIIV
quelle
3
Ich bin nicht daran interessiert, Boolesche Werte mit einem bitweisen Operator zu kombinieren. Das scheint mir nicht gut geschrieben zu sein. Im Allgemeinen verwende ich alles, was am besten lesbar aussieht, es sei denn, ich arbeite auf sehr niedrigem Niveau und die Anzahl der Prozessorzyklen.
Ameise
3
Ich habe spekiffisch verwendet b = b || otherComplicatedStuff();und @SargeBorsch nimmt eine Bearbeitung vor, um SCE zu entfernen. Vielen Dank, dass Sie mich über diese Änderung @Ant informiert haben.
KIIV
14

1) Verliere ich wirklich jedes Mal SCE? Darf der Compiler in einem bestimmten Szenario "optimieren" und trotzdem SCE bereitstellen?

Ich denke nicht, dass eine solche Optimierung erlaubt ist; OtherComplicatedFunctionCall()könnte insbesondere einige Nebenwirkungen haben.

2) Was ist die beste Vorgehensweise in einer solchen Situation? Ist es nur möglich (wenn ich SCE möchte), alles, was ich brauche, direkt in mir zu haben, wenn "und es einfach so formatieren, dass es so lesbar wie möglich ist"?

Ich ziehe es vor, es in eine Funktion oder eine Variable mit einem beschreibenden Namen umzugestalten. Dadurch bleiben sowohl die Kurzschlussbewertung als auch die Lesbarkeit erhalten:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

Und wenn wir getSomeResult()basierend auf SomeComplicatedFunctionCall()und implementieren OtherComplicatedFunctionCall(), können wir sie rekursiv zerlegen, wenn sie noch kompliziert sind.

songyuanyao
quelle
2
Ich mag das, weil Sie etwas Lesbarkeit erlangen können, indem Sie der Wrapper-Funktion einen beschreibenden Namen geben (obwohl wahrscheinlich nicht getSomeResult). Zu viele andere Antworten bringen nichts wirklich Wertvolles
aw04
9

1) Verliere ich wirklich jedes Mal SCE? Darf der Compiler in einem bestimmten Szenario "optimieren" und trotzdem SCE bereitstellen?

Nein, tust du nicht, aber es wird anders angewendet:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Hier wird der Compiler nicht einmal ausgeführt, OtherComplicatedFunctionCall()wenn SomeComplicatedFunctionCall()true zurückgegeben wird.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Hierbei können beide Funktionen werden ausgeführt werden, da sie in gespeichert werden müssen , b1und b2. Ff wird b1 == truedann b2nicht ausgewertet (SCE). Wurde OtherComplicatedFunctionCall()aber schon ausgeführt.

Wenn b2es nirgendwo anders verwendet wird, ist der Compiler möglicherweise intelligent genug, um den Funktionsaufruf im if zu integrieren, wenn die Funktion keine beobachtbaren Nebenwirkungen aufweist.

2) Was ist die beste Vorgehensweise in einer solchen Situation? Ist es nur möglich (wenn ich SCE möchte), alles, was ich brauche, direkt in mir zu haben, wenn "und es einfach so formatieren, dass es so lesbar wie möglich ist"?

Kommt darauf an. Haben Sie brauchen OtherComplicatedFunctionCall() wegen der Nebenwirkungen oder die Performance - Einbußen der Funktion auszuführen ist minimal , dann sollten Sie den zweiten Ansatz zur besseren Lesbarkeit verwenden. Andernfalls bleiben Sie beim ersten Ansatz bei SCE.

Sombrero Huhn
quelle
8

Eine andere Möglichkeit, die kurzschließt und die Bedingungen an einem Ort hat:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Sie können die Schleife in eine Funktion einfügen und die Funktion eine Liste von Bedingungen akzeptieren lassen und einen booleschen Wert ausgeben.

Levilime
quelle
1
@Erbureth Nein, sind sie nicht. Die Elemente des Arrays sind Funktionszeiger. Sie werden erst ausgeführt, wenn die Funktionen in der Schleife aufgerufen werden.
Barmar
Danke Barmar, aber ich habe eine Bearbeitung vorgenommen, Erbureth hatte Recht vor der Bearbeitung (ich dachte, meine Bearbeitung würde sich visuell direkter verbreiten).
Levilime
4

Sehr seltsam: Sie sprechen von Lesbarkeit, wenn niemand die Verwendung von Kommentaren im Code erwähnt:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

Darüber hinaus gehe ich meinen Funktionen immer einige Kommentare voraus, über die Funktion selbst, über ihre Eingabe und Ausgabe, und manchmal füge ich ein Beispiel hinzu, wie Sie hier sehen können:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Offensichtlich kann die für Ihre Kommentare zu verwendende Formatierung von Ihrer Entwicklungsumgebung abhängen (Visual Studio, JavaDoc unter Eclipse, ...).

Ich gehe davon aus, dass Sie mit SCE Folgendes meinen:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}
Dominique
quelle
-7

Die Lesbarkeit ist erforderlich, wenn Sie in einem Unternehmen arbeiten und Ihr Code von einer anderen Person gelesen wird. Wenn Sie ein Programm für sich selbst schreiben, liegt es an Ihnen, ob Sie die Leistung für verständlichen Code opfern möchten.

kurz
quelle
23
Wenn man bedenkt, dass "du in sechs Monaten" definitiv "jemand anderes" ist und "du morgen" manchmal sein kann. Ich würde niemals die Lesbarkeit für die Leistung opfern, bis ich solide Beweise dafür hatte, dass es ein Leistungsproblem gab.
Martin Bonner unterstützt Monica