Chapter 21 Quiz: COBOL Coding Standards and Best Practices
Test your understanding of COBOL coding standards, structured programming, and best practices. Each question is followed by a hidden answer -- try to answer before revealing it.
Question 1
What is the primary reason coding standards are more important in COBOL than in many other programming languages?
Show Answer
COBOL programs have **exceptionally long lifespans** -- often 20, 30, or even 40+ years in production. During that time, the original programmer retires or moves on, and dozens of other programmers will maintain the code. Standards ensure that any competent COBOL programmer can understand, modify, and debug the program without deciphering the original author's personal style. In languages where programs are rewritten every few years, inconsistent style is an inconvenience. In COBOL, it is a maintenance crisis that costs organizations thousands of hours over a program's lifetime.Question 2
Which of the following is the BEST variable name for a counter that tracks how many records have been written to the output file?
a) X
b) WS-CTR
c) CT-OUTPUT-RECORDS-WRITTEN
d) OUTPUT-COUNTER
e) W01-WRT-CT
Show Answer
**c) CT-OUTPUT-RECORDS-WRITTEN**. This name follows all best practices: - Uses the standard prefix **CT-** for counters - Uses hyphens to separate words - Is self-documenting (you know exactly what it counts without reading any comments) - Is specific (not just "counter" but which counter) The other options fail for these reasons: - a) Single-character name, completely opaque - b) Too generic -- which counter? - d) Missing the standard prefix - e) Uses a non-standard prefix (W01-) and excessive abbreviationQuestion 3
True or False: In modern COBOL development, GO TO statements should be completely eliminated from all programs.
Show Answer
**Mostly True**, with a minor exception. The structured programming principle says that every block of code should have a single entry point and a single exit point, achieved through PERFORM, EVALUATE, and IF/ELSE. GO TO statements create multiple exit paths, making programs harder to understand and debug. The one commonly accepted exception is **GO TO used for early exit within a paragraph to a common exit point** (sometimes called the "exit paragraph" pattern): 3000-VALIDATE.
IF WS-FIELD-1 = SPACES
MOVE 'ERR-1' TO WS-ERROR-CODE
GO TO 3000-VALIDATE-EXIT
END-IF
...
3000-VALIDATE-EXIT.
EXIT.
However, even this pattern can usually be replaced with nested IF or EVALUATE. In new code, GO TO should be avoided entirely. In legacy code, removing GO TO statements should be done carefully during planned refactoring, not as a drive-by change.
Question 4
What is the standard paragraph numbering convention in COBOL?
Show Answer
The standard convention uses a **four-digit numeric prefix** followed by a descriptive name:0000-MAIN (main control paragraph)
1000-INITIALIZE (first major function)
1100-OPEN-FILES (sub-function of 1000)
1200-LOAD-TABLES (sub-function of 1000)
2000-PROCESS (second major function)
2100-READ-INPUT (sub-function of 2000)
2200-VALIDATE (sub-function of 2000)
2300-CALCULATE (sub-function of 2000)
3000-PROCESS-DETAIL (third major function)
9000-TERMINATE (termination -- always 9xxx)
9100-ERROR-HANDLER (error handling -- 91xx)
9200-ABEND-ROUTINE (abend routine -- 92xx)
The numbering provides three benefits: (1) paragraphs sort in logical order in the listing, (2) the number indicates the hierarchy (1100 is subordinate to 1000), and (3) gaps between numbers (1000, 2000) leave room for inserting new paragraphs without renumbering.
Question 5
What is the maximum recommended length for a single COBOL paragraph?
Show Answer
The widely accepted guideline is **50 lines or fewer** per paragraph (some standards say 30-40 lines). A paragraph should fit on a single screen without scrolling. If a paragraph exceeds this length, it is probably doing too much and should be broken into smaller, single-purpose paragraphs. The rationale: - Short paragraphs are easier to understand at a glance - Short paragraphs have fewer possible execution paths, making them easier to test - Short paragraphs can be reused (via PERFORM) more easily - When a bug is reported in paragraph 3200-CALCULATE-INTEREST, a 30-line paragraph takes minutes to analyze; a 200-line paragraph takes hoursQuestion 6
What are 88-level condition names, and why are they a best practice?
Show Answer
**88-level condition names** are named boolean conditions attached to a data item. They replace literal comparisons with self-documenting names: 05 WS-ACCOUNT-STATUS PIC X(1).
88 ACCT-ACTIVE VALUE 'A'.
88 ACCT-CLOSED VALUE 'C'.
88 ACCT-FROZEN VALUE 'F'.
88 ACCT-PROCESSABLE VALUE 'A' 'P'.
Instead of writing `IF WS-ACCOUNT-STATUS = 'A'`, you write `IF ACCT-ACTIVE`. Benefits:
1. **Self-documenting**: `IF ACCT-ACTIVE` reads like English
2. **Single point of change**: If the code for "active" changes from 'A' to '1', you change one line, not every IF statement
3. **Compound conditions**: `ACCT-PROCESSABLE` groups multiple values into one meaningful test
4. **SET statement**: `SET ACCT-ACTIVE TO TRUE` is clearer than `MOVE 'A' TO WS-ACCOUNT-STATUS`
Question 7
True or False: COPY members (copybooks) should be used for every record layout that is shared between two or more programs.
Show Answer
**True**. This is one of the most important COBOL standards. When two programs reference the same file, the record layout must be defined in a COPY member, not coded inline in each program. Benefits: 1. **Consistency**: All programs use exactly the same field names, sizes, and positions 2. **Single point of change**: When a field is added or modified, the COPY member is changed once, and all programs are recompiled 3. **Error prevention**: The most common cause of S0C7 abends in batch cycles is a record layout mismatch between programs -- one program adds a field, and another program is not updated to match. COPY members prevent this The COPY member is typically stored in a PDS (Partitioned Data Set) and referenced at compile time: 01 CUSTOMER-RECORD.
COPY CUSTREC.
Question 8
What is the difference between a "magic number" and a named constant in COBOL?
Show Answer
A **magic number** is a literal numeric value embedded directly in the PROCEDURE DIVISION without explanation: * BAD: Magic numbers
IF WS-BALANCE < 500
ADD 25.00 TO WS-FEES
IF CT-TRANS > 20
COMPUTE WS-EXCESS = (CT-TRANS - 20) * 0.35
A **named constant** is a WORKING-STORAGE variable with a meaningful name and a VALUE clause:
* GOOD: Named constants
01 WS-MIN-BALANCE-THRESHOLD PIC 9(5)V99 VALUE 500.00.
01 WS-LOW-BALANCE-FEE PIC 9(3)V99 VALUE 25.00.
01 WS-FREE-TRANS-LIMIT PIC 9(3) VALUE 20.
01 WS-EXCESS-TRANS-FEE PIC 9V99 VALUE 0.35.
IF WS-BALANCE < WS-MIN-BALANCE-THRESHOLD
ADD WS-LOW-BALANCE-FEE TO WS-FEES
IF CT-TRANS > WS-FREE-TRANS-LIMIT
COMPUTE WS-EXCESS =
(CT-TRANS - WS-FREE-TRANS-LIMIT)
* WS-EXCESS-TRANS-FEE
Named constants are self-documenting, can be changed in one place, and can be loaded from a configuration file instead of being hardcoded.
Question 9
Why should scope terminators (END-IF, END-READ, END-COMPUTE, etc.) always be used?
Show Answer
Scope terminators prevent ambiguity and reduce bugs in several ways: 1. **Prevent the "dangling ELSE" problem**: Without END-IF, nested IF statements can produce unexpected results because the compiler pairs ELSE with the nearest unmatched IF, which may not be what the programmer intended. 2. **Allow complex inline statements**: Without END-READ, you cannot put an IF statement inside an AT END clause because the period that would end the AT END would also end the IF. 3. **Make maintenance safer**: When adding code after an IF block, a missing END-IF can cause the new code to accidentally become part of the IF's ELSE clause. 4. **Enable the compiler to detect errors**: The compiler can verify that each block is properly closed. 5. **Improve readability**: Scope terminators provide visual boundaries that make the code structure clear. Before COBOL-85 introduced scope terminators, the only way to end a statement scope was with a period. This forced programmers into flat, unstructured code and was a major source of bugs. Scope terminators should be used on **every** statement that supports them.Question 10
What is PERFORM THRU, and why is it discouraged?
Show Answer
**PERFORM THRU** (or PERFORM ... THROUGH) executes a range of consecutive paragraphs: PERFORM 3000-START THRU 3000-EXIT
This executes every paragraph from 3000-START through 3000-EXIT in sequence. It is discouraged because:
1. **Hidden dependencies**: Any paragraph between the start and end is executed, even if it was not intended. If someone later adds a new paragraph between them, it silently becomes part of the PERFORM range.
2. **Difficult to trace**: When debugging, it is not obvious which paragraphs are included in the range without looking at the paragraph order.
3. **Fragile**: Reordering paragraphs or moving them to a different location in the source code changes the behavior of PERFORM THRU.
4. **Unnecessary**: Any PERFORM THRU can be replaced by performing each paragraph individually or by consolidating the logic into a single paragraph with sub-PERFORMs.
The only historical justification for PERFORM THRU was to allow a "GO TO exit" pattern within the range, but this pattern itself is unnecessary with proper structured programming.
Question 11
True or False: WORKING-STORAGE variables should be grouped logically (flags together, counters together, work fields together) rather than listed in the order they are first used.
Show Answer
**True**. Logical grouping makes the WORKING-STORAGE section self-documenting and easier to navigate. The recommended organization is:01 WS-FILE-STATUS-FIELDS (file statuses)
01 WS-FLAGS-AND-SWITCHES (boolean flags, EOF switches)
01 WS-COUNTERS (record counts, loop counters)
01 WS-ACCUMULATORS (running totals)
01 WS-WORK-FIELDS (calculation intermediates)
01 WS-INPUT-RECORD (working copy of input)
01 WS-OUTPUT-RECORD (working copy of output)
01 WS-CONSTANTS (named constants)
01 WS-TABLES (in-memory lookup tables)
01 WS-REPORT-FIELDS (report headers, detail lines)
01 WS-ERROR-FIELDS (error messages, codes)
This structure allows a maintenance programmer to find any variable quickly without searching through the entire WORKING-STORAGE section.
Question 12
What is the recommended way to handle the end-of-file condition in a READ loop?
Show Answer
Use an **88-level condition name** on a flag variable, set it in the AT END clause, and test it in the PERFORM UNTIL: 01 WS-EOF-FLAGS.
05 WS-EOF-INPUT PIC X VALUE 'N'.
88 FL-EOF-INPUT VALUE 'Y'.
88 FL-NOT-EOF-INPUT VALUE 'N'.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-READ-INPUT
PERFORM 3000-PROCESS
UNTIL FL-EOF-INPUT
PERFORM 9000-TERMINATE
STOP RUN
.
2000-READ-INPUT.
READ INPUT-FILE INTO WS-INPUT-RECORD
AT END
SET FL-EOF-INPUT TO TRUE
NOT AT END
ADD 1 TO CT-RECORDS-READ
END-READ
.
3000-PROCESS.
PERFORM 3100-VALIDATE
PERFORM 3200-CALCULATE
PERFORM 3300-WRITE-OUTPUT
PERFORM 2000-READ-INPUT
.
This pattern is preferred over `MOVE 'Y' TO WS-EOF` because `SET FL-EOF-INPUT TO TRUE` is self-documenting and compiler-verified (the compiler ensures FL-EOF-INPUT is a valid 88-level name).
Question 13
What does the principle "one paragraph, one purpose" mean?
Show Answer
Every paragraph should do **exactly one thing** and do it completely. A paragraph should have a single, clearly stated purpose, and all the code within it should relate to that purpose. If you cannot describe what a paragraph does in one sentence, it is probably doing too much. Examples of single-purpose paragraphs: - `2100-READ-TRANSACTION` -- reads one record and sets the EOF flag - `3000-VALIDATE-AMOUNT` -- checks that the amount is numeric, positive, and within range - `4200-CALCULATE-INTEREST` -- computes interest for one account - `9100-LOG-ERROR` -- writes one error record to the error log Counter-example (doing too much): - `PROCESS-EVERYTHING` -- reads a record, validates it, calculates interest, updates the master, writes a report line, and checks for control breaks The single-purpose principle makes paragraphs reusable, testable, and easy to understand. When a bug is reported, you can quickly identify which paragraph is responsible and limit your investigation to a small amount of code.Question 14
What are the risks of using a group MOVE to initialize a record area?
Show Answer
A **group MOVE** treats the receiving field as alphanumeric regardless of the subordinate field types: MOVE SPACES TO WS-CUSTOMER-RECORD
This moves spaces to every byte of WS-CUSTOMER-RECORD, including numeric fields (PIC 9, COMP, COMP-3). Risks:
1. **Spaces in numeric fields**: COMP-3 and COMP fields filled with spaces will cause S0C7 when used in arithmetic.
2. **Invalid packed decimal**: X'40' (EBCDIC space) is not a valid packed decimal digit or sign nibble.
3. **Incorrect binary values**: Spaces in a COMP (binary) field represent an arbitrary integer value, not zero.
The safe alternative is **INITIALIZE**:
INITIALIZE WS-CUSTOMER-RECORD
INITIALIZE sets alphanumeric fields to spaces and numeric fields to zeros, respecting each field's USAGE clause. It is always safe and always correct for clearing a record area.
Question 15
True or False: Comments that restate what the code does ("Add 1 to counter") are worse than no comments at all.
Show Answer
**True**, in the sense that they add visual clutter without adding information. A good comment explains **why** something is done, not **what** is being done (the code already tells you what). However, truly having no comments is also problematic -- the ideal is meaningful comments that add context. **Useless comment** (restates the code): * Add 1 to the record counter
ADD 1 TO CT-RECORDS-READ
**Useful comment** (explains why):
* Count includes both valid and invalid records for
* reconciliation against the trailer record total
ADD 1 TO CT-RECORDS-READ
**Useful comment** (explains business rule):
* Per FDIC Reg E, disputed charges must be provisionally
* credited within 10 business days (Section 205.11)
IF WS-DAYS-SINCE-DISPUTE > 10
PERFORM 5000-PROVISIONAL-CREDIT
END-IF
The standard: every paragraph should have a header comment explaining its purpose and business context. Inline comments should explain complex logic, business rules, and non-obvious decisions -- not restate simple operations.
Question 16
What is the recommended approach for handling constants that might change (such as fee amounts, rate limits, or threshold values)?
Show Answer
Use one or more of these approaches, in order of preference: 1. **External configuration file or table**: Load values from a file or DB2 table at program startup. Changes require no recompilation. 01 WS-CONFIG-RECORD.
05 WS-CFG-MIN-BALANCE PIC 9(7)V99.
05 WS-CFG-MONTHLY-FEE PIC 9(5)V99.
05 WS-CFG-FREE-TRANS PIC 9(3).
2. **COPY member with VALUE clauses**: Store constants in a copybook. Changes require recompilation but only the copybook needs editing.
* In COPY member FEECNST:
01 WS-FEE-CONSTANTS.
05 WS-MIN-BALANCE PIC 9(7)V99 VALUE 500.00.
05 WS-MONTHLY-FEE PIC 9(5)V99 VALUE 12.95.
05 WS-FREE-TRANS PIC 9(3) VALUE 20.
3. **Named constants in WORKING-STORAGE**: At minimum, give every literal a meaningful name. Never put numeric literals directly in the PROCEDURE DIVISION.
The worst approach is hardcoding: `IF WS-BALANCE < 500`. When the threshold changes from $500 to $750, someone must find every occurrence of the number 500 in the program -- and pray that 500 is not used for something else too.
Question 17
What is the "priming read" pattern, and why is it considered best practice?
Show Answer
The **priming read** (or "read-ahead") pattern reads the first record before entering the processing loop. The loop then processes the current record and reads the next one at the end: PERFORM 2000-READ-INPUT *> Priming read
PERFORM 3000-PROCESS
UNTIL FL-EOF-INPUT
.
3000-PROCESS.
PERFORM 3100-PROCESS-CURRENT-RECORD
PERFORM 2000-READ-INPUT *> Read next
.
Benefits:
1. **Clean loop termination**: The UNTIL condition is checked before entering the loop, so an empty file is handled correctly (the loop body never executes).
2. **Current record is always valid**: When 3000-PROCESS executes, the current record has already been read successfully. There is no need to check EOF inside the loop body.
3. **Single read paragraph**: The same 2000-READ-INPUT paragraph handles both the initial read and all subsequent reads, eliminating duplicate code.
Without the priming read, you must check for EOF at the top of the loop, which leads to awkward constructions like `PERFORM UNTIL WS-EOF = 'Y' ... READ ... AT END MOVE 'Y' TO WS-EOF ... IF WS-EOF = 'N' ... (process) ... END-IF`.
Question 18
What COBOL verb should be used instead of deeply nested IF/ELSE chains?
Show Answer
**EVALUATE** (the COBOL equivalent of switch/case). EVALUATE handles multiple conditions cleanly without nesting: * BAD: Nested IF chain
IF WS-CODE = 'A'
PERFORM 1000-ADD
ELSE
IF WS-CODE = 'C'
PERFORM 2000-CHANGE
ELSE
IF WS-CODE = 'D'
PERFORM 3000-DELETE
ELSE
PERFORM 9000-ERROR
END-IF
END-IF
END-IF
* GOOD: EVALUATE
EVALUATE WS-CODE
WHEN 'A'
PERFORM 1000-ADD
WHEN 'C'
PERFORM 2000-CHANGE
WHEN 'D'
PERFORM 3000-DELETE
WHEN OTHER
PERFORM 9000-ERROR
END-EVALUATE
EVALUATE is also preferred because it handles compound conditions naturally with EVALUATE TRUE, supports ALSO for multi-variable decisions, and always provides a WHEN OTHER default path.
Question 19
True or False: Every COBOL file should have a FILE STATUS clause in its SELECT statement.
Show Answer
**True**. This is one of the most important coding standards. Without a FILE STATUS clause, I/O errors can cause unpredictable behavior or abends with no diagnostic information. With FILE STATUS, the program can check the result of every I/O operation and take appropriate action. * REQUIRED: File status on every file
SELECT CUSTOMER-FILE ASSIGN TO CUSTMAST
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS CR-CUST-ID
FILE STATUS IS WS-CUST-STATUS.
After every I/O operation (OPEN, READ, WRITE, REWRITE, DELETE, CLOSE, START), the program should check the status:
OPEN I-O CUSTOMER-FILE
IF WS-CUST-STATUS NOT = '00'
DISPLAY 'CUSTOMER FILE OPEN FAILED: '
WS-CUST-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
Failure to check file status after OPEN is the single most common coding standards violation in production COBOL programs.
Question 20
What is the recommended maximum nesting depth for IF statements?
Show Answer
The recommended maximum is **3 levels** of nesting. Beyond 3 levels, the code becomes difficult to read, trace, and maintain. If the logic requires more than 3 levels, refactor by: 1. **Using EVALUATE**: Replace the entire IF chain with EVALUATE TRUE 2. **Extracting inner blocks**: Move nested logic into separate paragraphs with PERFORMs 3. **Using guard clauses**: Validate and reject early, then process the "happy path" without nesting 4. **Combining conditions**: `IF A AND B AND C` is flatter than `IF A ... IF B ... IF C` * BAD: 5 levels deep
IF condition-1
IF condition-2
IF condition-3
IF condition-4
IF condition-5
PERFORM process
END-IF
END-IF
END-IF
END-IF
END-IF
* GOOD: Guard clause pattern
IF NOT condition-1
CONTINUE
ELSE IF NOT condition-2
CONTINUE
ELSE
PERFORM 3100-VALIDATE-DETAILS
END-IF
Deep nesting is a symptom of doing too much in one paragraph. Refactoring into smaller paragraphs almost always eliminates excessive nesting.
Question 21
What should the IDENTIFICATION DIVISION of every program contain?
Show Answer
At minimum: IDENTIFICATION DIVISION.
PROGRAM-ID. LOANPOST.
AUTHOR. M-WILLIAMS.
DATE-WRITTEN. 2021-06-15.
DATE-COMPILED.
Note: AUTHOR and DATE-WRITTEN are technically deprecated in COBOL-85 but are still widely used and considered best practice in most enterprise shops. DATE-COMPILED is automatically replaced with the compilation date by the compiler.
The PROGRAM-ID is the only required paragraph. However, best practice adds a detailed header comment block (before or after the IDENTIFICATION DIVISION) containing:
- Program purpose and description
- Input/output files
- Called programs
- Business rules
- Change history
The header comment block is often more valuable than the IDENTIFICATION DIVISION paragraphs because it can contain unlimited detail and is the first thing a maintenance programmer reads.
Question 22
Why is MOVE CORRESPONDING considered risky and generally discouraged?
Show Answer
**MOVE CORRESPONDING** moves all fields from one group item to another where the field names match: MOVE CORRESPONDING WS-INPUT-REC TO WS-OUTPUT-REC
Risks:
1. **Implicit behavior**: You cannot tell by reading the statement which fields will be moved. You must mentally compare the two group items to determine the matching field names.
2. **Unintended matches**: If a field name like FILLER or WS-STATUS exists in both groups but means different things, it will be moved incorrectly.
3. **Missed fields**: Fields that do not have matching names are silently skipped, which may not be the programmer's intent.
4. **Fragile under maintenance**: Adding or renaming a field in either group changes which fields are moved, with no compiler warning.
5. **Difficult to debug**: When a field has the wrong value, MOVE CORRESPONDING does not appear in the cross-reference as a modifier of individual fields, making it hard to trace.
The alternative is explicit field-level MOVEs, which are self-documenting and fully visible in the cross-reference listing.
Question 23
What is the standard convention for COBOL program return codes?
Show Answer
The universal convention used by virtually all mainframe shops: | Return Code | Meaning | Action | |-------------|---------|--------| | **0** | Successful completion | Continue to next step | | **4** | Warning -- completed with minor issues | Review log, usually continue | | **8** | Error -- completed but with significant issues | Investigate before proceeding | | **12** | Severe error -- partial completion | Do not proceed; manual intervention needed | | **16** | Critical failure -- terminated abnormally | Immediate investigation required | These codes are set via `MOVE nn TO RETURN-CODE` before STOP RUN. JCL COND parameters or IF/THEN/ELSE logic use these values to control subsequent job steps: * Standard return code setting at termination
EVALUATE TRUE
WHEN CT-ERRORS = 0
MOVE 0 TO RETURN-CODE
WHEN CT-ERRORS > 0 AND CT-ERRORS <= 10
MOVE 4 TO RETURN-CODE
WHEN CT-ERRORS > 10 AND CT-ERRORS <= 100
MOVE 8 TO RETURN-CODE
WHEN OTHER
MOVE 16 TO RETURN-CODE
END-EVALUATE
STOP RUN
Question 24
What is the difference between inline PERFORM and out-of-line PERFORM, and when should each be used?
Show Answer
**Inline PERFORM** contains the code directly within the PERFORM statement: PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-TABLE-SIZE
MOVE TB-CODE(WS-IDX) TO WS-WORK-CODE
IF WS-WORK-CODE = WS-SEARCH-CODE
MOVE TB-DESC(WS-IDX) TO WS-RESULT
SET WS-IDX TO WS-TABLE-SIZE
END-IF
END-PERFORM
**Out-of-line PERFORM** references a separate paragraph:
PERFORM 3100-SEARCH-TABLE
VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-TABLE-SIZE
Guidelines:
- **Use inline PERFORM** for very short loops (3-5 lines) that are used only once
- **Use out-of-line PERFORM** for logic that is longer than 5 lines, reused, or complex enough to benefit from a descriptive paragraph name
- **Never use inline PERFORM** for deeply nested loops (inline within inline) -- it becomes unreadable
- Out-of-line is generally preferred because the paragraph name provides documentation