【问题标题】:if statement - short circuit evaluation vs readabilityif 语句 - 短路评估与可读性
【发布时间】:2017-02-26 03:15:31
【问题描述】:

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

例如这个:

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

进入这个

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

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

(提供的示例并没有那么不好,这只是为了说明......想象一下其他带有多个参数的调用等)

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

  1. 我真的每次都失去 SCE 吗?是否存在允许编译器“优化”并仍提供 SCE 的某些场景?
  2. 有没有办法在不丢失 SCE 的情况下保持第二个 sn-p 改进的可读性?

【问题讨论】:

  • 实践表明,您将在此处或其他地方看到的大多数关于性能的答案在大多数情况下都是错误的(4 错误 1 ​​正确)。我的建议是始终进行分析并自己检查,您将避免“过早优化”并学习新东西。
  • @MarekR 不仅仅关乎性能,还关乎 OtherCunctionCall 中可能出现的副作用 ...
  • @David 在提及其他网站时,指出cross-posting is frowned upon 通常会有所帮助
  • 如果可读性是您最关心的问题,请不要在 if 条件内调用具有副作用的函数
  • 潜在的接近选民:再次阅读问题。第 (1) 部分基于意见,而第 (2) 部分可以通过删除对任何假定“最佳实践”的引用的编辑轻松停止基于意见,正如我即将做的那样.

标签: c++ if-statement short-circuiting side-effects


【解决方案1】:

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

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

一个与短路评估同样出色的性能优势,但代码看起来更具可读性。

【讨论】:

  • @relaxxx:我明白了,但是“if 之后要做的事情更多”也表明您的函数或方法太大,应该拆分成更小的函数或方法。它并不总是最好的方法,但通常是!
  • 这违反了白名单原则
  • @JoulinRouge:有趣,我从来没有听说过这个原理。我自己更喜欢这种“短路”方法,因为它具有可读性的好处:它减少了缩进并消除了在缩进块之后发生某些事情的可能性。
  • 它更具可读性吗?正确命名b2,你会得到someConditionAndSomeotherConditionIsTrue,不是很有意义。此外,在这个练习期间,我必须在我的心理堆栈中保留一堆变量(并且直到我停止在这个范围内工作)。我会选择SJuan76 的第二个解决方案,或者只是将整个事情放在一个函数中。
  • 我还没有阅读所有的 cmets,但是在快速搜索之后,我没有发现第一个代码 sn-p 的一大优势,即调试。将内容直接放入 if 语句中,而不是事先将其分配给变量,然后使用该变量来代替,这会使调试变得比需要的更加困难。使用变量还允许在语义上将值分组在一起,从而提高可读性。
【解决方案2】:

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

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

即使在处理多个运算符 (&&) 时,您也只需要在每对括号中增加缩进即可。 SCE 仍然有效 - 无需使用变量。多年来,以这种方式编写代码使其对我来说更具可读性。更复杂的例子:

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

【讨论】:

    【解决方案3】:

    如果您有很长的条件链以及要保持一些短路的内容,那么您可以使用临时变量来组合多个条件。以您为例,可以这样做,例如

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

    如果你有一个支持 C++11 的编译器,你可以使用 lambda expressions 将表达式组合成函数,类似于上面的:

    auto e = []()
    {
        return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
    };
    
    if (e() && some_other_expression) { ... }
    

    【讨论】:

      【解决方案4】:

      1) 是的,您不再拥有 SCE。否则,你会有那个

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

      根据以后是否有if 语句以一种或另一种方式工作。太复杂了。

      2) 这是基于意见的,但对于相当复杂的表达,您可以这样做:

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

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

      【讨论】:

        【解决方案5】:

        你也可以使用:

        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()
           )
        

        【讨论】:

        • 我不热衷于将布尔值与按位运算符结合起来。这对我来说似乎不是很好的类型。通常我使用看起来最易读的任何东西,除非我工作的级别非常低并且处理器周期数。
        • 我专门使用了 b = b || otherComplicatedStuff(); 和 @SargeBorsch 进行编辑以删除 SCE。感谢@Ant 通知我这一变化。
        【解决方案6】:

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

        我不认为这样的优化是允许的;尤其是OtherComplicatedFunctionCall() 可能会有一些副作用。

        2) 在这种情况下,最佳做法是什么?是否只有可能(当我想要 SCE 时)将我需要的所有内容直接放在 if 中并“将其格式化为尽可能可读”?

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

        bool getSomeResult() {
            return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
        }
        
        ...
        
        if (getSomeResult())
        {
            //do stuff
        }
        

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

        【讨论】:

        • 我喜欢这个,因为你可以通过给包装函数一个描述性的名称来获得一些可读性(尽管可能不是 getSomeResult),太多其他答案并没有真正增加任何价值
        【解决方案7】:

        1) 我真的每次都输掉 SCE 吗?是编译器是一些场景 允许“优化它”并仍然提供 SCE?

        不,你没有,但它的应用方式不同:

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

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

        bool b1 = SomeComplicatedFunctionCall();
        bool b2 = OtherComplicatedFunctionCall();
        
        if (b1 || b2)
        {
            //do stuff
        }
        

        在这里,两个函数运行,因为它们必须存储在b1b2 中。 Ff b1 == true 然后 b2 将不会被评估 (SCE)。但是OtherComplicatedFunctionCall() 已经运行了。

        如果 b2 没有在其他任何地方使用,编译器可能足够聪明,可以在 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
          

          你可以把循环放到一个函数中,让函数接受一个条件列表并输出一个布尔值。

          【讨论】:

          • @Erbureth 不,他们不是。数组的元素是函数指针,直到函数在循环中被调用,它们才会被执行。
          • 感谢 Barmar,但我在编辑之前做了一个编辑,Erbureth 是对的(我认为我的编辑会更直接地在视觉上传播)。
          【解决方案9】:

          很奇怪:当没有人提到代码中注释的用法时,您正在谈论可读性:

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

          最重要的是,我总是在我的函数之前加上一些 cmets,关于函数本身,关于它的输入和输出,有时我会举一个例子,你可以在这里看到:

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

          显然,用于您的 cmets 的格式可能取决于您的开发环境(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) {
          ...
          }
          

          【讨论】:

            【解决方案10】:

            如果您在一家公司工作并且您的代码将被其他人阅读,那么可读性是必要的。如果您为自己编写程序,您是否愿意为了代码的可理解性而牺牲性能。

            【讨论】:

            • 请记住,“六个月后的你”肯定是“其他人”,而“明天的你”有时也可能是。在我有确凿的证据表明存在性能问题之前,我绝不会为了性能而牺牲可读性。
            猜你喜欢
            • 2012-12-16
            • 1970-01-01
            • 2020-12-28
            • 2021-08-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多