Guideline RP7: Reorganize loops to consolidate and reduce code
The basic structure of a loop in a program consists of some initialization, a number of iterations,
and a termination.
Even with a very simple loop we can be parsimonious with our code. The example shows the simplest loop structure:
an integer counter which is initialized, incremented, and tested as the loop progresses.
The statements dealing with just the loop bookkeeping are highlighted.
Notice that just by changing from a while
to a for
loop,
we can save 50% to 67% in bookkeeping lines of code!
(In case my hands were moving too fast, the range is due to how you might define lines of code.
Some will say there are 4 lines in the original, 2 lines in the revision, hence 50% reduction.
Others, arguing that a line with just a closing brace is not really code,
hence we go from 3 lines to 1 line, yielding a 67% reduction.)
counter = startVal; while (counter < endVal) { // ... do some processing ... counter++; }
for (counter = startVal; count < endVal; count++) { // ... do some processing ... }
But there is further advantage to be gained here! Not only do we save lines of code, the revised code presents its intent more clearly to the reader. Let me show the same example turning off the "academic interest" filter, and perhaps making it look slightly worse on purpose. Looking at the "before" code, can you spot the loop bookkeeping? Certainly, you can, but compare the ease with which you can spot it in the "after" code-- it is all right there, in one line.
Well-written code would dictate that in the "before" code the loop initialization should be immediately before the loop with no intervening lines of code, and the loop incrementation should be at the very end of the loop, not in the middle. But, there is no enforcement of that by the language, so one could be sloppy and get away with it. (Indeed, there are times when one must...)
counter = startVal; index = obj.prefetch(s1, s2); data[index div 2] = "new text string" + "[" + stuff + "]"; while (counter < endVal) { // ... do some processing ... check(A, index); check(B, index); counter++; if (counter == index) { check(C, index); } }
index = obj.prefetch(s1, s2); data[index div 2] = "new text string" + "[" + stuff + "]"; for (counter = startVal; count < endVal; count++) { // ... do some processing ... check(A, index); check(B, index); if ((counter+1) == index) { check(C, index); } }
Often, as is the case particularly with I/O operation, loop iteration may be based on something other than a simple counter. The example of reading a file a bit at a time is typical. As shown in the example, we keep reading a buffer of data and processing it until we use it up. A common practice, as shown in the original code, is to invoke the read function before the loop, then again inside the loop. It is better to have only one call if you can engineer it that way (and there are plenty of times you cannot). So the revised code in the example shows a technique which takes less space, removes duplication, and centralizes loop bookkeeping to one line of code. Oh, and it is also easier to understand. "Pardon?" you say? Some people will strongly disagree with that last point. But I retort that if you are fluent in the language, the second version, though a more complex single line, is easier to understand, because you have more information on the one line-- you know where the characters are read from, where they're stored, and what you'll get when you're done.
charsRead = reader.read(buffer); while (charsRead != -1) { process(buffer); charsRead = reader.read(buffer); }
while ((charsRead = reader.read(buffer)) != -1) { process(buffer); }