Often I built functions, in C, that checks some parameters and return an error code.
Which is the best approach to stop the values checking when I found an error?
First example:
ErrorCode_e myCheckFunction( some params )
{
ErrorCode_e error = CHECK_FAILED;
if( foo == bar )
{
if( foo_1 == bar_1 )
{
if( foo_2 == bar_2 )
{
error = CHECK_SUCCESS;
}
}
}
return error;
}
Second Example:
ErrorCode_e myCheckFunction( some params )
{
if( foo != bar )
{
return CHECK_FAILED;
}
if( foo_1 != bar_1 )
{
return CHECK_FAILED;
}
if( foo_2 != bar_2 )
{
return CHECK_SUCCESS;
}
}
I prefer the first approach because I read that the MISRA rules avoid multiple return statement.
Which is the best approach?
The second is best because it is so much easier to read, scales well with increased complexity and immediately stops executing the function upon errors. This is the only sensible way to write such functions when you have extensive error handling inside a function, for example if the function is a parser or protocol decoder.
That MISRA-C disallows multiple return statements in a function is a defect of MISRA-C. The intention is supposedly to disallow spaghetti code that returns from all over the place, but dogmatically banning multiple return statements can actually turn code far less readable, as we can see from your example. Imagine if you needed to check 10 different errors. You'd then have 10 compound if statements, which would be an unreadable mess.
I have reported this defect several times to the MISRA committee but they have not listened. Instead, MISRA-C just blindly cites IEC 61508 as source for the rule. Which in turn only lists one questionable source for this rule (IEC 61508:7 C.2.9) and it's some a dinosaur programming book from 1979.
This is not professional nor scientific - both MISRA-C and IEC 61508 (and ISO 26262) should feel ashamed over (directly or indirectly) listing subjective nonsense from 1979 as their only source and rationale.
Simply use the second form and raise a permanent deviation against this defect MISRA rule.
I agree with the Lundin’s answer but I would like to provide another solution that complies with the single exit rule and still is similarly readable to the second example:
ErrorCode_e myCheckFunction( some params )
{
ErrorCode_e error = CHECK_FAILED;
if( foo != bar )
{
error = CHECK_FAILED;
}
else if( foo_1 != bar_1 )
{
error = CHECK_FAILED;
}
else if( foo_2 != bar_2 )
{
error = CHECK_SUCCESS;
}
else
{
// else (even empty) is required by MISRA after else-if
}
return error;
}
Since there are only two options in the example, we could use just one condition:
ErrorCode_e myCheckFunction( some params )
{
ErrorCode_e error = CHECK_FAILED;
if( (foo == bar) && (foo_1 == bar_1) && (foo_2 != bar_2) )
{
error = CHECK_SUCCESS;
}
return error;
}
This case can be even more simplified, we don’t need any local variables:
ErrorCode_e myCheckFunction( some params )
{
return ( (foo == bar) && (foo_1 == bar_1) && (foo_2 != bar_2) )
? CHECK_SUCCESS : CHECK_FAILED;
}
The method I use is goto error_exit.
You have to consider why a function might fail.
Reason 1 is illegal arguments, like passing a negative to a square root. So assert fail, the error is caller's.
Reason 2 is out of memory - that's an inherent problem with functions that scale. You need to shunt the failure up, though normally if a program won't give you a small amount of memory to hold, say, a file path, then it's dead.
Reason 3 is bad grammar. That's a special case of illegal arguments. If the argument is a double for a square root, caller can reasonably be expected to check for negatives. If the argument is a basic program, caller cannot check for correctness except by effectively writing his own parser. So bad grammar needs to be handled as normal flow control.
Reason 4 is malfunctioning hardware. Nothing much you can do except shunt the error up, unless you are familiar with the specific device.
Reason 5 is an internal programming error. By definition there is no correct behaviour because your own code is not correct. But you often need to fudge or throw out degenerate cases in geometry, for example.
The goto error_exit method is the one I favour, however. It keeps the one point of entry . and of exit principle essentially intact, without introducing artificial nesting for memory allocation errors that are less likely to happen than the computer breaking.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With