Untangling with Nested Loops

Start with an ugly loop (as presented on an earlier page) with multiple recurrent GO TOs:
 2000-PROCESS-RECORD. 
* 
     PERFORM 2900-READ-NEXT-RECORD. 
* 
     IF EOF 
        GO TO 2000-EXIT
     ELSE IF JK-ORD-REC 
        PERFORM 2700-PROCESS-ORD-REC
        GO TO 2000-PROCESS-RECORD.
*
     IF NOT ORD-POSTED
        GO TO 2000-PROCESS-RECORD.
*
     IF JK-AFTER-ZIP IS NUMERIC
     AND JK-AFTER-ZIP NOT = ALL ZEROS
        GO TO 2000-PROCESS-RECORD.
*
     PERFORM 2100-REPORT-BAD-ZIP.
     GO TO 2000-PROCESS-RECORD. 
*
 2000-EXIT.
     EXIT.
One way to untangle this loop is to rewrite it as one or more loops nested inside another.

A purely mechanical transformation of the code may not be very satisfying if it does not reflect a sound instinct for what the program is trying to do. Let us therefore consider this example in more detail.

How the Example Works

The input file represents a series of orders for telephone service. Each order consists of a single order record followed by one or more WTN (Working Telephone Number) records, one for each line number affected by the order.

Each order record carries a flag indicating whether the order is posted, cancelled, or in error status. It also carries various other scraps of information about the type of customer and the kind of service provided.

Each WTN record contains a ZIP code field reflecting the geographical location of the telephone. Usually all the line numbers for a given account are at the same location, but not always. Specialized vendor software assigns ZIP codes on the basis of the customer's address. Sometimes, however, this software can't assign a ZIP code, for any of various reasons. These cases require manual intervention.

The example program is supposed to report all the line numbers to which we have been unable to assign a ZIP code. In these cases the ZIP code field is filled either with zeros or blanks.

However, we only care about posted orders. Cancelled orders obviously don't matter, and the orders in error status, by definition, are invalid anyway. Moreover, we don't care about mobile telephones, since a ZIP code is meaningless for them. We can screen out all of these irrelevancies on the basis of information in the order record.

Nesting in the Example

One approach is for the main loop to process an order rather than a single record. Within the outer loop we can have one loop to find the next order record, and another to process each WTN record in an eligible order. Assuming we have appropriate 88-levels in the order record, the resulting code might look like this:
 2000-PROCESS-ORDER. 
*
* find the next order record, if we don't already have one
*
     PERFORM 2900-READ-NEXT-RECORD
        UNTIL EOF OR JK-ORD-REC.
*
* process it; then read past it
*
     IF NOT EOF
        PERFORM 2700-PROCESS-ORD-REC
        PERFORM 2900-READ-NEXT-RECORD
*
* process each WTN record in an eligible order 
*
        IF (ORD-POSTED AND NOT ORD-MOBILE-SERVICE)
           PERFORM UNTIL EOF OR JK-ORD-REC
*
              IF JK-AFTER-ZIP NOT NUMERIC
              OR JK-AFTER-ZIP = ALL ZEROS
                 PERFORM 2100-REPORT-BAD-ZIP
              END-IF
              PERFORM 2900-READ-NEXT-RECORD
*
           END-PERFORM.
*
* either exit the loop or continue
*
    IF EOF
       GO TO 2000-EXIT
    ELSE
       GO TO 2000-PROCESS-RECORD. 
*
 2000-EXIT.
     EXIT.
The revised version has two inner loops, coded in-line in order to make the nesting explicit. In a real program I would probably pull the second loop into a separate paragraph.

In any case, only one recurrent GO TO remains.

Summary

You jumped to this summary without studying every word, didn't you? I don't blame you. Examples like this are tedious to read. Heck, they're tedious to write.

This very tedium illustrates my main point.

When you restructure a loop into nested loops, you have to pay close attention to the dirty details of the application. You can't just rearrange the code mechanically. That's why I had to explain so many boring details.

Because this process is not mechanical, it is error-prone. I designed this example to be simple, but it still took me a while to get it right. At least I think I got it right.


[home]COBOL Home [style forum]COBOL Style Forum [spaghetti]Spaghetti code