Loop Control

Loop Exits

The single most essential component of loop control is the point of exit. There are places where you don't want an exit. But the majority of loops exit under specified conditions. Possibly the most important single line of code in a looping operation is detecting the exit condition.

Infinite Loops Do Not Exit!

If your loop is intended to exit, then it is not infinite.

This is a true binary condition. Either you want your loop to exit, or you don’t. There are no alternatives here.

Few things can be more simple or self evident. Yet the use of an infinite loop when there are specific exit criteria runs rampant. To create an infinite loop when you have specific exit cirteria in mind is oxymoronic. You have contradicted yourself even before you type in the first character of your working code.

In the first edition of McConnel’s Code Complete, Chapter 15, Controlling Loops, he uses this example:

   FOREVER
   {
   GetNextRating( &RatingIncrement );
   Rating = Rating + RatingIncrement;
   If( !(Score < TargetScore && RatingIncrement !=0) )
      break;

   GetNextScore( &ScoreIncrement );
   Score = Score + ScoreIncrement;
   }

He begins the loop by stating it is forever, then has an explicit exit point. Certainly we can figure this out, but if you have a planned and explicit exit point, then do not start the loop with FOREVER! (
Note: The book uses the following definition: #define for(;;)

Before we continue, I must point out that the escape if clause of this fragment is poorly designed. That statement is discussed in another another page on this site titled Think Positive found here.

We will return to this example shortly.

Loops With Exits Must Exit

While the statement is a tautology, not all exiting loops are guaranteed to exit. When you write an exiting loop, within the confines of that loop, there should be a guaranteed exit. Any code that depends on external events, such as the returned value from a function call, does not have a guaranteed exit.

We may write a loop that depends on the return value of a function call to exit. We may even guarantee that the function will produce the exit condition. But we cannot guarantee that our successors won't find another use for that function and modify it for their needs. While working in that function, the dependency of your loop on the internals of that function may not be apparent. The code of the function is clearly separated from the loop control back somewhere in one of the function callers. The end result is that you do not have a guaranteed exit.

Returning to the above example, let’s add a safety valve without changing any of the working code within the loop.

   const int ITERATION_LIMIT = 100;
   int       iteration_count = 0;
   do
     {
     GetNextRating( &RatingIncrement );
     Rating = Rating + RatingIncrement;
     If( ! (Score < TargetScore && RatingIncrement !=0) )
         break;
         
     GetNextScore( &ScoreIncrement );
     Score = Score + ScoreIncrement;
     }while( ++iteration_count < ITERATION_LIMIT ) 

From the perspective of our loop control, the function

       GetNextScore( &ScoreIncrement );
should be considered a black box. We don't know the internals. Someone may want the ability to fix an error by allowing it to return a negative value. Or they just may make an error. In the same manner, from the perspective of this loop, we do not have control over function:
        GetNextRating( &RatingIncrement );

The addition of the variable iteration_count, its limiting constant ITERATION_LIMIT, and its independent increment function within the while loop test condition, provides a guaranteed exit. That exit is clear and evident to the most inexperienced coder. Note that not one line of the original code within the loop was changed. If that code had been much more complicated, it would still be undisturbed.

After the loop, it is a good idea to test iteration_count. If the limit was reached, then we should display and/or log an error to make us aware that the safety valve was activated.

BTW: Recognizing that this is a code fragment, and recalling the original definition of the structures that differentiate procedures and functions, this line of code:
    GetNextRating( &RatingIncrement ); 
is structured as a procedure, but operationally its a function. With only one value to return and no input values (none shown in this fragment), we gain some clarity by writing:
    RatingIncrement = GetNextRating();

On the flip side, we should note that McConnell’s example here does a better job of nameing his functions and variables than many authors.

Every coder has a long list of questions to review before declaring that their code is ready for release. If you don't have a written list, please start one. Here are a couple of additions:

1) Does our code contradict itself? If yes, fix it. Your code should not only be consistant, it should look consistant.

2) Does each loop have a guarenteed exit? If not, fix it.

Remember: Always have positive control over your loops.


May 2008