Tags

, , , , ,

I was looking through some questions on StackOverflow last week when I came across a curious answer. In it there was a link to a coding standard that forbade the use of the “continue” keyword. There was even positive support for this answer. Needless to say, I was aghast. It’s like the arguments against “goto” have run amok and started targeting other language keywords.

Naturally I can’t just condemn such principles without showing why it is a problem. Looking through my own code it is certainly a commonly used keyword. While alternatives do exist, “continue” is an essential aspect of flow control which helps to reduce code and improve clarity. Instead of trying to prove its worth, I’ll instead go the other direction and show the consequences of not using “continue”.

Reductio ad Falsam

Avoiding the minor arguments, let’s jump right to a logical hypothesis: forbidding “continue” also requires forbidding any non-tail “return” statement. That is, if you believe using “continue” is wrong, logically our argument must also forbid the use of using a “return” statement anywhere other than as the last statement of a function. They both prematurely interrupt the flow of execution for a particular block of code.

We can show this using some simple code examples. First let’s start with a very typical loop using the the “continue” keyword.

for( int i : range )
{
    if( some_cond( i ) )
		continue;

	...
}

Now let’s use a function instead of the inline loop body. I have chosen this approach since as it is a common recommendation.

void process( int i, ... )
{
	if( some_cond( i ) )
		return;

	...
}

for( int i : range )
	process( i, ... );

By doing this you have not modified the meaning of the code at all. An optimizing compiler could very well compile both code bits exactly the same. You have however increased the size of the code, and moved the loop body logic away from the loop — some people may find this lowers readability. Take this a step further in a language that supports closures (I’ll attempt to use a C++11 lambda):

Ignoring some syntactic bloat, this looks like the original code sample, albeit without a “continue” statement. By using a closure we can meet the requirement yet still produce the same code.

for( int i : range )
	[](int i) {
		if( some_cond( i ) )
			return;

		...
	} (i);

These code samples are to illustrate that using a non-tail “return” can be identical in meaning to using a “continue” statement. This would imply that whatever reason one would have to forbid the use of “continue” would also apply to this use of the “return” keyword. My intuition is that if I took this line of reasoning far enough the omission of “continue” might actually require a pure functional programming approach. Simply forbidding the use of a non-tail “return” is likely enough to show the absurdity of the original requirement.

Practical Example

By way of example let me show how not allowing “continue” will produce what I consider to be bad code. Looking through my recent project I found a sample that looks somewhat like the below (it scans a list of logical objects looking for those which can be processed).

for( object_id_t id : proc_list )
{
	object * obj = find_object( id );
	if( !obj )
		continue;

	if( obj->is_active() )
		continue;
		
	time_t elapsed = now() - obj->begin;
	if( elapsed < timeout )
		continue;

	...
}

If we forbid “continue” (and non-tail “return”) this code would have to be convereted using embedded “if” blocks.

for( object_id_t id : proc_list )
{
	object * obj = find_object( id );
	if( obj )
	{
		if( !obj->is_active() )
		{
			time_t elapsed = now() - obj->begin;
			if( elapsed >= timeout )
			{
				...
			}
		}
	}
}

High levels of block depth are difficult to read. It is difficult to visually identify where the block ends and what the chain of conditions is. Converting this to a series of functions would result in a lot of nearly trivial functions. A large number of trivial forwarding functions are also hard to read as it is difficult to follow the code. I find the first code example, using “continue”, to be a very clear way to write the code.

Conclusion

Often requirements get taken out of context, and this might very well be one of those situations. The StackOverflow answer picked one aspect out of a very large document. Out of context, and without rationale I definitely see the requirement (of not using “continue”) to be more harmful than beneficial. When I looked through the requirements, most of them actually have a “Rationale” entry. This is very good practice; you should always document why you have certain restrictions. Strangely however the requirement on “continue” was lacking such a rationale, so we have no idea why it is not allowed — and in particular why they still allow non-tail “return” statements.