关于C#:如果使用else子句构造,则终止if有什么好处?

What is the benefit of terminating if … else if constructs with an else clause?

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

if … else if constructs should be terminated with an else clause

例1:

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

例2:

1
2
3
4
5
6
7
8
9
10
11
12
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


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

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

原因有两个:

  • 自我记录代码。如果你写一个else,但不写,这意味着:"我确实考虑过这样的情况:ifelse if都不是真的。"

    不写else意味着:"要么我考虑了ifelse if都不是真的情况,要么我完全忘了考虑,我的代码中可能有一个胖错误。"

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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的理由,它只是说明:

Note: a final else statement is not required for a simple if
statement.


贵公司遵循misra编码指南。这些指导方针有几个版本包含了这条规则,但是来自misra-c:2004?:

Rule 14.10 (required): All if … else if constructs shall be terminated
with an else clause.

This rule applies whenever an if statement is followed by one or more
else if statements; the final else if shall be followed by an else
statement. In the case of a simple if statement then the else
statement need not be included. The requirement for a final else
statement is defensive programming. The else statement shall either
take appropriate action or contain a suitable comment as to why no
action is taken. This is consistent with the requirement to have a
final default clause in a switch statement. For example this code
is a simple if statement:

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

whereas the following code demonstrates an if, else if construct

1
2
3
4
5
6
7
8
9
10
11
12
13
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个条件并执行单个操作。

1
2
3
4
if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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)购买。


This is the equivalent of requiring a default case in every switch.

这个额外的将减少程序的代码覆盖率。

在我将Linux内核或Android代码移植到不同平台的经验中,我们经常做一些错误的事情,在logcat中我们看到一些错误,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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("
 [function or module name]: this should never happen
"
);

        /* 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.
        */

}


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

在大多数语言中,不需要包含"空"的else语句(和不必要的{..}语句),而在"简单的小程序"中则不需要。但是真正的程序员不会写"简单的小程序",同样重要的是,他们也不会写那些会被使用一次然后被丢弃的程序。

当一个人写if/else时:

1
2
3
4
if(something)
  doSomething;
else
  doSomethingElse;

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

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

1
2
3
4
5
if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

突然,doSomethingElse有点忘了它应该在else的腿上。

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

1
2
3
4
5
if(something) {
  if(anotherThing) {
    doSomething;
  }
}

一切都很好,直到那个新来的孩子午夜修改:

1
2
3
4
5
6
7
8
9
if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

是的,它的格式不正确,但是项目中的一半代码也是这样,"auto-formatter"被所有#ifdef语句搞混了。当然,真正的代码要比这个玩具例子复杂得多。

不幸的是(或不是),我已经离开这类事情几年了,所以我没有一个新的"真实"的例子在头脑中——上面(显然)是人为的,有点矫揉造作。


这样做是为了使代码更易读,便于以后的引用,并向以后的审阅者清楚地表明,最后一个else处理的其余案例都是不做任何事情的,因此它们在第一眼就不会被忽略。

这是一个很好的编程实践,使代码可以重用和扩展。


我想添加到&ndash;中,部分与前面的答案相矛盾。虽然使用if-else是很常见的,如果以一种类似开关的方式使用if-else,它应该覆盖表达式的全部可思考值范围,但决不能保证完全覆盖任何可能的条件范围。关于switch构造本身也可以这样说,因此需要使用default子句,该子句捕获所有剩余值,如果无论如何都不需要,则可以将其用作断言保护。

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

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

另一方面,我不明白为什么必须有一个else子句,特别是对于最新和最内部的if语句。在C语言中,没有"else if"这样的词。只有if和else。相反,根据Misra的说法,构造应该以这种方式正式缩进(我应该在它们自己的行上放置开放的花括号,但我不喜欢这样):

1
2
3
4
5
6
7
8
9
10
11
if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

当米斯拉要求在每根树枝上都加上花括号时,它会自相矛盾地提到"如果……Else if构造"。

任何人都可以想象,如果还有其他树的话,那么深巢树的丑陋之处,请看这里的旁注。现在假设这个构造可以任意扩展到任何地方。然后在最后要求一个else子句,而不是在其他任何地方,就变得荒谬了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.
}

因此,我确信,制定《米斯拉指南》的人,会像其他人一样,考虑到自己的意图。

最后,他们要精确地定义"如果……else if构造"


基本原因可能是代码覆盖率和隐含的else:如果条件不是真的,代码将如何工作?对于真正的测试,您需要某种方法来查看您的测试条件是否为假。如果您已经通过了if子句的每个测试用例,那么您的代码在现实世界中可能会因为没有测试的条件而出现问题。

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


逻辑上,任何测试都意味着两个分支。如果它是真的,你做什么?如果它是假的,你做什么?

对于任何一个分支都没有功能的情况,可以添加一条关于为什么不需要功能的注释。

这可能对下一个维护程序员的到来有好处。他们不必搜索太远就可以决定代码是否正确。你可以预先猎杀大象。

就个人而言,它有助于我,因为它迫使我审视其他案例,并对其进行评估。这可能是一个不可能的条件,在这种情况下,我可能会因为违反合同而提出例外。它可能是良性的,在这种情况下,评论可能就足够了。

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


大多数情况下,当您只有一个if声明时,这可能是以下原因之一:

  • 功能保护检查
  • 初始化选项
  • 可选处理分支

例子

1
2
3
4
5
6
void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

但是当你做if .. else if的时候,这可能是一个原因,比如:

  • 动态开关箱
  • 加工叉
  • 处理处理参数

如果你的if .. else if涵盖了所有的可能性,如果你的最后一个if (...)不需要,你可以去掉它,因为在那一点上,唯一可能的值就是那些条件所涵盖的值。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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条款中处理它们,处理可以通过业务级过程、用户通知、内部错误机制等来完成。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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++,例如:

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

嗯,我的例子涉及到未定义的行为,但有时有些人试图表现得花哨而失败,看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

你可能永远也不会期望拥有不是truefalsebool,但这可能发生。就我个人而言,我认为这是由决定做一些花哨事情的人引起的问题,但附加的else声明可以阻止任何进一步的问题。


我们的软件不是关键任务,但由于防御编程,我们也决定使用这个规则。我们在理论上无法访问的代码中添加了一个抛出异常(switch+if-else)。当软件快速失败时,它为我们节省了很多时间,例如,当一个新类型被添加时,我们忘记了更改一个或两个(如果有的话)或者切换。作为一个额外的好处,找到这个问题非常容易。


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

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

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