【问题标题】:What is the benefit of terminating if … else if constructs with an else clause?用 else 子句终止 if ... else if 结构有什么好处?
【发布时间】:2016-05-05 08:23:20
【问题描述】:

我们的组织有一个必需的编码规则(没有任何解释):

if ... else if 结构应该用 else 子句终止

示例 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

示例 2:

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

这设计用于处理什么边缘情况?

我还担心的原因是,示例 1 不需要 else,但 示例 2 需要。如果原因是可重用性和可扩展性,我认为两种情况都应该使用else

【问题讨论】:

  • 也许可以向贵公司询问原因和好处。乍一看,它迫使程序员考虑并添加注释“无需操作”。与 Java 的检查异常背后的推理相同(至少与争议一样)。
  • 示例 2 实际上是一个很好的示例,说明 assert(false, "should never go here") 可能有意义的地方
  • 我们的组织有类似的规则,但没有那么细化。在我们的案例中,目的有两个。首先,代码一致性。其次,没有松散的字符串/可读性。要求 else,即使在不需要时,也会增加代码的清晰度,即使它没有记录在案。要求 else 是我过去做过的事情,即使在不需要时也是如此,只是为了确保我已经考虑了应用程序中所有可能的结果。
  • 如果使用if (x &lt; 0) { x = 0; } else { if (y &lt; 0) { x = 3; }},您总是可以击败。或者你可以只遵循这样的规则,其中许多是愚蠢的,只是因为你必须这样做。
  • @Thilo 我有点晚了,但仍然没有人发现错误:没有迹象表明 else 永远不会发生,只是它应该没有副作用(这在做的时候似乎很正常&lt; 0 检查),因此断言将使程序崩溃,这可能是值在预期范围内的最常见情况。

标签: c misra


【解决方案1】:

正如另一个答案中提到的,这是来自 MISRA-C 编码指南。目的是防御性编程,这是一个经常用于任务关键型编程的概念。

也就是说,每个if - else if 必须以else 结尾,每个switch 必须以default 结尾。

这有两个原因:

  • 自记录代码。如果您写了else 但将其留空,则意味着:“我绝对考虑过ifelse if 都不是真的情况”。

    不写else 意味着:“要么我考虑了ifelse if 都不正确的情况,要么我完全忘记了考虑它并且我的代码中可能存在一个胖错误”。

  • 停止失控的代码。在任务关键型软件中,您需要编写健壮的程序来解决极不可能的情况。所以你可以看到像

    这样的代码
    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }
    

    对于 PC 程序员和计算机科学家来说,这段代码完全陌生,但它在关键任务软件中非常有意义,因为它可以捕捉到“mybool”因任何原因而损坏的情况。

    从历史上看,您会担心由于 EMI/噪声而损坏 RAM 内存。今天这不是什么大问题。更有可能的是,由于代码中其他地方的错误导致内存损坏:指向错误位置的指针、数组越界错误、堆栈溢出、代码失控等。

    所以在大多数情况下,当您在实施阶段编写错误时,这样的代码会反过来打自己的脸。这意味着它也可以用作调试技术:您正在编写的程序会告诉您何时编写了错误。


编辑

关于为什么在每个if 之后不需要else

if-elseif-else if-else 完全涵盖了变量可以具有的所有可能值。但是一个普通的if 声明不一定涵盖所有可能的值,它有更广泛的用途。大多数情况下,您只想检查某个条件,如果不满足,则什么也不做。那么编写防御性编程来覆盖else 的情况根本没有意义。

另外,如果您在每个if 之后写一个空的else,代码会完全混乱。

MISRA-C:2012 15.7 没有说明为什么不需要else,它只是声明:

注意:简单的if 不需要最后的else 语句 声明。

【讨论】:

  • 如果内存损坏,我希望它破坏的不仅仅是 mybool,可能包括检查代码本身。您为什么不添加另一个块来验证您的 if/else if/else 编译为您所期望的?然后还要验证之前的验证者?
  • 叹息。伙计们,如果您除了桌面编程之外绝对没有其他经验,那么就没有必要对您显然没有经验的事情进行无所不知。当讨论防御性编程并且 PC 程序员停下来时,这种情况总是会发生。出于某种原因,我添加了注释“此代码对 PC 程序员来说将是完全陌生的”。 您不会对安全关键型软件进行编程以在基于 RAM 的台式计算机上运行。时期。代码本身将驻留在带有 ECC 和/或 CRC 校验和的闪存 ROM 中。
  • @Deduplicator 确实(除非 mybool 具有非布尔类型,就像 C 拥有自己的 bool 之前的情况一样;然后编译器不会在没有额外静态分析的情况下做出假设)。关于“如果你写了一个 else 但把它留空,这意味着:“我肯定考虑过 if 和 else if 都不为真的场景。”:我的第一反应是假设程序员忘记把代码放进去else 块,否则为什么有一个空的 else 块就坐在那里? // unused 评论将是合适的,而不仅仅是一个空块。
  • @JAB 是的,如果没有代码,else 块需要包含某种注释。空else 的常见做法是单个分号加上注释:else { ; // doesn't matter }。因为没有理由让任何人只在自己的一行上键入一个缩进的单分号。类似的做法有时用于空循环:while(something) { ; // do nothing }。 (显然,带有换行符的代码。所以 cmets 不允许它们)
  • 我想指出,这个带有两个 IF 和 else 的示例代码即使在多线程环境中的普通桌面软件中也可以转到 else,因此 mybool 的值可以在两者之间更改
【解决方案2】:

贵公司遵循 MISRA 编码指南。这些指南有几个版本包含此规则,但来自MISRA-C:2004

规则 14.10(必需):所有 if ... else if 结构应被终止 带有 else 子句。

只要 if 语句后跟一个或多个 else if 语句;最后的 else if 后跟 else 陈述。如果是简单的if 语句,那么else 声明不必包括在内。最终要求else 声明是防御性编程。 else 声明应 采取适当的行动或包含适当的评论,说明为什么不 采取行动。这与拥有一个 switch 语句中的最后一个 default 子句。例如这段代码 是一个简单的 if 语句:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

而以下代码演示了ifelse if 构造

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

MISRA-C:2012 中,它取代了 2004 版本并且是新项目的当前推荐,the same rule exists but is numbered 15.7

示例 1: 在单个 if 语句中,程序员可能需要检查 n 个条件并执行单个操作。

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

在常规用法中,当使用if 时,并不需要一直执行操作。

示例 2: 在这里,程序员检查 n 个条件并执行多个操作。在常规使用中if..else if 就像switch 您可能需要执行类似默认的操作。因此,根据 misra 标准,需要使用 else

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

这些出版物的当前和过去版本可通过MISRA webstore (via) 购买。

【讨论】:

  • 谢谢,您的回答是 Misra 规则的全部内容,但我希望回答我在问题编辑部分的困惑。
  • 好答案。出于好奇,如果您预计 else 子句无法访问,这些指南是否说明了该怎么做? (不用最后的条件,也许会抛出错误?)
  • 我将反对剽窃和侵犯版权的链接。我将编辑该帖子,以便更清楚您的内容和 MISRA 的内容。最初,这个答案只不过是原始复制/粘贴。
  • @Lundin 我接受它是 misra 编码指南的复制/粘贴。检查问题的编辑和我的答案的编辑,这将使您更好地理解。最初的问题是空白的,然后它被编辑。根据问题,我正在编辑我的答案。无论如何感谢您的编辑
  • @TrieuTheVan:问题不得移动目标。确保您的问题在发布之前 完整。
【解决方案3】:

这相当于在每个开关中都需要一个默认情况。

这个额外的 else 将减少你程序的code coverage


根据我将 linux 内核或 android 代码移植到不同平台的经验,很多时候我们做错了,在 logcat 中我们看到一些错误,例如

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}

【讨论】:

  • 这是__FILE____LINE__ 宏的有用之处,可用于在消息打印时轻松找到源位置。
【解决方案4】:

只是一个简短的解释,因为我大约在 5 年前就这样做了。

(对于大多数语言)没有语法要求包含“null”else 语句(和不必要的{..}),并且在“简单的小程序”中没有必要。但真正的程序员不会编写“简单的小程序”,同样重要的是,他们不会编写会被使用一次然后被丢弃的程序。

当一个人写一个 if/else 时:

if(something)
  doSomething;
else
  doSomethingElse;

这一切看起来都很简单,甚至连添加{..} 的意义都没有。

但是从现在起几个月后的某一天,其他一些程序员(你永远不会犯这样的错误!)将需要“增强”程序并添加一条语句。

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

突然doSomethingElse 有点忘记它应该在else 腿中。

所以你是一个优秀的小程序员,你总是使用{..}。但是你写:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

一切都很好,直到那个新孩子在午夜进行修改:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

是的,它的格式不正确,但项目中的一半代码也是如此,并且“自动格式化程序”被所有 #ifdef 语句所混淆。当然,真正的代码比这个玩具示例要复杂得多。

不幸的是(或不是),我已经离开这种事情好几年了,所以我没有一个新的“真实”例子——上面的例子(显然)是人为的,而且是有点胡闹。

【讨论】:

    【解决方案5】:

    这样做是为了使代码更具可读性,以供以后参考,并让以后的审阅者清楚,最后一个else 处理的剩余案例什么都不做案例,以便他们一见钟情就不会被忽视。

    这是一种很好的编程习惯,它使代码可重用可扩展

    【讨论】:

      【解决方案6】:

      我想补充——部分反驳——之前的答案。虽然以类似 switch 的方式使用 if-else if 当然很常见,它应该涵盖表达式的所有可考虑值,但决不能保证完全涵盖任何可能的条件范围。对于 switch 构造本身也可以这样说,因此需要使用 default 子句,该子句捕获所有剩余值,并且如果没有其他要求,则可以用作断言保护。

      这个问题本身就是一个很好的反例:第二个条件根本与 x 无关(这就是为什么我经常更喜欢基于 if 的变体而不是基于 switch 的变体的原因)。从示例中可以明显看出,如果满足条件 A,则应将 x 设置为某个值。如果不满足 A,则测试条件 B。如果满足,那么 x 应该收到另一个值。如果 A 和 B 都不满足,那么 x 应该保持不变。

      这里我们可以看到,应该使用一个空的else分支来注释程序员对读者的意图。

      另一方面,我不明白为什么必须有一个 else 子句,特别是对于最新和最里面的 if 语句。 在 C 中,没有“else if”这样的东西。只有 if 和 else。 相反,根据 MISRA,结构应该以这种方式正式缩进(我应该将开始的花括号放在自己的行上,但我不喜欢这样):

      if (A) {
          // do something
      }
      else {
          if (B) {
              // do something else (no pun intended)
          }
          else {
              // don't do anything here
          }
      }
      

      当 MISRA 要求在每个分支周围加上花括号时,它会通过提及“if ... else if 构造”而自相矛盾。

      任何人都可以想象深度嵌套的 if else 树的丑陋,see here on a side note。现在想象这个结构可以在任何地方任意扩展。然后在最后要求一个 else 子句,而不是在其他任何地方,就变得荒谬了。

      if (A) {
          if (B) {
              // do something
          }
          // you could to something here
      }
      else {
          // or here
          if (B) { // or C?
              // do something else (no pun intended)
          }
          else {
              // don't do anything here, if you don't want to
          }
          // what if I wanted to do something here? I need brackets for that.
      }
      

      因此,我确信制定 MISRA 指南的人心中有类似开关的 if-else if 意图。

      最后,他们需要准确定义“if ... else if 构造”的含义

      【讨论】:

        【解决方案7】:

        基本原因可能是代码覆盖率和隐含的 else:如果条件不成立,代码将如何表现?对于真正的测试,您需要某种方式来查看您是否在条件 false 的情况下进行了测试。如果您拥有的每个测试用例都经过 if 子句,那么您的代码在现实世界中可能会因为您没有测试的条件而出现问题。

        但是,某些条件可能与示例 1 类似,例如在纳税申报表上:“如果结果小于 0,请输入 0。”您仍然需要在条件为假的情况下进行测试。

        【讨论】:

          【解决方案8】:

          从逻辑上讲,任何测试都意味着两个分支。如果是真的,你会怎么做,如果是假的,你会怎么做。

          对于任何一个分支都没有功能的情况,添加注释说明为什么它不需要功能是合理的。

          这可能对下一个维护程序员有好处。他们不应该搜索太远来确定代码是否正确。你可以Prehunt the Elephant

          就个人而言,它对我很有帮助,因为它迫使我查看 else 案例并对其进行评估。这可能是一个不可能的条件,在这种情况下,我可能会因为违反合同而抛出异常。它可能是良性的,在这种情况下,评论可能就足够了。

          您的里程可能会有所不同。

          【讨论】:

            【解决方案9】:

            大多数情况下,您只有一个 if 语句,这可能是以下原因之一:

            • 函数保护检查
            • 初始化选项
            • 可选处理分支

            例子

            void print (char * text)
            {
                if (text == null) return; // guard check
            
                printf(text);
            }
            

            但是当你做if .. else if时,可能是这样的原因之一:

            • 动态开关盒
            • 处理分叉
            • 处理处理参数

            如果您的 if .. else if 涵盖所有可能性,在这种情况下,您的最后一个 if (...) 不需要,您可以将其删除,因为此时唯一可能的值是该条件所涵盖的值。

            例子

            int absolute_value (int n)
            {
                if (n == 0)
                {
                    return 0;
                }
                else if (n > 0)
                {
                    return n;
                }
                else /* if (n < 0) */ // redundant check
                {
                    return (n * (-1));
                }
            }
            

            在大多数这些原因中,有可能某些内容不适合您的 if .. else if 中的任何类别,因此需要在最后的 else 子句中处理它们,可以通过业务级别进行处理程序、用户通知、内部错误机制等。

            例子

            #DEFINE SQRT_TWO   1.41421356237309504880
            #DEFINE SQRT_THREE 1.73205080756887729352
            #DEFINE SQRT_FIVE  2.23606797749978969641
            
            double square_root (int n)
            {
                     if (n  > 5)   return sqrt((double)n);
                else if (n == 5)   return SQRT_FIVE;
                else if (n == 4)   return 2.0;
                else if (n == 3)   return SQRT_THREE;
                else if (n == 2)   return SQRT_TWO;
                else if (n == 1)   return 1.0;
                else if (n == 0)   return 0.0;
                else               return sqrt(-1); // error handling
            }
            

            最后的else 子句与JavaC++ 等语言中的其他一些内容非常相似,例如:

            • default switch 语句中的大小写
            • catch(...) 出现在所有特定的 catch 块之后
            • finally 在 try-catch 子句中

            【讨论】:

              【解决方案10】:

              我们的软件不是关键任务,但由于防御性编程,我们也决定使用此规则。 我们为理论上无法访问的代码(switch + if-else)添加了抛出异常。由于软件快速失败,它为我们节省了很多次,例如当添加了新类型而我们忘记更改一两个 if-else 或 switch 时。作为奖励,它使查找问题变得超级容易。

              【讨论】:

                【解决方案11】:

                好吧,我的示例涉及未定义的行为,但有时有些人试图花哨而失败,看看:

                int a = 0;
                bool b = true;
                uint8_t* bPtr = (uint8_t*)&b;
                *bPtr = 0xCC;
                if(b == true)
                {
                    a += 3;
                }
                else if(b == false)
                {
                    a += 5;
                }
                else
                {
                    exit(3);
                }
                

                您可能永远不会想到bool 既不是true 也不是false,但它可能会发生。我个人认为这是由决定做一些花哨的人造成的问题,但额外的else 声明可以防止任何进一步的问题。

                【讨论】:

                  【解决方案12】:

                  我目前正在使用 PHP。创建注册表单和登录表单。我只是纯粹使用 if 和 else。没有其他如果或任何不必要的东西。

                  如果用户点击提交按钮 -> 它会转到下一个 if 语句...如果用户名少于“X”个字符,则发出警报。如果成功则检查密码长度等等。

                  如果可以消除服务器加载时间的可靠性以检查所有额外代码,则不需要额外的代码,例如 else。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-09-19
                    • 2019-04-09
                    • 2011-12-25
                    • 2021-03-07
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-08-18
                    相关资源
                    最近更新 更多