Michael Coughlan
Page 17
Example 6-3. Model for a Gravity-Driven COBOL Program
P1.
statement
statement
statement
P2.
statement
statement
P3.
statement
IF cond GO TO P2
statement
statement
IF cond GO TO P3
P4.
statement
IF cond GO TO P2
statement
statement
STOP RUN
111
Chapter 6 ■ Control StruCtureS: IteratIon
Closed Subroutines
A closed subroutine is a named block of code that can only be executed by invoking it by name. Control cannot
“fall into” a closed subroutine. A closed subroutine can usually declare its own local data, and that data cannot be accessed outside the subroutine. Data in the main program can be passed to the subroutine by means of parameters specified when the subroutine is invoked. In C and Modula-2, procedures and functions implement closed subroutines.
In Java, methods are used.
COBOL Subroutines
COBOL supports both open and closed subroutines. Open subroutines are implemented using the first format of the PERFORM verb. Closed subroutines are implemented using the CALL verb and contained or external subprograms.
You learn about contained and external subprograms later in the book.
■ ISO 2002 ISo 2002 CoBol provides additional support for closed subroutines in the form of methods. Methods in CoBol bear a very strong syntactic resemblance to contained subprograms.
Why Use Open Subroutines?
The open subroutines represented by paragraphs (and sections) are used to make programs more readable and
maintainable. Although PERFORMed paragraphs are not as robust as the user-defined procedures or functions found in other languages, they are still useful. They allow you to partition code into a hierarchy of named tasks and subtasks without the formality or overhead involved in coding a procedure or function. COBOL programmers who require the protection of that kind of formal partitioning can use contained or external subprograms.
Partitioning a task into subtasks makes each subtask more manageable; and using meaningful names for the
subtasks effectively allows you to document in code what the program is doing. For instance, a block of code that prints report headings can be removed to a paragraph called PrintReportHeadings. The details of how the task is being accomplished can be replaced with a name that indicates what is being done.
Consider the partitioning and documentation benefits provided by the program skeleton in Example 6-4. The
skeleton contains no real code (only PERFORMs and paragraph names), but the hierarchy of named tasks and subtasks allows you to understand that the program reads through a file containing sales records for various shops and for each shop prints a line on the report that summarizes the sales for that shop.
Example 6-4. Program Skeleton
PrintSummarySalesReport.
PERFORM PrintReportHeadings
PERFORM PrintSummaryBody UNTIL EndOfFile
PERFORM PrintFinalTotals
STOP RUN.
PrintSummaryBody.
PERFORM SummarizeShopSales
UNTIL ShopId <> PreviousShopId
OR EndOfFile
PERFORM PrintShopSummary
SummarizeShopSales.
Statements
112
Chapter 6 ■ Control StruCtureS: IteratIon
PrintReportHeadings.
Statements
PrintShopSummary.
Statements
PrintFinalTotals.
Statements
Obviously, it is possible to take partitioning to an extreme. You should try to achieve a balance between making the program too fragmented and too monolithic. As a rule of thumb, there should be a good reason for creating a paragraph that contains five statements or fewer.
PERFORM NamedBlock
This first format of the PERFORM (see Figure 6-1) is not an iteration construct. It simply instructs the computer to transfer control to an out-of-line block of code (that is, an open subroutine). The block of code may be a paragraph or a section. When the end of the block is reached, control reverts to the statement (not the sentence) immediately following the PERFORM.
Figure 6-1. Metalanguage for PERFORM format 1
In Figure 6-1, StartblockName and EndblockName are the names of paragraphs or sections. PERFORM..THRU
instructs the computer to treat the paragraphs or sections from StartblockName TO EndblockName as a single block of code.
PERFORM s can be nested. A PERFORM may execute a paragraph that contains another PERFORM, but neither direct nor indirect recursion is allowed. Unfortunately, this restriction is not enforced by the compiler, so a syntax error does not result; but your program will not work correctly if you use recursive PERFORMs.
The order of execution of the paragraphs is independent of their physical placement. It does not matter where you put the paragraphs—the PERFORM will find and execute them.
How PERFORM Works
Listing 6-1 shows a short COBOL program that demonstrates how PERFORM works. The program executes as follows: 1. Control starts in paragraph LevelOne, and the message “Starting to run program” is
displayed.
2. When PERFORM LevelTwo is executed, control is passed to LevelTwo and the statements in
that paragraph start to execute.
3. When PERFORM LevelThree is executed, control passes to LevelThree. When PERFORM
LevelFour is executed, the message “Now in LevelFour” is displayed.
4. When the end of LevelFour is reached, control returns to the statement following the
PERFORM that invoked it, and the message “Back in LevelThree” is displayed.
113
Chapter 6 ■ Control StruCtureS: IteratIon
5. When LevelThree ends, control returns to the statement following the PERFORM, and the
message “Back in LevelTwo” is displayed. Finally, when LevelTwo ends, control returns to
paragraph LevelOne, and the “Back in LevelOne” message is displayed.
6. When STOP RUN is reached, the program stops.
Notice that the order of paragraph execution is independent of physical placement. For instance, although the paragraph LevelTwo comes after LevelThree and LevelFour in the program text, it is executed before them.
As I mentioned earlier, although PERFORMs can be nested, neither direct nor indirect recursion is allowed. So it would not be valid for paragraph LevelThree to contain the statement PERFORM LevelThree . This would be direct recursion. Neither would it be valid for LevelTwo to contain the statement PERFORM LevelOne. This would be indirect recursion because LevelOne contains the instruction PERFORM LevelTwo.
A frequent mistake made by beginning COBOL programmers is to forget to include STOP RUN at the end of
the first paragraph. Example 6-5 shows the output that would be produced by Listing 6-1 if you forgot to include STOP RUN. From the output produced, try to follow the order of execution of the paragraphs.
Example 6-5. Output when STOP RUN is missing
> Starting to run program
> > Now in LevelTwo
> > > Now in LevelThree
> > > > Now in LevelFour
> > > Back in LevelThree
> > Back in LevelTwo
> Back in LevelOne
> > > > Now in LevelFour
> > > Now in LevelThree
> > > > Now in LevelFour
> > > Back in LevelThree
> > Now in LevelTwo
> > > Now in LevelThree
> > > > Now in LevelFour
> > > Back in LevelThree
> > Back in LevelTwo
Listing 6-1. Demonstrates How PERFORM Works
IDENTIFICATION DIVISION.
PROGRA
M-ID. Listing6-1.
AUTHOR. Michael Coughlan.
PROCEDURE DIVISION.
LevelOne.
DISPLAY "> Starting to run program"
PERFORM LevelTwo
DISPLAY "> Back in LevelOne"
STOP RUN.
LevelFour.
DISPLAY "> > > > Now in LevelFour".
LevelThree.
114
Chapter 6 ■ Control StruCtureS: IteratIon
DISPLAY "> > > Now in LevelThree"
PERFORM LevelFour
DISPLAY "> > > Back in LevelThree".
LevelTwo.
DISPLAY "> > Now in LevelTwo"
PERFORM LevelThree
DISPLAY "> > Back in LevelTwo".
PERFORM..THRU Dangers
One variation that exists in all the PERFORM formats is PERFORM..THRU. When you use PERFORM..THRU, all the code from StartblockName to EndblockName is treated as a single block of code. Because PERFORM..THRU is generally regarded as a dangerous construct, it should only be used to PERFORM a paragraph and its immediately succeeding paragraph exit.
The problem with using PERFORM..THRU to execute a number of paragraphs as one unit is that, in the
maintenance phase of the program’s life, another programmer may need to create a new paragraph and may
physically place it in the middle of the PERFORM..THRU block. Suddenly the program stops working correctly.
Why? Because now PERFORM..THRU is executing an additional, unintentional, paragraph.
Using PERFORM..THRU Correctly
The warning against using PERFORM..THRU is not absolute, because when used correctly, PERFORM..THRU can be very useful.
In COBOL there is no way to break out a paragraph that is the target of a PERFORM. All the statements have to be executed until the end of the paragraph is reached. But sometimes, such as when you encounter an error condition, you do not want to execute the remaining statements in the paragraph. This is a circumstance when PERFORM..THRU can be handy.
Consider the program outline in Example 6-6. In this example, control will not return to Begin until SumEarnings has ended, but you do not want to execute the remaining statements if an error is detected. The solution adopted is to hide the remaining statements behind an IF NoErrorFound statement. This might be an adequate solution if there were only one type of error; but if there is more than one type, then nested IF statements must be used. This quickly becomes unsightly and cumbersome.
Example 6-6. Using IFs to Skip Statements When an Error Is Detected
PROCEDURE DIVISION.
Begin.
PERFORM SumEarnings
STOP RUN.
SumEarnings.
Statements
Statements
IF NoErrorFound
Statements
Statements
IF NoErrorFound
Statements
Statements
Statements
END-IF
END-IF.
115
Chapter 6 ■ Control StruCtureS: IteratIon
In Example 6-7, PERFORM..THRU is used to deal with the problem in a more elegant manner. The dangers of
PERFORM..THRU are ameliorated by having only two paragraphs in the target block and by using a name for the second paragraph that clearly indicates that it is bound to the first.
Example 6-7. Using PERFORM..THRU and GO TO to Skip Statements
PROCEDURE DIVISION
Begin.
PERFORM SumEarnings THRU SumEarningsExit
STOP RUN.
SumEarnings.
Statements
Statements
IF ErrorFound
GO TO SumEarningsExit
END-IF
Statements
Statements
IF ErrorFound
GO TO SumEarningsExit
END-IF
Statements
Statements
Statements
SumEarningsExit.
EXIT.
When the statement PERFORM SumEarnings THRU SumEarningsExit is executed, both paragraphs are performed as if they are one paragraph. The GO TO jumps to the exit paragraph, which, because the paragraphs are treated as one, is the end of the block of code. This technique allows you to skip over the code that should not be executed when an error is detected.
The EXIT statement in SumEarningsExit is a dummy statement. It has absolutely no effect on the flow of control. It is in the paragraph merely to conform to the rule that every paragraph must have one sentence. It has the status of a comment.
The PERFORM..THRU and GO TO constructs used in this example are dangerous. GO TO in particular is responsible for the “spaghetti code” that plagues many COBOL legacy systems. For this reason, you should use PERFORM..THRU
and GO TO only as demonstrated in Example 6-7.
PERFORM..TIMES
PERFORM..TIMES (see Figure 6-2) is the second format of the PERFORM verb.
Figure 6-2. Metalanguage for PERFORM format 2
116
Chapter 6 ■ Control StruCtureS: IteratIon
This format has no real equivalent in most programming languages, perhaps because of its limited usefulness. It simply allows a block of code to be executed RepeatCount#il times before returning control to the statement following PERFORM.
Like the other formats of PERFORM, this format allows two types of execution:
• Out-of-line execution of a block of code
• Inline execution of a block of code
Example 6-8 gives some example PERFORM..TIMES statements. These examples specify the RepeatCount using
both literals and identifiers and show the inline and out-of-line variants of PERFORM.
Example 6-8. Using PERFORM..TIMES
PERFORM PrintBlankLine 10 Times
MOVE 10 TO RepetitionCount
PERFORM DisplayName RepetitionCount TIMES
PERFORM 15 TIMES
DISPLAY "Am I repeating myself?"
END-PERFORM
Inline Execution
Inline execution will be familiar to programmers who have used the iteration constructs (while, do/repeat, for) of most other programming languages. An inline PERFORM iteratively executes a block of code contained within the same paragraph as the PERFORM. That is, the loop body is inline with the rest of the paragraph code. The block of code to be executed starts at the keyword PERFORM and ends at the keyword END-PERFORM (see Listing 6-2).
Listing 6-2. Demonstrates PERFORM..TIMES and Inline vs. Out-of-Line Execution
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing6-2.
AUTHOR. Michael Coughlan.
*> in-line and out-of-line PERFORM..TIMES
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NumOfTimes PIC 9 VALUE 5.
PROCEDURE DIVISION.
Begin.
DISPLAY "About to start in-line Perform"
PERFORM 4 TIMES
DISPLAY "> > > > In-line Perform"
END-PERFORM
DISPLAY "End of in-line Perform"
DISPLAY "About to start out-of-line Perform"
PERFORM OutOfLineCode NumOfTimes TIMES
DISPLAY "End of out-of-line Perform"
STOP RUN.
OutOfLineCode.
DISPLAY "> > > > > Out-of-line Perform".
117
Chapter 6 ■ Control StruCtureS: IteratIon
■ ANS 85 In-line PERFORMs were only introduced as part of the anS 85 CoBol specification. In older legacy systems, the loop body is always out of line.
Out-of-Line Execution
In an out-of-line PERFORM, the loop body is a separate paragraph or section. This is the equivalent, in other languages, of having a procedure, function, or method invocation inside the loop body of a while or for construct.
When a loop is required, but only a few statements are involved, you should use an inline PERFORM. When
a loop is required, and the loop body ex
ecutes some specific task or function, out-of-line code should be used.
The paragraph name chosen for the out-of-line code should identify the task or function of the code.
PERFORM..UNTIL
PERFORM..UNTIL (see Figure 6-3) is the third format of the PERFORM verb. This format implements both pre-test and post-test iteration in COBOL. It is the equivalent of Java’s while and do..while or Pascal’s While and Repeat..Until looping constructs.
Figure 6-3. Metalanguage for PERFORM format 3
Pre-test and post-test iteration structures seem to be strangely implemented in many languages. Some languages confuse when the test is done with how the terminating condition is tested (Pascal’s While and Repeat structures, for example). In many languages, the test for how the loop terminates emphasizes what makes the loop keep going, rather than what makes it stop. Although this may make formal reasoning about the loop easier, it does not come across as an entirely natural way of framing the question. In your day-to-day life, you do not say, “Heat the water while the water is not boiled” or “Pour water into the cup while the cup is not full.”
Pre-test and post-test looping constructs are one area where COBOL seems to have things right. Whether the loop is pre-test or post-test, it is separated from how the terminating condition is tested; and the test for termination emphasizes what makes the loop stop, rather than what makes it keep going. In COBOL you might write
118
Chapter 6 ■ Control StruCtureS: IteratIon
PERFORM ProcessSalesFile WITH TEST BEFORE
UNTIL EndOfSalesFile
or
PERFORM GetNextCharacter WITH TEST AFTER