EmbeddedRelated.com
Blogs

Short Circuit Execution vs. Unit Testing

The key to effective communication is to say what you mean and avoid ambiguity.  Words and phrases with multiple meanings can confuse your audience and hinder communication. That’s why so many programmers prefer writing code to writing specifications: written human language introduces ambiguity and subsequently, confusion. Code only has one interpretation, period. This doesn’t, however, ensure that the right message is getting through. Code can, indeed, only do one thing, but that doesn’t mean it does what the programmer intended it would.  Take, for example, this little snippet of code:

if( TRUE == a && TRUE == conditionalFunction() )
{
	…
}

This would seem to be straightforward: if a is TRUE and the return of the conditionalFunction function is TRUE the statements inside the body of the if will execute, but if either of the conditions is FALSE, the body won’t execute.

Sadly, this is not straightforward: the code and its description paragraph are not equivalent. To see why, we have to dig a bit beneath the surface of C to see what’s going on.

 The problem here is a behavior of C (I hesitate to label it either a feature or a bug) called short circuit evaluation . Short circuit evaluation is a method employed by C and several other programming languages which reduces the amount of work they’re to do when evaluating boolean expressions.  Example:

if (TRUE == a || TRUE == b)

If a is TRUE there’s no reason to evaluate b at all. Alternatively:

if (TRUE == a && TRUE == b)

If a is FALSE, there’s no reason to evaluate b. Short circuit evaluation changes this if statement:

if( TRUE == a && TRUE == b)
{
	c= TRUE;
}
else
{
	c=FALSE;
}

into this equivalent series of statements:

c=FALSE;

if(TRUE == a)
{
	if(TRUE == b)
	{
		c=TRUE;
	}
}



This is a great idea and can save time while producing the same logical result.

 Testing Woes

 Look at that last sentence - logical result. The c variable is always the correct value in the end despite the fact that not all of the conditions are evaluated. But short-circuit evaluation does not produce the same expected results overall compared to non-short circuited evaluation. Take this example:

if( TRUE == a && TRUE == conditionalFunction() )
{
	c= TRUE;
}
else
{
	c=FALSE;
}

If you were writing a unit test for this snippet of code you’d want to do decision coverage on the complete truth table to ensure that both of the conditions have an effect on the output: 

Test Case    a conditionalFunction return c
0 FALSE FALSE FALSE
1 FALSE TRUE FALSE
2 TRUE FALSE FALSE
3 TRUE TRE TRUE

This is a great logical test, but in order to completely test the functionality of this snippet of code you also have to verify that conditionalFunction is called. Typically this is done with a stub function replacing the actual conditionalFunction. The stub function will usually just increment a variable to show that it was called whenever it’s called and return whatever you want. Like this:

bool conditionalFunction(...)
{
	call_count_conditionalFunction++;
	return b;
}

Then your expected results change to this:

Test Case    a b c call_count_conditionalFunction
0 FALSE FALSE FALSE 1
1 FALSE TRUE FALSE 1
2 TRUE FALSE FALSE 1
3 TRUE TRUE TRUE 1

But what you’ll get is this: 

Test Case    a b c call_count_conditionalFunction
0 FALSE FALSE FALSE 0
1 FASEL TRUE FALSE 0
2 TRUE FALSE FALSE 1
3 TRUE TRUE TRUE 1

Note the two bolded zeros - these represent test failures! The reason for this is that short-circuit evaluation has changed your code to this: 

c=FALSE;
if(TRUE == a)
{
	if(TRUE == conditionalFunction())
	{
		c=TRUE;
	}
}

conditionalFunction is not called in the first two cases because of short-circuit evaluation, so if you write your unit test and expect that it will be called each time you’ll get a failure which would be hard to diagnose (if you hadn’t read this article). 

You might protest: who cares if the function is called or not as long as the code produces the correct result?  That is a good point but it carries with it an assumption: that conditionalFunction has no side-effects. If conditionalFunction writes any global variables, reads a hardware register that clears a bit as a side effect, sends a serial message or does anything other than read variables and return a result you may not see the behavior you expect from your software. This is a bug that a unit test won’t necessarily catch but will prove very frustrating later during software integration. 

Given this, it’s best to assume that the call to conditionalFunction is necessary and shouldn’t be skipped. There are several possibilities for fixing this issue: 

  • Place the function call first in the conditional list - This will only work if you have one function call in your conditional list. Otherwise, no dice. 
  • Change your expected results such that you expect no calls to conditionalFunction in the first two cases - This could be a nightmare for maintainability. If you change the order of conditionals later you’ll have to remember to change your expected results as well. Without appropriate documentation of the reasons for the odd-looking expected results you’ll probably end up chasing your tail for half a day figuring out if there’s a grenade buried somewhere in the code or not. Additionally, this only works out if conditionalFunction has no side effects that you’re relying on. 
  • Use a temporary variable for the result of the call to conditionalFunction - This is the preferred method. Change the code to this:
bool retVal = conditionalFunction();

if(TRUE == a && TRUE == retVal)
{
	c=TRUE;
}
else
{
	c= FALSE;
}

This is the best of all worlds:

  • The behavior of the function matches what you would expect (i.e., conditionalFunction is always called), so any effects always occur.
  • The logic is equivalent
  • The code is neater and easier to read.

Always be certain of what your code is doing vs. what you expect. Don’t light the forest on fire. Eat your vegetables.


[ - ]
Comment by Fernando37August 11, 2014
short circuit execution = lazy evaluation !!

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: