Sunday, April 10, 2005

Fortify Your Loops



Excel-web sharingThis is another post in a continuing, yet oddly sporadic, series of entries on building reliable software. Here's another outrageous tenet of my philosophy:

Ban the While Loop

Yes, that's right: ban the while loop. Get rid of any while loops in your code. Today. Here's why.

Consider the following, oh-so-typical code:

myQuery.FetchFirst();
while (!myQuery.IsEndOfFile()) {
    ... processing steps ...
    myQuery.FetchNext();
}


What's wrong with that? Nothing you say? Au contraire, mon frere. Consider the jamoke who comes after you and adds some additional logic, like so:

myQuery.FetchFirst();
while (!myQuery.IsEndOfFile()) {
    ... processing steps ...
    if (bSkipRecord) {
        continue;
    }

    ... processing steps ...
    myQuery.FetchNext();
}


Guess what? If the boolean bSkipRecord ever gets set, you're in infinite-loop-land and you might as well go out for coffee and a cigarettes -- indefinitely -- while this code runs and runs and runs... basically like the Energizer Bunny plugged into a 220-volt outlet.

So, what do we do in cases like this instead of a while loop? Basically, fortify all of your loops. Make them into for loops.

for (myQuery.FetchFirst(); !myQuery.IsEndOfFile(); myQuery.FetchNext()) {
    ... processing steps ...
    if (bSkipRecord) {
        continue;
    }

    ... processing steps ...
}


Now when Einstein adds his logic, we no longer have the catastrophic result of the system hanging (or an internal denial-of-service attack, as I like to call it).

Going a step further, we can fail-safe the loop. By "fail-safing", I mean assigning a maximum number of loop iterations and recording an error if we hit that maximum. This serves two purposes: to short-circuit a possible infinite loop and to detect the fact that the loop constraint did not work as intended.

for (ixCount = 0, myQuery.FetchFirst();
        !myQuery.IsEndOfFile() && ixCount < MAX_TBL_COUNT;
        myQuery.FetchNext(), ixCount++) {
    ... processing steps ...
    if (bSkipRecord) {
        continue;
    }
    ... processing steps ...
}
if (ixCount >= MAX_TBL_COUNT) {
    // Note that our loop did not work as intended!
}


So, I guess we can boil this lesson down to two tenets: (a) fortify your loops; and (b) fail-safe your loops.
 

No comments: