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

耗尽温柔 提交于 2019-11-26 09:20:40

问题


Our organization has a required coding rule (without any explanation) that:

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

Example 1:

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

Example 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 */
}

What edge case is this designed to handle?

What also concerns me about the reason is that Example 1 does not need an else but Example 2 does. If the reason is re-usability and extensibility, I think else should be used in both cases.


回答1:


As mentioned in another answer, this is from the MISRA-C coding guidelines. The purpose is defensive programming, a concept which is often used in mission-critical programming.

That is, every if - else if must end with an else, and every switch must end with a default.

There are two reasons for this:

  • Self-documenting code. If you write an else but leave it empty it means: "I have definitely considered the scenario when neither if nor else if are true".

    Not writing an else there means: "either I considered the scenario where neither if nor else if are true, or I completely forgot to consider it and there's potentially a fat bug right here in my code".

  • Stop runaway code. In mission-critical software, you need to write robust programs that account even for the highly unlikely. So you could see code like

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

    This code will be completely alien to PC programmers and computer scientists, but it makes perfect sense in mission-critical software, because it catches the case where the "mybool" has gone corrupt, for whatever reason.

    Historically, you would fear corruption of the RAM memory because of EMI/noise. This is not much of an issue today. Far more likely, memory corruption occurs because of bugs elsewhere in the code: pointers to wrong locations, array-out-of-bounds bugs, stack overflow, runaway code etc.

    So most of the time, code like this comes back to slap yourself in the face when you have written bugs during the implementation stage. Meaning it could also be used as a debug technique: the program you are writing tells you when you have written bugs.


EDIT

Regarding why else is not needed after every single if:

An if-else or if-else if-else completely covers all possible values that a variable can have. But a plain if statement is not necessarily there to cover all possible values, it has a much broader usage. Most often you just wish to check a certain condition and if it is not met, then do nothing. Then it is simply not meaningful to write defensive programming to cover the else case.

Plus it would clutter up the code completely if you wrote an empty else after each and every if.

MISRA-C:2012 15.7 gives no rationale why else is not needed, it just states:

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




回答2:


Your company followed MISRA coding guidance. There are a few versions of these guidelines that contain this rule, but from 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:

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

whereas the following code demonstrates an if, else if construct

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

In MISRA-C:2012, which supersedes the 2004 version and is the current recommendation for new projects, the same rule exists but is numbered 15.7.

Example 1: in a single if statement programmer may need to check n number of conditions and performs single operation.

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

In a regular usage performing a operation is not needed all the time when if is used.

Example 2: Here programmer checks n number of conditions and performing multiple operations. In regular usage if..else if is like switch you may need to perform a operation like default. So usage else is needed as per misra standard

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

Current and past versions of these publications are available for purchase via the MISRA webstore (via).




回答3:


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

This extra else will Decrease code coverage of your program.


In my experience with porting linux kernel , or android code to different platform many time we do something wrong and in logcat we see some error like

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



回答4:


Only a brief explanation, since I did this all about 5 years ago.

There is (with most languages) no syntactic requirement to include "null" else statement (and unnecessary {..}), and in "simple little programs" there is no need. But real programmers don't write "simple little programs", and, just as importantly, they don't write programs that will be used once and then discarded.

When one write an if/else:

if(something)
  doSomething;
else
  doSomethingElse;

it all seems simple and one hardly sees even the point of adding {..}.

But some day, a few months from now, some other programmer (you would never make such a mistake!) will need to "enhance" the program and will add a statement.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

Suddenly doSomethingElse kinda forgets that it's supposed to be in the else leg.

So you're a good little programmer and you always use {..}. But you write:

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

All's well and good until that new kid makes a midnight modification:

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

Yes, it's improperly formatted, but so is half the code in the project, and the "auto formatter" gets bollixed up by all the #ifdef statements. And, of course, the real code is far more complicated than this toy example.

Unfortunately (or not), I've been out of this sort of thing for a few years now, so I don't have a fresh "real" example in mind -- the above is (obviously) contrived and a bit hokey.




回答5:


This, is done to make the code more readable, for later references and to make it clear, to a later reviewer, that the remaining cases handled by the last else, are do nothing cases, so that they are not overlooked somehow at first sight.

This is a good programming practice, which makes code reusable and extend-able.




回答6:


I would like to add to – and partly contradict – the previous answers. While it is certainly common to use if-else if in a switch-like manner that should cover the full range of thinkable values for an expression, it is by no means guaranteed that any range of possible conditions is fully covered. The same can be said about the switch construct itself, hence the requirement to use a default clause, which catches all remaining values and can, if not otherwise required anyway, be used as an assertion safeguard.

The question itself features a good counter-example: The second condition does not relate to x at all (which is the reason why I often prefer the more flexible if-based variant over the switch-based variant). From the example it is obvious that if condition A is met, x should be set to a certain value. Should A not be met, then condition B is tested. If it is met, then x should receive another value. If neither A nor B are met, then x should remain unchanged.

Here we can see that an empty else branch should be used to comment on the programmer's intention for the reader.

On the other hand, I cannot see why there must be an else clause especially for the latest and innermost if statement. In C, there is no such thing as an 'else if'. There is only if and else. Instead, according to MISRA, the construct should formally be indented this way (and I should have put the opening curly braces on their own lines, but I don't like that):

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

When MISRA asks to put curly braces around every branch, then it contradicts itself by mentioning "if ... else if constructs".

Anyone can imagine the ugliness of deeply nested if else trees, see here on a side note. Now imagine that this construct can be arbitrarily extended anywhere. Then asking for an else clause in the end, but not anywhere else, becomes absurd.

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

So I am sure that the people who developed the MISRA guidelines had the switch-like if-else if intention in mind.

In the end, it comes down for them to defining precisely what is meant with an "if ... else if construct"




回答7:


The basic reason is probably code coverage and the implicit else: how will the code behave if the condition is not true? For genuine testing, you need some way to see that you have tested with the condition false. If every test case you have goes through the if clause, your code could have problems in the real world because of a condition that you did not test.

However, some conditions may properly be like Example 1, like on a tax return: "If the result is less than 0, enter 0." You still need to have a test where the condition is false.




回答8:


Logically any test implies two branches. What do you do if it is true, and what do you do if it is false.

For those cases where either branch has no functionality, it is reasonable to add a comment about why it doesn't need to have functionality.

This may be of benefit for the next maintenance programmer to come along. They should not have to search too far to decide if the code is correct. You can kind of Prehunt the Elephant.

Personally, it helps me as it forces me to look at the else case, and evaluate it. It may be an impossible condition, in which case i may throw an exception as the contract is violated. It may be benign, in which case a comment may be enough.

Your mileage may vary.




回答9:


Most the time when you just have a single if statement, it's probably one of reasons such as:

  • Function guard checks
  • Initialization option
  • Optional processing branch

Example

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

    printf(text);
}

But when you do if .. else if, it's probably one of reasons such as:

  • Dynamic switch-case
  • Processing fork
  • Handling a processing parameter

And in case your if .. else if covers all possibilities, in that case your last if (...) is not needed, you can just remove it, because at that point the only possible values are the ones covered by that condition.

Example

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));
    }
}

And in most of these reasons, it's possible something doesn't fit into any of the categories in your if .. else if, thus the need to handle them in a final else clause, handling can be done through business-level procedure, user notification, internal error mechanism, ..etc.

Example

#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
}

This final else clause is quite similar to few other things in languages such as Java and C++, such as:

  • default case in a switch statement
  • catch(...) that comes after all specific catch blocks
  • finally in a try-catch clause



回答10:


Our software was not mission critical, yet we also decided to use this rule because of defensive programming. We added a throw exception to the theoretically unreachable code (switch + if-else). And it saved us many times as the software failed fast e.g. when a new type has been added and we forgot to change one-or-two if-else or switch. As a bonus it made super easy to find the issue.




回答11:


Well, my example involves undefined behavior, but sometimes some people try to be fancy and fails hard, take a look:

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);
}

You probably would never expect to have bool which is not true nor false, however it may happen. Personally I believe this is problem caused by person who decides to do something fancy, but additional else statement can prevent any further issues.




回答12:


I'm currently working with PHP. Creating a registration form and a login form. I am just purely using if and else. No else if or anything that is unnecessary.

If user clicks submits button -> it goes to the next if statement... if username is less than than 'X' amount of characters then alert. If successful then check password length and so on.

No need for extra code such as an else if that could dismiss reliability for server load time to check all the extra code.



来源:https://stackoverflow.com/questions/35053371/what-is-the-benefit-of-terminating-if-else-if-constructs-with-an-else-clause

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!