终止if…else if带有else子句的构造有什么好处?


136

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

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这两种情况都应使用。


30
也许问贵公司原因和好处。乍一看,它迫使程序员对此进行思考,并添加一条注释“无需采取任何措施”。Java的检查异常背后的原因相同(至少有争议)。
锡洛

32
示例2实际上是一个assert(false, "should never go here")可能有意义的很好的示例
Thilo

2
我们的组织有类似的规则,但并不十分详细。在我们的案例中,目的是双重的。首先,代码一致性。第二,没有松散的字符串/可读性。即使不需要,也需要else,即使没有文档记录,也可以增加代码的清晰度。即使在不需要时,我过去也要做过其他事情,只是为了确保我已经考虑了应用程序中所有可能的结果。
LuvnJesus

4
如果使用,您总会失败if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}。或者您可能只是遵循这样的规则,其中许多都是愚蠢的,仅仅是因为您必须这样做。
Jim Balter

3
@Thilo我来晚了,但是仍然没有人发现错误:没有迹象表明其他情况永远不会发生,只是它应该没有副作用(在进行< 0检查时看起来很正常),所以断言会继续使程序崩溃,可能是最常见的情况,即值在预期范围内。
罗杜维克

Answers:


148

如另一个答案中所述,这来自MISRA-C编码准则。目的是防御性编程,这是一种在关键任务编程中经常使用的概念。

也就是说,每个if - else if必须以结束else,并且每个switch必须以结束default

有两个原因:

  • 自文档代码。如果您写了一个,else但将其留空,则表示:“我肯定已经考虑了既不成立if也不else if成立的情况”。

    不写else那里的意思是:“要么我考虑了既不是事实if也不else if是事实的场景,或者我完全忘记了考虑它,而我的代码中就存在一个潜在的错误”。

  • 停止失控代码。在关键任务软件中,您需要编写健壮的程序,甚至可以解决极少发生的情况。所以你可以看到类似的代码

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }

    该代码对于PC程序员和计算机科学家而言将是完全陌生的,但是在任务关键型软件中它是完全有意义的,因为它会捕获“ mybool”由于任何原因而损坏的情况。

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

    因此,在大多数情况下,当您在实施阶段编写错误时,像这样的代码就会再次冒出来。这意味着它也可以用作调试技术:正在编写的程序会告诉您何时编写了错误。


编辑

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

一个if-elseif-else if-else完全覆盖该变量可以具有的所有可能的值。但是,一个简单的if语句不一定涵盖所有可能的值,它的用法更为广泛。通常,您只是希望检查某个条件,如果不满足,则什么也不做。那么编写防御性程序来掩盖else事实根本没有意义。

另外,如果您else在每个之后都写了一个空白,它将完全使代码混乱if

MISRA-C:2012 15.7没有给出为什么不需要的理由else,它只是指出:

注意:else对于简单的if 声明,不需要最终声明。


6
如果内存损坏,我希望它不仅会破坏mybool,还可能包括检查代码本身。为什么不添加另一个块来验证if/else if/else编译是否符合您的期望?然后再验证一次以前的验证程序?
Oleg V. Volkov,

49
叹。伙计们,如果您除了台式机编程之外完全没有其他经验,那么您无需对您显然没有经验的事情做所有了解。当讨论防御性编程并且PC程序员停下来时,总是会发生这种情况。我添加了注释“此代码将完全与PC程序员无关”是有原因的。您不对安全性至关重要的软件进行编程以使其在基于RAM的台式计算机上运行。期。代码本身将驻留在具有ECC和/或CRC校验和的闪存ROM中。
隆丁

6
@Deduplicator确实(除非mybool具有非布尔类型,如C在拥有自己的类型之前的情况bool;否则编译器在没有附加静态分析的情况下不会做出假设)。关于“如果您编写else却将其保留为空,则表示”:“我肯定已经考虑了是否为true的情况。”。 else块,否则为什么只坐在那里一个空的else块呢?一种// unused意见是合适的,不只是一个空块。
JAB

5
@JAB是的,else如果没有代码,则该块需要包含某种注释。清空的常见做法else是使用单个分号加一个注释:else { ; // doesn't matter }。因为没有理由没有人会在其自己的一行上键入一个缩进的单半冒号。有时在空循环中使用类似的做法:while(something) { ; // do nothing }。(显然,带有换行符的代码。因此注释不允许这样做)
Lundin

5
我想强调一点,这两个国际单项体育联合会和其他此示例代码可以去别的即使在多线程环境通常桌面软件,所以mybool的值可以只是之间变化
伊利亚Bursov

61

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

规则14.10(必需):如果…else,则所有条件都应以else子句终止。

只要if语句后接一个或多个else if语句,则适用此规则。最后的其他if说明之后else 。如果是简单的if语句,则else 无需包括该语句。最后else 声明的要求是防御性编程。该else声明应采取适当的措施或就为什么不采取任何措施提出适当的意见。这与defaultswitch语句中具有final 子句的要求是一致的。例如,这段代码是一个简单的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年版本,并且是新项目的当前建议,存在相同的规则,但编号为15.7

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

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

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

示例2: 此处程序员检查n个条件并执行多项操作。在常规用法if..else if中,switch您可能需要执行诸如default之类的操作。因此else,按照misra标准需要使用

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

这些出版物的当前和过去版本可以通过MISRA网络商店通过)购买。


1
谢谢,您的答案是Misra规则的所有内容,但是我希望在我对问题的编辑部分感到困惑时给出答案。
Trevor

2
好答案。出于好奇,如果您期望该else子句无法到达,这些指南是否会说些什么?(而是放弃最终条件,也许会抛出错误?)
jpmc26 '16

17
对于going窃和侵犯版权的链接,我将投反对票。我将对帖子进行编辑,以使您的字眼和MISRA的字眼更加清楚。最初,此答案不过是原始副本/粘贴而已。
伦丁

8
@TrieuTheVan:问题一定不能成为目标。发布问题之前,请确保问题已完成。
TJ Crowder

1
@ jpmc26不,但是MISRA的目的不是给您紧凑的代码,而是给您安全的代码。如今,任何编译器无论如何都会优化无法访问的代码,因此没有不利之处。
格雷厄姆

19

这等效于在每个开关中都要求使用默认情况。

这些额外的内容将减少程序的代码覆盖率


根据我将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.
        */
}

2
在这里__FILE____LINE__宏非常有用,可以使您很容易找到源位置(如果曾经打印过该消息)。
彼得·科德斯

9

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

(对于大多数语言)没有“ 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语句所混淆。而且,当然,实际代码比这个玩具示例要复杂得多。

不幸的是(或者不是),我已经摆脱这种事情已有几年了,所以我没有想到一个新的“真实”示例-上面的(显然)是人为设计的,有点胡扯。


7

这样做是为了使代码更具可读性,以供以后参考,并向以后的审阅者明确说明,last所处理的其余情况else都不做任何事,因此,乍一看也不会被忽略。

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


6

我想补充(部分矛盾)先前的答案。尽管使用if-else if以类似开关的方式来覆盖表达式的所有可考虑值的范围肯定很常见,但绝不能保证完全覆盖任何范围的可能条件。关于switch构造本身也可以这样说,因此使用default子句的要求可以捕获所有剩余的值,并且如果没有其他要求也可以用作断言保护。

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

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

另一方面,我看不到为什么必须要有else子句,尤其是对于最新和最里面的if语句。在C语言中,没有“ else if”之类的东西。只有在其他情况下。相反,根据MISRA的说法,应该以这种方式正式缩进该构造(并且我应该将花括号放在自己的行上,但我不喜欢这样):

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

当MISRA要求在每个分支上都使用花括号时,则通过提及“ if ... else if构造”而自相矛盾。

任何人都可以想象,如果没有其他树木,则深深嵌套的丑陋,请参阅此处的旁注。现在想象一下,该结构可以任意扩展到任何地方。然后,最后要求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 ... else if构造”的含义


5

根本原因可能是代码覆盖率以及其他隐式原因:如果条件不成立,代码将如何表现?对于真正的测试,您需要某种方式来证明您在条件为假的情况下进行了测试。如果您拥有的每个测试用例都经过if子句,则由于未测试的条件,您的代码在现实世界中可能会遇到问题。

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


5

从逻辑上讲,任何测试都包含两个分支。如果为真,该怎么办?如果为假,该怎么办。

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

这对于下一个维护程序员可能会有好处。他们不必搜索太多就可以确定代码是否正确。您可以对大象进行预捕

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

你的旅费可能会改变。


4

大多数情况下,只有一条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,因此需要在final 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子句与诸如Java和的其他语言非常相似C++,例如:

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

2

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


2

好吧,我的示例涉及未定义的行为,但有时有些人会幻想并努力失败,请看一下:

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这不truefalse,但它可能发生。我个人认为这是由决定做某事的人引起的问题,但是附加else声明可以防止任何进一步的问题。


1

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

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

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

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.