if语句-短路评估与可读性


90

有时,一条if语句可能相当复杂或冗长,因此,为了便于阅读,最好在之前提取复杂的调用if

例如:

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

进入这个

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

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

(提供的示例并没有那么糟糕,它只是为了说明……想象一下具有多个参数的其他调用,等等。)

但是通过这种提取,我失去了短路评估(SCE)。

  1. 我真的每次都会失去SCE吗?在某些情况下,允许编译器“对其进行优化”并仍然提供SCE吗?
  2. 有没有办法在不丢失SCE的情况下提高第二个片段的可读性?

20
实践表明,在大多数情况下,您将在此处或其他地方看到的有关性能的大多数答案都是错误的(4错误1正确)。我的建议是始终进行性能分析并自行检查,您将避免“过早的优化”并学习新知识。
Marek R

25
@MarekR不仅与性能有关,还与OtherCunctionCall中可能产生的副作用有关
Relaxxx '16

3
@大卫指其他网站时,它往往是有帮助的一点,交叉发布是令人难以接受的
蚊蚋

7
如果您主要关注可读性,请不要在有条件的情况下调用带有副作用的函数
Morgen

3
潜在的接近投票者:再次阅读该问题。第一部分(1)不是基于观点的,而第(2)部分很容易通过编辑删除象我将要做的那样,不再引用任何所谓的“最佳实践”。
duplode

Answers:


119

一种自然的解决方案如下所示:

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

if (bn)
{
  // do stuff
}

这具有易于理解,适用于所有情况以及短路行为的优点。


这是我最初的解决方案:方法调用和for循环主体中的一个好的模式如下:

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

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

// do stuff

人们可以通过短路评估获得同样不错的性能优势,但是代码看起来更具可读性。


4
@relaxxx:我明白了,但是“之后需要做更多的事情if”也表明您的函数或方法太大,应该拆分为较小的函数或方法。并非总是最好的方法,但往往是最好的方法!
nperson325681

2
这违反了白名单原则
JoulinRouge

13
@JoulinRouge:有趣的是,我从未听说过这个原理。我本人更喜欢这种“短路”方法,以提高可读性:它减少了压痕,并消除了在压痕块之后发生某些情况的可能性。
Matthieu M.

2
它更具可读性吗?b2正确命名,您将获得someConditionAndSomeotherConditionIsTrue,而不是超级有意义。此外,在此练习中,我必须在我的思维栈上保留一堆变量(直到我停止在该范围内工作之前,还要等待th)。我会使用SJuan76的2号解决方案,或者只是将整个东西放在一个函数中。
内森·库珀

2
我还没有阅读所有注释,但是经过快速搜索之后,我发现第一个代码段的主要优势不是调试。将内容直接放入if语句中,而不是事先将其分配给一个变量,然后使用该变量代替,这使得调试比需要的更加困难。使用变量还可以将值在语义上分组在一起,从而提高了可读性。
rbaleksandar '16

31

我倾向于将条件分解为多行,即:

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

即使与多个运算符(&&)进行处理,您也只需要使每对括号都缩进即可。SCE仍然有效-无需使用变量。这样编写代码使我多年来对它的可读性更高。更复杂的示例:

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

28

如果条件条件很长,并且要保持某些短路状态,则可以使用临时变量来组合多个条件。以您的例子为例,可能会做

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

如果您具有支持C ++ 11的编译器,则可以使用lambda表达式将表达式组合成函数,类似于上面的代码:

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

if (e() && some_other_expression) { ... }

21

1)是的,您不再具有SCE。否则,您将拥有

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

一种工作方式还是另一种工作方式取决于if稍后是否有声明。方式太复杂了。

2)这是基于意见的,但是对于相当复杂的表达式,您可以执行以下操作:

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

如果方法过于复杂,则显而易见的解决方案是创建一个计算表达式并调用它的函数。


21

您还可以使用:

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

并且SCE将起作用。

但这并不比例如更具可读性:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

3
我不热衷于将布尔值与按位运算符组合在一起。通常,除非我的工作水平非常低且处理器周期很短,否则我会使用看起来最易读的内容。
2016年

3
我专门使用了@SargeBorsch b = b || otherComplicatedStuff();进行编辑以删除SCE。感谢您通知我有关@Ant的更改。
基辅2016年

14

1)我真的每次都会失去SCE吗?是否允许编译器“优化”某些方案并仍然提供SCE?

我认为这种优化是不允许的。特别是OtherComplicatedFunctionCall()可能会有一些副作用。

2)在这种情况下的最佳做法是什么?如果并且“仅格式化它以使其尽可能可读”,是否只有可能(当我需要SCE时)将我需要的所有内容直接包含在其中?

我更喜欢将其重构为一个函数或一个具有描述性名称的变量;这将保留短路评估和可读性:

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

...

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

当我们getSomeResult()基于SomeComplicatedFunctionCall()和实现时OtherComplicatedFunctionCall(),如果它们仍然很复杂,则可以递归分解它们。


2
我之所以这样,是因为您可以通过给包装函数一个描述性的名称来获得一些可读性(尽管可能不是getSomeResult),但是其他答案却并没有真正增加任何价值
aw04

9

1)我真的每次都会失去SCE吗?是否允许编译器“优化”某些方案并仍然提供SCE?

不,您没有,但是它的用法有所不同:

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

在这里,OtherComplicatedFunctionCall()如果SomeComplicatedFunctionCall()返回true ,则编译器甚至不会运行。

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

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

在这里,两个函数都将运行,因为它们必须存储在b1和中b2。FF b1 == trueb2不会进行评估(SCE)。但是OtherComplicatedFunctionCall()已经运行了。

如果b2在其他地方都没有使用if,则编译器可能足够聪明,可以在if函数没有明显副作用的情况下内联该函数调用。

2)在这种情况下的最佳做法是什么?如果并且“仅格式化它以使其尽可能可读”,是否只有可能(当我需要SCE时)将我需要的所有内容直接包含在其中?

那要看。您是否 OtherComplicatedFunctionCall()由于副作用而需要运行,或者该功能对性能的影响很小,那么您应该使用第二种方法来提高可读性。否则,请通过第一种方法坚持使用SCE。


8

短路并在一个地方具有条件的另一种可能性:

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

您可以将循环放入函数中,并让该函数接受条件列表并输出一个布尔值。


1
@Erbureth不,他们不是。数组的元素是函数指针,直到在循环中调用函数后才执行它们。
Barmar

感谢Barmar,但我进行了编辑,在编辑之前,Erbureth是对的(我认为我的编辑将在视觉上更直接地进行传播)。
levilime '16

4

非常奇怪:您在谈论可读性时,没有人提及代码中注释的用法:

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

最重要的是,我总是在函数前添加一些注释,包括函数本身,输入和输出的注释,有时还会举一个示例,如下所示:

/*---------------------------*/
/*! 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)

显然,用于注释的格式可能取决于您的开发环境(Visual Studio,Eclipse下的JavaDoc等)。

就SCE而言,我假设您的意思是:

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

-7

如果您在公司工作,并且其他人可以读取您的代码,则必须具有可读性。如果您为自己编写程序,则要取决于可理解的代码而牺牲性能,这取决于您。


23
请记住,“六个月后的您” 肯定是“其他人”,有时“您就是明天”。在没有确凿的证据表明存在性能问题之前,我永远不会牺牲性能的可读性。
马丁·邦纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.