COBOL programs live for decades. A program written in 1985 is not a curiosity or a museum piece -- it is running in production today, processing millions of transactions every night, and someone will need to modify it next month. The original...
In This Chapter
- Introduction: Why Standards Matter in COBOL
- 21.1 Naming Conventions
- 21.2 Program Structure Standards
- 21.3 Data Definition Standards
- 21.4 Documentation Standards
- 21.5 File Handling Standards
- 21.6 Error Handling Standards
- 21.7 Performance Coding Standards
- 21.8 Deprecated Features to Avoid
- 21.9 COBOL-DB2 Coding Standards
- 21.10 Code Style Comparison: Good vs. Bad
- 21.10 Code Review Checklist
- 21.11 Adapting Standards for GnuCOBOL
- 21.12 CICS Programming Standards
- 21.13 Standards for Team Environments
- Summary
- 21.14 JCL Standards for COBOL Programs
- 21.15 Modernization-Ready Standards
- Exercises
Chapter 21: COBOL Coding Standards and Best Practices
Introduction: Why Standards Matter in COBOL
COBOL programs live for decades. A program written in 1985 is not a curiosity or a museum piece -- it is running in production today, processing millions of transactions every night, and someone will need to modify it next month. The original programmer retired fifteen years ago. The three programmers who maintained it after that have all moved on. The programmer who inherits it next needs to understand what the program does, why it does it that way, and where to make changes safely.
This reality -- that COBOL programs outlive their authors by decades -- makes coding standards more important in COBOL than in perhaps any other programming language. In languages where programs are rewritten every few years, coding standards are a convenience. In COBOL, they are a survival mechanism. A well-written COBOL program with consistent naming conventions, clear structure, thorough documentation, and defensive error handling can be maintained by any competent COBOL programmer. A poorly written program with cryptic variable names, tangled control flow, missing error handling, and no documentation is a maintenance nightmare that costs organizations thousands of hours and millions of dollars over its lifetime.
This chapter presents a comprehensive set of coding standards and best practices for COBOL programming. These standards are drawn from decades of enterprise experience, IBM COBOL programming guides, and the collective wisdom of the COBOL development community. They are not arbitrary rules imposed for their own sake; each standard exists because violating it has caused real problems in real production systems.
21.1 Naming Conventions
Clear, consistent naming is the foundation of readable COBOL code. COBOL's long variable names (up to 30 characters) are one of its great strengths -- use them.
General Naming Rules
- Use meaningful names: Every data name and paragraph name should describe its purpose without requiring a comment.
- Use hyphens to separate words: COBOL uses hyphens, not underscores or camelCase.
- Be consistent: Once you establish a naming pattern, follow it throughout the program.
- Avoid abbreviations unless they are universally understood: CUST for customer, ACCT for account, AMT for amount, NUM for number, and ADDR for address are acceptable. CRSMTPGDR is not.
WORKING-STORAGE Prefixes
Use a prefix on every WORKING-STORAGE variable to identify its section and purpose:
| Prefix | Meaning | Example |
|---|---|---|
| WS- | Working-Storage variable | WS-CUSTOMER-NAME |
| WK- | Work/scratch variable | WK-TEMP-AMOUNT |
| CT- | Counter | CT-RECORDS-READ |
| AC- | Accumulator | AC-TOTAL-AMOUNT |
| FL- | Flag | FL-END-OF-FILE |
| SW- | Switch | SW-FIRST-RECORD |
| IX- | Index | IX-TABLE-ENTRY |
| TB- | Table entry | TB-STATE-CODE |
| ER- | Error-related | ER-FILE-STATUS |
| RPT- | Report-related | RPT-PAGE-NUMBER |
| DT- | Date-related | DT-CURRENT-DATE |
| MSG- | Message | MSG-ERROR-TEXT |
* GOOD: Clear prefixes identify the purpose
01 WS-CUSTOMER-RECORD.
05 WS-CUST-ID PIC X(10).
05 WS-CUST-NAME PIC X(30).
05 WS-CUST-BALANCE PIC S9(7)V99 COMP-3.
01 CT-PROCESSING-COUNTS.
05 CT-RECORDS-READ PIC 9(7) COMP VALUE 0.
05 CT-RECORDS-WRITTEN PIC 9(7) COMP VALUE 0.
05 CT-RECORDS-SKIPPED PIC 9(7) COMP VALUE 0.
05 CT-ERROR-COUNT PIC 9(5) COMP VALUE 0.
01 FL-CONTROL-FLAGS.
05 FL-EOF-FLAG PIC 9 VALUE 0.
88 FL-NOT-EOF VALUE 0.
88 FL-EOF VALUE 1.
* BAD: No prefixes, unclear purpose
01 RECORD-1.
05 FIELD-A PIC X(10).
05 FIELD-B PIC X(30).
05 AMOUNT PIC S9(7)V99 COMP-3.
FILE SECTION Naming
File records should use a prefix derived from the file name:
* GOOD: Prefix identifies the file
FD CUSTOMER-FILE.
01 CUST-RECORD.
05 CUST-ID PIC X(10).
05 CUST-NAME PIC X(30).
05 CUST-STATUS PIC X.
FD TRANSACTION-FILE.
01 TRAN-RECORD.
05 TRAN-ACCT-NUM PIC X(10).
05 TRAN-AMOUNT PIC S9(7)V99 COMP-3.
05 TRAN-TYPE PIC X.
LINKAGE SECTION Naming
Linkage section items should use the prefix LS- or LK-:
LINKAGE SECTION.
01 LS-INPUT-PARAMETERS.
05 LS-CUSTOMER-ID PIC X(10).
05 LS-REQUEST-TYPE PIC X.
01 LS-OUTPUT-RESULTS.
05 LS-RETURN-CODE PIC S9(4) COMP.
05 LS-ERROR-MESSAGE PIC X(80).
Paragraph Naming: Verb-Noun Convention
Paragraph names should follow the verb-noun pattern, describing what the paragraph does. They should be numbered for ordering:
* GOOD: Numbered, verb-noun format
0000-MAIN.
1000-INITIALIZE-PROGRAM.
1100-OPEN-FILES.
1200-LOAD-TABLES.
2000-PROCESS-TRANSACTIONS.
2100-READ-TRANSACTION.
2200-VALIDATE-TRANSACTION.
2300-UPDATE-MASTER.
2400-WRITE-AUDIT-RECORD.
3000-TERMINATE-PROGRAM.
3100-CLOSE-FILES.
3200-WRITE-SUMMARY-REPORT.
9000-ERROR-HANDLING.
9100-FILE-ERROR.
9200-DATA-ERROR.
* BAD: No numbers, unclear purpose
START-IT.
DO-STUFF.
READ-AND-CHECK.
FINISH.
ERRORS.
The numbering convention serves two purposes: it establishes a visual hierarchy (1000-level paragraphs are called from 0000-MAIN, 1100-level from 1000-level, etc.), and it ensures paragraphs appear in a logical order in the source listing.
88-Level Condition Names
Level 88 condition names should be adjectives or adjective phrases that describe the state being tested:
* GOOD: Adjective-style condition names
01 FL-EOF-FLAG PIC 9 VALUE 0.
88 FL-NOT-AT-END VALUE 0.
88 FL-AT-END VALUE 1.
01 WS-RECORD-STATUS PIC X.
88 WS-RECORD-VALID VALUE 'V'.
88 WS-RECORD-INVALID VALUE 'I'.
88 WS-RECORD-DUPLICATE VALUE 'D'.
01 WS-ACCOUNT-TYPE PIC X.
88 WS-CHECKING-ACCT VALUE 'C'.
88 WS-SAVINGS-ACCT VALUE 'S'.
88 WS-LOAN-ACCT VALUE 'L'.
* Usage reads like English:
IF FL-AT-END
PERFORM 3000-TERMINATE
END-IF
IF WS-RECORD-VALID
PERFORM 2300-UPDATE-MASTER
END-IF
IF WS-CHECKING-ACCT OR WS-SAVINGS-ACCT
PERFORM 2400-CALCULATE-INTEREST
END-IF
* BAD: Non-descriptive condition names
01 FLAG-1 PIC 9 VALUE 0.
88 FLAG-1-YES VALUE 1.
88 FLAG-1-NO VALUE 0.
21.2 Program Structure Standards
Top-Down Design
Every COBOL program should follow the top-down design pattern. The main paragraph performs high-level paragraphs, which in turn perform lower-level paragraphs. The program reads from top to bottom like an outline.
* GOOD: Top-down structure
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS
UNTIL FL-EOF
PERFORM 3000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
PERFORM 1100-OPEN-FILES
PERFORM 1200-LOAD-REFERENCE-TABLES
PERFORM 1300-INITIALIZE-ACCUMULATORS
PERFORM 2100-READ-INPUT
.
2000-PROCESS.
PERFORM 2200-VALIDATE-RECORD
IF WS-RECORD-VALID
PERFORM 2300-APPLY-BUSINESS-RULES
PERFORM 2400-WRITE-OUTPUT
ELSE
PERFORM 2500-WRITE-ERROR-RECORD
END-IF
PERFORM 2100-READ-INPUT
.
3000-TERMINATE.
PERFORM 3100-CLOSE-FILES
PERFORM 3200-DISPLAY-SUMMARY
.
Single Entry, Single Exit
Every paragraph should have one entry point (the paragraph name) and one exit point (the period at the end). Avoid using GO TO to jump out of or into the middle of paragraphs.
* GOOD: Single entry, single exit
2200-VALIDATE-RECORD.
SET WS-RECORD-VALID TO TRUE
IF WS-CUST-ID = SPACES
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-AMOUNT NOT NUMERIC
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-RECORD-VALID
IF WS-AMOUNT > WS-MAX-AMOUNT
SET WS-RECORD-INVALID TO TRUE
END-IF
END-IF
.
* BAD: Multiple exits via GO TO
2200-VALIDATE-RECORD.
IF WS-CUST-ID = SPACES
GO TO 2200-EXIT
END-IF
IF WS-AMOUNT NOT NUMERIC
GO TO 2200-EXIT
END-IF
SET WS-RECORD-VALID TO TRUE.
2200-EXIT.
EXIT.
Avoid GO TO
The GO TO statement is the single most damaging construct in COBOL for program maintainability. It creates non-linear control flow that makes programs difficult to understand, difficult to modify, and impossible to test in isolation.
* BAD: GO TO creates spaghetti code
READ-LOOP.
READ INPUT-FILE
AT END GO TO END-OF-FILE.
IF REC-TYPE = 'A'
GO TO PROCESS-TYPE-A.
IF REC-TYPE = 'B'
GO TO PROCESS-TYPE-B.
GO TO READ-LOOP.
PROCESS-TYPE-A.
ADD REC-AMOUNT TO TOTAL-A.
GO TO READ-LOOP.
PROCESS-TYPE-B.
ADD REC-AMOUNT TO TOTAL-B.
GO TO READ-LOOP.
END-OF-FILE.
* GOOD: Structured equivalent
2000-PROCESS-FILE.
PERFORM 2100-READ-INPUT
PERFORM UNTIL FL-EOF
EVALUATE WS-REC-TYPE
WHEN 'A'
ADD WS-REC-AMOUNT TO AC-TOTAL-A
WHEN 'B'
ADD WS-REC-AMOUNT TO AC-TOTAL-B
WHEN OTHER
PERFORM 9200-UNEXPECTED-TYPE
END-EVALUATE
PERFORM 2100-READ-INPUT
END-PERFORM
.
Exception: Some shops permit one specific use of GO TO: a forward GO TO to an EXIT paragraph at the end of a section, used as an early return. While this is tolerable, the structured alternative using nested IF or EVALUATE is preferred.
Section vs. Paragraph Organization
There are two schools of thought on program organization: sections with paragraphs, and paragraphs only. Most modern COBOL shops use paragraphs only, reserving sections for DECLARATIVES and SORT procedures that require them.
* PREFERRED: Paragraphs only (except where sections required)
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
...
1000-INITIALIZE.
...
2000-PROCESS.
...
* ALTERNATIVE: Sections with paragraphs
PROCEDURE DIVISION.
MAIN-SECTION SECTION.
0000-MAIN.
PERFORM 1000-INITIALIZE
...
INITIALIZE-SECTION SECTION.
1000-INITIALIZE.
...
21.3 Data Definition Standards
Level Numbers
Use consistent indentation of level numbers. The most common convention uses levels 01, 05, 10, 15, 20, leaving room for intermediate levels if the structure needs to be expanded:
* GOOD: Consistent level numbers with room to grow
01 WS-CUSTOMER-RECORD.
05 WS-CUST-ID.
10 WS-CUST-REGION PIC XX.
10 WS-CUST-BRANCH PIC XXX.
10 WS-CUST-SEQUENCE PIC 9(5).
05 WS-CUST-NAME.
10 WS-CUST-LAST-NAME PIC X(20).
10 WS-CUST-FIRST-NAME PIC X(15).
10 WS-CUST-MIDDLE-INIT PIC X.
05 WS-CUST-ADDRESS.
10 WS-CUST-STREET PIC X(30).
10 WS-CUST-CITY PIC X(20).
10 WS-CUST-STATE PIC XX.
10 WS-CUST-ZIP PIC X(10).
* BAD: Sequential level numbers leave no room
01 WS-CUSTOMER-RECORD.
02 WS-CUST-ID.
03 WS-CUST-REGION PIC XX.
03 WS-CUST-BRANCH PIC XXX.
FILLER Usage
Use FILLER explicitly for unused portions of records. In COBOL-85 and later, the word FILLER can be omitted (an anonymous item is treated as FILLER), but explicit FILLER is preferred for clarity:
* GOOD: Explicit FILLER
01 WS-PRINT-LINE.
05 FILLER PIC X(5) VALUE SPACES.
05 RPT-EMPLOYEE-ID PIC X(10).
05 FILLER PIC X(3) VALUE SPACES.
05 RPT-EMPLOYEE-NAME PIC X(30).
05 FILLER PIC X(3) VALUE SPACES.
05 RPT-SALARY PIC ZZZ,ZZ9.99.
05 FILLER PIC X(68) VALUE SPACES.
Numeric Field Standards
Choose numeric formats appropriate to the usage:
* COMP-3 (Packed Decimal): For fields used in arithmetic
* and stored in files. Most common on mainframes.
05 WS-AMOUNT PIC S9(7)V99 COMP-3.
* COMP/COMP-4 (Binary): For subscripts, counters, and
* fields that map to binary data (DB2 INTEGER, etc.)
05 WS-SUBSCRIPT PIC S9(4) COMP.
05 WS-COUNTER PIC S9(9) COMP.
* DISPLAY (Zoned Decimal): For fields that need to be
* human-readable in file dumps. Default, but less efficient.
05 WS-REPORT-AMT PIC Z,ZZZ,ZZ9.99-.
* Always use signed numeric (PIC S9) for fields that
* could be negative, including all arithmetic results
05 WS-NET-AMOUNT PIC S9(7)V99 COMP-3.
* NOT:
* 05 WS-NET-AMOUNT PIC 9(7)V99 COMP-3.
* (Unsigned truncates negative values silently!)
VALUE Clauses
Always initialize WORKING-STORAGE fields with VALUE clauses. This prevents S0C7 abends from uninitialized COMP-3 fields and eliminates unpredictable behavior from uninitialized flags:
* GOOD: All fields initialized
01 WS-COUNTERS.
05 CT-RECORDS-READ PIC 9(7) COMP VALUE 0.
05 CT-RECORDS-WRITTEN PIC 9(7) COMP VALUE 0.
05 CT-ERROR-COUNT PIC 9(5) COMP VALUE 0.
01 FL-FLAGS.
05 FL-EOF PIC 9 VALUE 0.
88 FL-NOT-AT-END VALUE 0.
88 FL-AT-END VALUE 1.
* BAD: Uninitialized fields
01 WS-COUNTERS.
05 CT-RECORDS-READ PIC 9(7) COMP.
05 CT-RECORDS-WRITTEN PIC 9(7) COMP.
21.4 Documentation Standards
Program Header
Every COBOL program should begin with a comprehensive header comment in the IDENTIFICATION DIVISION:
IDENTIFICATION DIVISION.
PROGRAM-ID. CUSTUPDT.
*========================================================*
* PROGRAM: CUSTUPDT *
* AUTHOR: J. Smith *
* DATE: 2025-01-15 *
* PURPOSE: Customer Master File Update Program *
* *
* DESCRIPTION: *
* This program reads a sequential transaction file *
* and applies add, change, and delete transactions *
* to the indexed customer master file. Rejected *
* transactions are written to an error file with *
* appropriate error codes. *
* *
* INPUT FILES: *
* TRANFILE - Transaction input (sequential) *
* CUSTMAST - Customer master (indexed, KSDS) *
* *
* OUTPUT FILES: *
* CUSTMAST - Customer master (updated in place) *
* ERRFILE - Rejected transactions *
* RPTFILE - Processing summary report *
* *
* RETURN CODES: *
* 0 - Successful completion *
* 4 - Completed with warnings (some rejects) *
* 8 - Completed with errors *
* 16 - Terminated abnormally *
* *
* CHANGE LOG: *
* DATE AUTHOR CHG# DESCRIPTION *
* ---------- ---------- ------- ------------------------- *
* 2025-01-15 J.SMITH CHG0001 Initial development *
* 2025-03-20 M.JONES CHG0042 Add zip+4 validation *
* 2025-06-10 A.PATEL CHG0087 Fix duplicate key check *
*========================================================*
Paragraph Headers
Major paragraphs should have a brief comment describing their purpose:
*---------------------------------------------------------*
* 2200-VALIDATE-TRANSACTION *
* Validates all fields in the current transaction record. *
* Sets WS-RECORD-VALID or WS-RECORD-INVALID flag. *
* Populates WS-ERROR-TABLE with all validation errors. *
*---------------------------------------------------------*
2200-VALIDATE-TRANSACTION.
Inline Comments
Use inline comments sparingly and only to explain why, not what. The code itself should be clear enough to show what it does:
* GOOD: Explains WHY
* Per regulatory requirement REG-2024-17,
* transactions over $10,000 require CTR reporting
IF WS-AMOUNT > 10000.00
PERFORM 2500-GENERATE-CTR
END-IF
* BAD: Explains WHAT (redundant with the code)
* Check if amount is greater than 10000
IF WS-AMOUNT > 10000.00
Change Log Maintenance
When modifying an existing program, add an entry to the change log in the header and mark modified lines with the change number:
* In the header:
* 2025-06-10 A.PATEL CHG0087 Fix duplicate key check
* In the code:
WRITE CUST-RECORD
FROM WS-CUSTOMER-DATA
INVALID KEY
*CHG0087 Check for duplicate before error logging
IF WS-CUST-STATUS = '22'
PERFORM 2250-HANDLE-DUPLICATE
ELSE
PERFORM 9200-WRITE-ERROR
END-IF
NOT INVALID KEY
ADD 1 TO CT-RECORDS-WRITTEN
END-WRITE
21.5 File Handling Standards
Always Use FILE STATUS
This is the single most important file handling standard. Every file in every program must have a FILE STATUS clause, and every I/O operation must check the status:
* REQUIRED: FILE STATUS on every file
FILE-CONTROL.
SELECT CUSTOMER-FILE
ASSIGN TO CUSTFILE
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS CUST-KEY
FILE STATUS IS WS-CUST-STATUS.
SELECT TRANSACTION-FILE
ASSIGN TO TRANFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-TRAN-STATUS.
SELECT REPORT-FILE
ASSIGN TO RPTFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
Check Status After Every I/O
* After OPEN
OPEN INPUT CUSTOMER-FILE
IF WS-CUST-STATUS NOT = '00'
DISPLAY 'OPEN FAILED: ' WS-CUST-STATUS
PERFORM 9000-ABORT
END-IF
* After READ
READ CUSTOMER-FILE INTO WS-CUST-WORK
EVALUATE WS-CUST-STATUS
WHEN '00' CONTINUE
WHEN '10' SET FL-EOF TO TRUE
WHEN '23' SET WS-NOT-FOUND TO TRUE
WHEN OTHER PERFORM 9100-FILE-ERROR
END-EVALUATE
* After WRITE
WRITE CUST-RECORD FROM WS-CUST-WORK
IF WS-CUST-STATUS NOT = '00'
PERFORM 9100-FILE-ERROR
END-IF
* After CLOSE
CLOSE CUSTOMER-FILE
IF WS-CUST-STATUS NOT = '00'
DISPLAY 'CLOSE WARNING: ' WS-CUST-STATUS
END-IF
Centralized File Error Routine
9100-FILE-ERROR.
DISPLAY '*** FILE I/O ERROR ***'
DISPLAY 'FILE: ' WS-CURRENT-FILE-NAME
DISPLAY 'OPERATION: ' WS-CURRENT-OPERATION
DISPLAY 'STATUS: ' WS-CURRENT-STATUS
DISPLAY 'PARAGRAPH: ' WS-CURRENT-PARAGRAPH
MOVE 16 TO RETURN-CODE
PERFORM 9900-CONTROLLED-SHUTDOWN
.
File Open/Close Patterns
Always open all files at the beginning of the program and close them at the end, in a predictable order:
1100-OPEN-FILES.
OPEN INPUT TRANSACTION-FILE
IF WS-TRAN-STATUS NOT = '00'
DISPLAY 'CANNOT OPEN TRANSACTION FILE'
SET FL-ABORT TO TRUE
END-IF
IF NOT FL-ABORT
OPEN I-O CUSTOMER-FILE
IF WS-CUST-STATUS NOT = '00'
DISPLAY 'CANNOT OPEN CUSTOMER FILE'
SET FL-ABORT TO TRUE
END-IF
END-IF
IF NOT FL-ABORT
OPEN OUTPUT REPORT-FILE
IF WS-RPT-STATUS NOT = '00'
DISPLAY 'CANNOT OPEN REPORT FILE'
SET FL-ABORT TO TRUE
END-IF
END-IF
.
3100-CLOSE-FILES.
CLOSE TRANSACTION-FILE
CLOSE CUSTOMER-FILE
CLOSE REPORT-FILE
.
21.6 Error Handling Standards
Every Arithmetic Statement Must Have ON SIZE ERROR
* REQUIRED for all arithmetic
ADD WS-AMOUNT TO AC-TOTAL
ON SIZE ERROR
PERFORM 9200-ARITHMETIC-ERROR
END-ADD
COMPUTE WS-INTEREST =
WS-PRINCIPAL * WS-RATE / 100 * WS-DAYS / 365
ON SIZE ERROR
PERFORM 9200-ARITHMETIC-ERROR
END-COMPUTE
DIVIDE WS-TOTAL BY CT-RECORDS-READ
GIVING WS-AVERAGE ROUNDED
ON SIZE ERROR
MOVE 0 TO WS-AVERAGE
END-DIVIDE
Always Use Explicit Scope Terminators
Never rely on periods to terminate conditional statements or I/O statements. Always use END-IF, END-EVALUATE, END-READ, END-WRITE, END-PERFORM, END-CALL, END-COMPUTE, and similar explicit scope terminators:
* GOOD: Explicit scope terminators
IF WS-AMOUNT > 0
ADD WS-AMOUNT TO AC-CREDIT-TOTAL
ON SIZE ERROR
PERFORM 9200-OVERFLOW
END-ADD
ELSE
SUBTRACT WS-AMOUNT FROM AC-DEBIT-TOTAL
ON SIZE ERROR
PERFORM 9200-OVERFLOW
END-SUBTRACT
END-IF
* BAD: Period-terminated, dangerous
IF WS-AMOUNT > 0
ADD WS-AMOUNT TO AC-CREDIT-TOTAL.
* The period ends the IF! The ELSE is not part of the IF!
ELSE
SUBTRACT WS-AMOUNT FROM AC-DEBIT-TOTAL.
Use EVALUATE Instead of Nested IF
* GOOD: Clear, flat, maintainable
EVALUATE TRUE
WHEN WS-TRANS-TYPE = 'A'
PERFORM 2300-ADD-CUSTOMER
WHEN WS-TRANS-TYPE = 'C'
PERFORM 2400-CHANGE-CUSTOMER
WHEN WS-TRANS-TYPE = 'D'
PERFORM 2500-DELETE-CUSTOMER
WHEN WS-TRANS-TYPE = 'I'
PERFORM 2600-INQUIRE-CUSTOMER
WHEN OTHER
MOVE 'INVALID TRANSACTION TYPE'
TO WS-ERROR-MESSAGE
PERFORM 9200-DATA-ERROR
END-EVALUATE
EVALUATE with Multiple Subjects
EVALUATE can test multiple conditions simultaneously, providing a cleaner alternative to complex nested IF:
* GOOD: Multiple-subject EVALUATE
EVALUATE WS-ACCOUNT-TYPE
ALSO WS-TRANS-TYPE
WHEN 'C' ALSO 'D'
PERFORM 2100-CHECKING-DEPOSIT
WHEN 'C' ALSO 'W'
PERFORM 2200-CHECKING-WITHDRAWAL
WHEN 'S' ALSO 'D'
PERFORM 2300-SAVINGS-DEPOSIT
WHEN 'S' ALSO 'W'
PERFORM 2400-SAVINGS-WITHDRAWAL
WHEN OTHER
PERFORM 9200-INVALID-COMBINATION
END-EVALUATE
Guard Clauses for Early Validation
When a paragraph needs to validate multiple conditions before proceeding, use a guard clause pattern with a validation flag:
* GOOD: Guard clause pattern
2200-PROCESS-WITHDRAWAL.
SET WS-CAN-PROCESS TO TRUE
* Guard 1: Account must exist
PERFORM 2210-READ-ACCOUNT
IF WS-ACCOUNT-NOT-FOUND
MOVE 'ACCOUNT DOES NOT EXIST'
TO WS-ERROR-MESSAGE
SET WS-CANNOT-PROCESS TO TRUE
END-IF
* Guard 2: Account must be active
IF WS-CAN-PROCESS
IF WS-ACCT-STATUS NOT = 'A'
MOVE 'ACCOUNT IS NOT ACTIVE'
TO WS-ERROR-MESSAGE
SET WS-CANNOT-PROCESS TO TRUE
END-IF
END-IF
* Guard 3: Sufficient funds
IF WS-CAN-PROCESS
IF WS-WITHDRAWAL-AMOUNT > WS-ACCT-BALANCE
MOVE 'INSUFFICIENT FUNDS'
TO WS-ERROR-MESSAGE
SET WS-CANNOT-PROCESS TO TRUE
END-IF
END-IF
* All guards passed -- proceed with withdrawal
IF WS-CAN-PROCESS
PERFORM 2220-EXECUTE-WITHDRAWAL
ELSE
PERFORM 2290-REJECT-TRANSACTION
END-IF
.
Return Code Standards
Follow the standard return code convention consistently:
3200-SET-RETURN-CODE.
EVALUATE TRUE
WHEN FL-ABORT
MOVE 16 TO RETURN-CODE
WHEN CT-ERROR-COUNT > 0
MOVE 8 TO RETURN-CODE
WHEN CT-WARNING-COUNT > 0
MOVE 4 TO RETURN-CODE
WHEN OTHER
MOVE 0 TO RETURN-CODE
END-EVALUATE
.
21.7 Performance Coding Standards
Use COMP-3 for Numeric Fields in Files
COMP-3 (packed decimal) is the most efficient numeric format on IBM mainframes for arithmetic operations and storage. Use it for all monetary amounts, quantities, and numeric fields stored in files:
* GOOD: COMP-3 for file data and arithmetic
05 WS-AMOUNT PIC S9(7)V99 COMP-3.
05 WS-QUANTITY PIC S9(5) COMP-3.
* Use COMP (binary) for subscripts and counters
05 WS-TABLE-INDEX PIC S9(4) COMP.
05 WS-LOOP-COUNTER PIC S9(9) COMP.
Use COMP for Subscripts and Counters
Binary (COMP) fields are most efficient for subscripts and loop counters because the hardware uses binary arithmetic for address calculations:
01 WS-SUBSCRIPTS.
05 WS-ROW-SUB PIC S9(4) COMP.
05 WS-COL-SUB PIC S9(4) COMP.
05 WS-LOOP-CTR PIC S9(9) COMP.
Avoid Unnecessary MOVEs
Do not move data to intermediate fields when you can reference it directly:
* BAD: Unnecessary intermediate MOVE
MOVE CUST-NAME TO WS-TEMP-NAME
WRITE RPT-RECORD FROM WS-TEMP-NAME
* GOOD: Direct reference
MOVE CUST-NAME TO RPT-NAME-FIELD
WRITE RPT-RECORD FROM WS-REPORT-LINE
Perform Table Lookups Efficiently
For large tables, use SEARCH ALL (binary search) instead of SEARCH (sequential search):
* GOOD: Binary search on sorted table
01 WS-STATE-TABLE.
05 WS-STATE-ENTRY OCCURS 50 TIMES
ASCENDING KEY IS WS-ST-CODE
INDEXED BY WS-ST-IDX.
10 WS-ST-CODE PIC XX.
10 WS-ST-NAME PIC X(20).
10 WS-ST-TAX PIC V99.
SEARCH ALL WS-STATE-ENTRY
AT END
SET WS-STATE-NOT-FOUND TO TRUE
WHEN WS-ST-CODE(WS-ST-IDX) = WS-SEARCH-CODE
MOVE WS-ST-NAME(WS-ST-IDX)
TO WS-RESULT-NAME
END-SEARCH
Minimize I/O Operations
I/O is the slowest operation in any program. Minimize the number of I/O operations:
* BAD: Reading the same file multiple times
OPEN INPUT CUSTOMER-FILE
PERFORM UNTIL FL-EOF-1
READ CUSTOMER-FILE ...
* Count records
END-PERFORM
CLOSE CUSTOMER-FILE
OPEN INPUT CUSTOMER-FILE
PERFORM UNTIL FL-EOF-2
READ CUSTOMER-FILE ...
* Process records
END-PERFORM
CLOSE CUSTOMER-FILE
* GOOD: Single pass through the file
OPEN INPUT CUSTOMER-FILE
PERFORM UNTIL FL-EOF
READ CUSTOMER-FILE ...
ADD 1 TO CT-RECORDS-READ
PERFORM 2200-PROCESS-RECORD
END-PERFORM
CLOSE CUSTOMER-FILE
21.8 Deprecated Features to Avoid
Several COBOL features, while still supported by compilers for backward compatibility, are considered deprecated or harmful. Avoid them in new code.
ALTER Statement
The ALTER statement changes the target of a GO TO statement at runtime. It creates code that is literally impossible to understand from reading the source, because the flow of control depends on what has happened during execution:
* TERRIBLE: Never use ALTER
ROUTING-PARAGRAPH.
GO TO DEFAULT-PROCESSING.
* Somewhere else in the program:
ALTER ROUTING-PARAGRAPH TO PROCEED TO
SPECIAL-PROCESSING.
* Now ROUTING-PARAGRAPH goes to SPECIAL-PROCESSING
* instead of DEFAULT-PROCESSING. A maintenance
* programmer reading ROUTING-PARAGRAPH has no way
* to know this without searching the entire program
* for ALTER statements.
* GOOD ALTERNATIVE: Use EVALUATE or a flag
ROUTING-PARAGRAPH.
EVALUATE WS-PROCESSING-MODE
WHEN 'D' PERFORM DEFAULT-PROCESSING
WHEN 'S' PERFORM SPECIAL-PROCESSING
END-EVALUATE
.
NEXT SENTENCE vs. CONTINUE
NEXT SENTENCE transfers control to the next sentence (the statement after the next period). CONTINUE does nothing and continues with the next statement. The difference is subtle but dangerous:
* DANGEROUS: NEXT SENTENCE
IF WS-AMOUNT > 0
IF WS-TYPE = 'A'
NEXT SENTENCE
ELSE
PERFORM 2200-PROCESS-TYPE-B
END-IF
PERFORM 2300-COMMON-PROCESSING
END-IF
* NEXT SENTENCE jumps past the period that ends the
* outer IF, skipping 2300-COMMON-PROCESSING entirely!
* This is almost certainly not what was intended.
* SAFE: CONTINUE
IF WS-AMOUNT > 0
IF WS-TYPE = 'A'
CONTINUE
ELSE
PERFORM 2200-PROCESS-TYPE-B
END-IF
PERFORM 2300-COMMON-PROCESSING
END-IF
* CONTINUE does nothing and falls through to
* 2300-COMMON-PROCESSING as expected.
Rule: Never use NEXT SENTENCE. Always use CONTINUE.
GO TO
As discussed in Section 21.2, avoid GO TO entirely in new code. The only tolerable exception is a forward GO TO to an EXIT paragraph at the end of a section, and even that should be replaced with structured logic when possible.
Nested Programs as GO TO Replacements
Some old programs use nested PERFORM THRU with GO TO to simulate early exit from a paragraph. Replace these with nested programs or restructured logic:
* BAD: PERFORM THRU with GO TO for early exit
PERFORM 2200-VALIDATE THRU 2200-EXIT.
2200-VALIDATE.
IF WS-FIELD-1 = SPACES
MOVE 'BLANK FIELD' TO WS-ERROR
GO TO 2200-EXIT
END-IF
IF WS-FIELD-2 NOT NUMERIC
MOVE 'BAD NUMBER' TO WS-ERROR
GO TO 2200-EXIT
END-IF
SET WS-RECORD-VALID TO TRUE.
2200-EXIT.
EXIT.
* GOOD: Structured validation with flag
2200-VALIDATE.
SET WS-RECORD-VALID TO TRUE
IF WS-FIELD-1 = SPACES
MOVE 'BLANK FIELD' TO WS-ERROR
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-RECORD-VALID AND
WS-FIELD-2 NOT NUMERIC
MOVE 'BAD NUMBER' TO WS-ERROR
SET WS-RECORD-INVALID TO TRUE
END-IF
.
COMPUTE vs. Individual Arithmetic Verbs
There is a historical debate about whether to use COMPUTE or individual verbs (ADD, SUBTRACT, MULTIPLY, DIVIDE). Both are acceptable. COMPUTE is preferred for complex formulas; individual verbs are preferred for simple operations:
* GOOD: COMPUTE for complex formula
COMPUTE WS-INTEREST =
WS-PRINCIPAL *
((1 + WS-RATE / 1200) ** WS-MONTHS - 1)
ON SIZE ERROR
PERFORM 9200-OVERFLOW
END-COMPUTE
* GOOD: ADD for simple accumulation
ADD WS-AMOUNT TO AC-TOTAL-AMOUNT
ON SIZE ERROR
PERFORM 9200-OVERFLOW
END-ADD
21.9 COBOL-DB2 Coding Standards
Programs that use embedded SQL require additional standards to ensure consistency, performance, and maintainability.
Host Variable Naming
Host variables should use a consistent prefix (HV- or H-) and should map clearly to their corresponding DB2 column names:
* GOOD: HV- prefix, names match DB2 columns
01 HV-CUST-ID PIC S9(9) COMP.
01 HV-CUST-NAME PIC X(30).
01 HV-CUST-BALANCE PIC S9(7)V99 COMP-3.
* BAD: No prefix, unclear mapping to DB2
01 CUSTOMER-NUMBER PIC S9(9) COMP.
01 NAME-FIELD PIC X(30).
01 BALANCE PIC S9(7)V99 COMP-3.
Always Check SQLCODE
After every EXEC SQL statement, check SQLCODE. No exceptions. Even operations that you believe cannot fail (such as COMMIT) should have SQLCODE checks:
* REQUIRED: Check SQLCODE after every SQL statement
EXEC SQL
SELECT CUST_NAME
INTO :HV-CUST-NAME
FROM CUSTOMER
WHERE CUST_ID = :HV-CUST-ID
END-EXEC
EVALUATE SQLCODE
WHEN 0 CONTINUE
WHEN +100 SET WS-NOT-FOUND TO TRUE
WHEN OTHER PERFORM 9100-SQL-ERROR
END-EVALUATE
Use DCLGEN for Host Variable Declarations
Never hand-code host variables when DCLGEN can generate them. Hand-coded host variables are a leading cause of data type mismatches between COBOL and DB2. DCLGEN guarantees that the host variable declarations exactly match the DB2 table definition:
* GOOD: Use DCLGEN-generated include
EXEC SQL INCLUDE DCLCUST END-EXEC
* BAD: Manually code host variables (risk of mismatch)
01 HV-CUST-BALANCE PIC S9(9)V99 COMP-3.
* If the DB2 column is DECIMAL(9,2), the correct
* COBOL type is PIC S9(7)V99 COMP-3, not S9(9)V99!
SQL Formatting Standards
Format SQL statements for readability. Each clause should start on a new line, indented consistently:
* GOOD: Formatted SQL
EXEC SQL
SELECT C.CUST_ID,
C.CUST_NAME,
C.CUST_BALANCE,
A.ACCT_TYPE
INTO :HV-CUST-ID,
:HV-CUST-NAME,
:HV-CUST-BALANCE,
:HV-ACCT-TYPE
FROM CUSTOMER C
INNER JOIN ACCOUNT A
ON C.CUST_ID = A.CUST_ID
WHERE C.CUST_STATUS = 'A'
AND A.ACCT_BALANCE > 0
ORDER BY C.CUST_NAME
END-EXEC
* BAD: One-line SQL (unreadable)
EXEC SQL SELECT C.CUST_ID, C.CUST_NAME, C.CUST_BALANCE, A.ACCT_TYPE INTO :HV-CUST-ID, :HV-CUST-NAME, :HV-CUST-BALANCE, :HV-ACCT-TYPE FROM CUSTOMER C INNER JOIN ACCOUNT A ON C.CUST_ID = A.CUST_ID WHERE C.CUST_STATUS = 'A' AND A.ACCT_BALANCE > 0 ORDER BY C.CUST_NAME END-EXEC
Indicator Variables for Nullable Columns
Always use indicator variables for columns that can contain NULL. Failing to do so results in SQLCODE -305 at runtime when a NULL value is encountered:
* REQUIRED for nullable columns
01 HV-PHONE PIC X(15).
01 IND-PHONE PIC S9(4) COMP.
EXEC SQL
SELECT PHONE
INTO :HV-PHONE :IND-PHONE
FROM CUSTOMER
WHERE CUST_ID = :HV-CUST-ID
END-EXEC
Commit Strategy Standards
Every batch DB2 program must have a documented commit strategy. The commit frequency should be configurable and documented in the program header:
*========================================================*
* COMMIT STRATEGY: *
* Commit frequency: Every 1000 rows (configurable) *
* Cursor type: WITH HOLD (survives commit) *
* Restart: Yes (checkpoint table BATCH_CHECKPOINT) *
*========================================================*
21.10 Code Style Comparison: Good vs. Bad
The following side-by-side comparisons illustrate the difference between professional-quality COBOL code and code that will create maintenance problems.
Example 1: File Processing
*========================================================*
* BAD STYLE: Hard to read, maintain, and debug *
*========================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. BAD1.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F1 ASSIGN TO DD1.
SELECT F2 ASSIGN TO DD2.
DATA DIVISION.
FILE SECTION.
FD F1. 01 R1 PIC X(80).
FD F2. 01 R2 PIC X(80).
WORKING-STORAGE SECTION.
01 A PIC 9. 01 B PIC 9(7)V99 COMP-3.
01 C PIC 9(7)V99 COMP-3.
01 X PIC X(80).
PROCEDURE DIVISION.
P1. OPEN INPUT F1 OUTPUT F2.
P2. READ F1 AT END GO TO P4.
MOVE R1 TO X.
IF X(1:1) = 'H' GO TO P2.
ADD B TO C.
WRITE R2 FROM X.
GO TO P2.
P4. CLOSE F1 F2. DISPLAY C. STOP RUN.
*========================================================*
* GOOD STYLE: Clear, maintainable, production-quality *
*========================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. TRANPROC.
*========================================================*
* PROGRAM: TRANPROC *
* PURPOSE: Process transaction file, total amounts, *
* exclude header records *
* INPUT: TRANFILE - Transaction records *
* OUTPUT: OUTFILE - Non-header records *
*========================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANSACTION-FILE
ASSIGN TO TRANFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-TRAN-STATUS.
SELECT OUTPUT-FILE
ASSIGN TO OUTFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-OUT-STATUS.
DATA DIVISION.
FILE SECTION.
FD TRANSACTION-FILE
RECORDING MODE IS F
RECORD CONTAINS 80 CHARACTERS.
01 TRAN-RECORD PIC X(80).
FD OUTPUT-FILE
RECORDING MODE IS F
RECORD CONTAINS 80 CHARACTERS.
01 OUT-RECORD PIC X(80).
WORKING-STORAGE SECTION.
01 WS-FILE-STATUSES.
05 WS-TRAN-STATUS PIC XX.
05 WS-OUT-STATUS PIC XX.
01 FL-CONTROL-FLAGS.
05 FL-EOF-FLAG PIC 9 VALUE 0.
88 FL-NOT-EOF VALUE 0.
88 FL-EOF VALUE 1.
01 WS-WORK-RECORD PIC X(80).
01 WS-RECORD-TYPE PIC X.
88 WS-HEADER-RECORD VALUE 'H'.
01 AC-RUNNING-TOTAL PIC S9(7)V99 COMP-3
VALUE 0.
01 WS-TRAN-AMOUNT PIC S9(7)V99 COMP-3.
01 CT-COUNTERS.
05 CT-RECORDS-READ PIC 9(7) COMP VALUE 0.
05 CT-RECORDS-WRITTEN PIC 9(7) COMP VALUE 0.
05 CT-HEADERS-SKIPPED PIC 9(7) COMP VALUE 0.
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS
UNTIL FL-EOF
PERFORM 3000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT TRANSACTION-FILE
IF WS-TRAN-STATUS NOT = '00'
DISPLAY 'CANNOT OPEN TRANSACTION FILE: '
WS-TRAN-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
OPEN OUTPUT OUTPUT-FILE
IF WS-OUT-STATUS NOT = '00'
DISPLAY 'CANNOT OPEN OUTPUT FILE: '
WS-OUT-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
PERFORM 2100-READ-TRANSACTION
.
2000-PROCESS.
MOVE TRAN-RECORD TO WS-WORK-RECORD
MOVE WS-WORK-RECORD(1:1) TO WS-RECORD-TYPE
IF WS-HEADER-RECORD
ADD 1 TO CT-HEADERS-SKIPPED
ELSE
MOVE WS-WORK-RECORD(10:9) TO WS-TRAN-AMOUNT
ADD WS-TRAN-AMOUNT TO AC-RUNNING-TOTAL
ON SIZE ERROR
DISPLAY 'TOTAL OVERFLOW'
MOVE 16 TO RETURN-CODE
STOP RUN
END-ADD
WRITE OUT-RECORD FROM WS-WORK-RECORD
IF WS-OUT-STATUS = '00'
ADD 1 TO CT-RECORDS-WRITTEN
ELSE
DISPLAY 'WRITE ERROR: ' WS-OUT-STATUS
MOVE 12 TO RETURN-CODE
END-IF
END-IF
PERFORM 2100-READ-TRANSACTION
.
2100-READ-TRANSACTION.
READ TRANSACTION-FILE
AT END
SET FL-EOF TO TRUE
NOT AT END
ADD 1 TO CT-RECORDS-READ
END-READ
.
3000-TERMINATE.
CLOSE TRANSACTION-FILE
CLOSE OUTPUT-FILE
DISPLAY '=============================='
DISPLAY 'RECORDS READ: ' CT-RECORDS-READ
DISPLAY 'HEADERS SKIPPED: ' CT-HEADERS-SKIPPED
DISPLAY 'RECORDS WRITTEN: ' CT-RECORDS-WRITTEN
DISPLAY 'TOTAL AMOUNT: ' AC-RUNNING-TOTAL
DISPLAY '=============================='
.
Example 2: Data Validation
* BAD: Deeply nested IF, hard to follow
IF A NOT = SPACES
IF B NUMERIC
IF B > 0
IF B < 100000
IF C = 'A' OR C = 'B' OR C = 'C'
MOVE 'Y' TO GOOD-FLAG
END-IF
END-IF
END-IF
END-IF
END-IF
* GOOD: Flat structure with early exit logic
2200-VALIDATE-FIELDS.
SET WS-RECORD-VALID TO TRUE
IF WS-CUST-ID = SPACES
MOVE 'CUSTOMER ID REQUIRED'
TO WS-ERROR-MESSAGE
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-AMOUNT NOT NUMERIC
MOVE 'AMOUNT MUST BE NUMERIC'
TO WS-ERROR-MESSAGE
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-RECORD-VALID AND WS-AMOUNT NUMERIC
IF WS-AMOUNT <= 0
MOVE 'AMOUNT MUST BE POSITIVE'
TO WS-ERROR-MESSAGE
SET WS-RECORD-INVALID TO TRUE
END-IF
IF WS-AMOUNT >= 100000
MOVE 'AMOUNT EXCEEDS LIMIT'
TO WS-ERROR-MESSAGE
SET WS-RECORD-INVALID TO TRUE
END-IF
END-IF
IF WS-RECORD-VALID
EVALUATE WS-TRANS-TYPE
WHEN 'A' CONTINUE
WHEN 'B' CONTINUE
WHEN 'C' CONTINUE
WHEN OTHER
MOVE 'INVALID TRANSACTION TYPE'
TO WS-ERROR-MESSAGE
SET WS-RECORD-INVALID TO TRUE
END-EVALUATE
END-IF
.
21.10 Code Review Checklist
Use this checklist when reviewing COBOL code, whether your own or a colleague's:
Identification and Structure
- [ ] Program has a complete header comment (program name, purpose, author, date, files, return codes, change log)
- [ ] PROCEDURE DIVISION follows top-down design
- [ ] Paragraphs are numbered and follow verb-noun naming
- [ ] No GO TO statements (except forward GO TO to EXIT paragraph if shop standard permits)
- [ ] No ALTER statements
- [ ] No NEXT SENTENCE (use CONTINUE instead)
- [ ] All scope terminators are explicit (END-IF, END-READ, etc.)
- [ ] Program ends with STOP RUN or GOBACK
Data Division
- [ ] All WORKING-STORAGE variables have prefix (WS-, CT-, FL-, etc.)
- [ ] All LINKAGE SECTION variables have LS- or LK- prefix
- [ ] File record fields have file-derived prefix
- [ ] 88-level conditions use adjective naming
- [ ] All numeric fields have VALUE 0 clauses
- [ ] All flags have VALUE clauses
- [ ] COMP-3 used for arithmetic/file data, COMP for subscripts
- [ ] All signed fields use PIC S9
File Handling
- [ ] Every file has FILE STATUS clause
- [ ] Every I/O operation is followed by status check
- [ ] Files are opened at program start and closed at termination
- [ ] Controlled shutdown closes all files on error
Error Handling
- [ ] All arithmetic has ON SIZE ERROR
- [ ] All CALL statements have ON EXCEPTION (for dynamic calls)
- [ ] INVALID KEY used on keyed file operations
- [ ] AT END used on sequential READ
- [ ] Error counter maintained and checked against limit
- [ ] Processing summary displayed at end of program
- [ ] Return code set based on error conditions
Defensive Programming
- [ ] All input data validated before processing
- [ ] Numeric validation (IS NUMERIC) before arithmetic
- [ ] Table subscripts validated against bounds
- [ ] Division by zero guarded
- [ ] Reference modification validated
Performance
- [ ] COMP used for subscripts and loop counters
- [ ] Large table lookups use SEARCH ALL where possible
- [ ] No unnecessary I/O operations
- [ ] No redundant MOVE operations
Documentation
- [ ] Program header is complete and accurate
- [ ] Major paragraphs have purpose comments
- [ ] Complex business logic has explanatory comments
- [ ] Change log is maintained
- [ ] Comments explain WHY, not WHAT
21.11 Adapting Standards for GnuCOBOL
When developing with GnuCOBOL on open platforms, most of the standards in this chapter apply without modification. However, there are a few adaptations:
Free-Format Source
GnuCOBOL supports free-format source code (not constrained to columns 7-72). If your shop uses free format, the column 7 debugging line (D) is replaced by >>D:
>>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FREEFORMAT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNTER PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
0000-MAIN.
>>D DISPLAY 'DEBUG: STARTING PROGRAM'
PERFORM 1000-PROCESS
>>D DISPLAY 'DEBUG: COMPLETED PROCESSING'
STOP RUN
.
File Assignment
GnuCOBOL maps file names to operating system paths rather than DD names:
* GnuCOBOL file assignment
SELECT CUSTOMER-FILE
ASSIGN TO 'customer.dat'
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS CUST-KEY
FILE STATUS IS WS-CUST-STATUS.
* Or via environment variable:
SELECT CUSTOMER-FILE
ASSIGN TO WS-CUST-FILENAME
Compiler Options
For GnuCOBOL, defensive compiler options should be standard:
# Recommended GnuCOBOL compiler options
cobc -x \
-Wall \ # All warnings
-Wextra \ # Extra warnings
-fcheck \ # Runtime bounds checking
-debug \ # Debug mode
program.cob
21.12 CICS Programming Standards
CICS programs require additional standards beyond those for batch COBOL programs.
COMMAREA Design Standards
The COMMAREA is the primary mechanism for passing data between pseudo-conversational instances. Design it carefully:
* GOOD: Structured COMMAREA with versioning
01 WS-COMMAREA.
05 CA-VERSION PIC X(2) VALUE '01'.
05 CA-PROGRAM-STATE PIC X.
88 CA-INITIAL VALUE 'I'.
88 CA-MAP-SENT VALUE 'M'.
88 CA-DETAIL VALUE 'D'.
05 CA-LAST-MAP PIC X(8).
05 CA-ERROR-FLAG PIC 9.
05 CA-PRIMARY-KEY PIC X(10).
05 CA-SAVED-DATA.
10 CA-CUST-NAME PIC X(30).
10 CA-BALANCE PIC S9(7)V99 COMP-3.
10 CA-TIMESTAMP PIC X(26).
05 FILLER PIC X(20).
The FILLER at the end provides room for future expansion. The version field allows backward compatibility when the COMMAREA layout changes.
RESP/RESP2 Standards
Always use RESP and RESP2 on every EXEC CICS command. Never use HANDLE CONDITION in new programs:
* REQUIRED: RESP on every EXEC CICS command
EXEC CICS READ
FILE('CUSTFILE')
INTO(WS-RECORD)
RIDFLD(WS-KEY)
LENGTH(WS-LENGTH)
RESP(WS-CICS-RESP)
RESP2(WS-CICS-RESP2)
END-EXEC
* PROHIBITED in new programs:
* EXEC CICS HANDLE CONDITION ...
Map Field Initialization
Always initialize the output map to LOW-VALUES before populating it. This ensures that unchanged fields are not transmitted, improving performance:
* REQUIRED before every SEND MAP
MOVE LOW-VALUES TO CUSTMAPO
* Then populate only the fields that have data
MOVE WS-CUST-NAME TO CUSTNMO
MOVE WS-BAL-FORMATTED TO CUSTBALO
MOVE WS-MESSAGE TO MSGEXTO
AID Key Handling Standards
Every CICS program must handle all possible AID keys, not just the ones the user is expected to press:
* REQUIRED: Handle all AID keys
EVALUATE EIBAID
WHEN DFHENTER
PERFORM 2100-PROCESS-ENTER
WHEN DFHPF3
PERFORM 9000-EXIT
WHEN DFHPF12
PERFORM 1000-RESET-SCREEN
WHEN DFHCLEAR
PERFORM 9000-EXIT
WHEN DFHPA1
CONTINUE
WHEN DFHPA2
CONTINUE
WHEN DFHPA3
CONTINUE
WHEN OTHER
MOVE 'INVALID KEY PRESSED'
TO MSGEXTO
PERFORM 3000-RESEND-MAP
END-EVALUATE
The PA keys (PA1, PA2, PA3) should typically be handled with CONTINUE because some terminal emulators generate them inadvertently.
21.13 Standards for Team Environments
Source Control
All COBOL source should be maintained in a version control system (Git, Endevor on z/OS, or similar). Follow these practices:
- One logical change per commit: Do not mix unrelated changes.
- Meaningful commit messages: "Fix S0C7 in CUSTUPDT paragraph 2200 by adding NUMERIC check on AMOUNT field (CHG0087)" is good. "Fix bug" is not.
- Branch naming: Use the change number or feature name:
feature/CHG0087-zip-validation.
Code Review Process
- All code changes should be reviewed by at least one other programmer.
- The reviewer should use the checklist from Section 21.10.
- Review comments should reference the specific standard being violated.
- The original programmer should make corrections and re-submit.
Copybook Management
Shared data structures should be defined in copybooks and included with COPY statements:
* In copybook CUSTOMER.cpy:
01 CUSTOMER-RECORD.
05 CUST-ID PIC X(10).
05 CUST-NAME PIC X(30).
05 CUST-ADDRESS PIC X(50).
05 CUST-BALANCE PIC S9(7)V99 COMP-3.
05 CUST-STATUS PIC X.
* In program:
COPY CUSTOMER.
Copybook standards: - One record layout per copybook - Copybook names match the data entity (CUSTOMER.cpy, TRANSACTION.cpy) - Copybooks do not contain level 01 items in some shops (the including program provides the 01) - Changes to copybooks require recompilation of all programs that use them
Summary
COBOL coding standards are not bureaucratic overhead -- they are the accumulated wisdom of decades of maintaining programs that run the world's financial systems. The standards in this chapter address every aspect of COBOL program construction:
- Naming conventions use prefixes (WS-, CT-, FL-, LS-) to identify variable scope and purpose, verb-noun paragraphs for procedures, and adjective-style 88-level condition names.
- Program structure follows top-down design with numbered paragraphs, single entry/single exit, and the complete avoidance of GO TO.
- Data definition uses consistent level numbers (01, 05, 10, 15), appropriate numeric formats (COMP-3 for data, COMP for subscripts), and mandatory VALUE clauses for initialization.
- Documentation includes comprehensive program headers with change logs, paragraph purpose comments, and inline comments that explain the business reason rather than restating the code.
- File handling requires FILE STATUS on every file and status checking after every I/O operation, with no exceptions.
- Error handling requires ON SIZE ERROR on all arithmetic, explicit scope terminators on all statements, error counters with limits, and controlled shutdown procedures.
- Performance considerations guide the choice of numeric formats, table search algorithms, and I/O patterns.
- Deprecated features -- ALTER, NEXT SENTENCE, GO TO -- are identified and replaced with modern structured alternatives.
- Code review is supported by a comprehensive checklist covering structure, data, files, errors, defense, performance, and documentation.
The goal of all these standards is a single outcome: any competent COBOL programmer should be able to pick up any program in the shop and understand it, modify it, and maintain it without requiring the original author's assistance. In a language where programs live for forty years, that is not a luxury -- it is a necessity.
21.14 JCL Standards for COBOL Programs
While JCL (Job Control Language) is not COBOL, the JCL that runs COBOL programs is part of the application and should follow consistent standards.
Standard JCL Job Structure
//*========================================================*
//* JOB: CUSTUPDT *
//* PURPOSE: Daily customer master update *
//* INPUT: PROD.DAILY.TRANSACTIONS *
//* OUTPUT: PROD.CUSTOMER.MASTER (updated in place) *
//* PROD.DAILY.ERRORS *
//* FREQUENCY: Daily, after TRANPREP job completes *
//* DEPENDENCIES: TRANPREP must complete with RC <= 4 *
//* CONTACT: App Support Team, ext. 1234 *
//*========================================================*
//CUSTUPDT JOB (ACCT),'CUSTOMER UPDATE',
// CLASS=A,MSGCLASS=X,MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*
//STEP01 EXEC PGM=CUSTUPDT
//STEPLIB DD DSN=PROD.LOADLIB,DISP=SHR
//TRANFILE DD DSN=PROD.DAILY.TRANSACTIONS,DISP=SHR
//CUSTFILE DD DSN=PROD.CUSTOMER.MASTER,DISP=SHR
//ERRFILE DD DSN=PROD.DAILY.ERRORS,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,5),RLSE),
// DCB=(RECFM=FB,LRECL=130,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
JCL Standards Checklist
- Every job must have a descriptive header comment
- NOTIFY=&SYSUID for development; remove for production
- SYSUDUMP or SYSABEND DD statement for dump capture
- SYSOUT DD for DISPLAY output
- Consistent naming for DD names that match COBOL SELECT ASSIGN
- Space allocation appropriate for expected output volume
- DISP=(NEW,CATLG,DELETE) for output files (delete on failure)
Matching JCL DD Names to COBOL
The JCL DD name must match the external name in the COBOL ASSIGN clause:
* In the COBOL program:
SELECT TRANSACTION-FILE
ASSIGN TO TRANFILE.
* In the JCL:
//TRANFILE DD DSN=PROD.DAILY.TRANSACTIONS,DISP=SHR
The name TRANFILE must match exactly between the COBOL program and the JCL. A mismatch produces file status '96' (missing DD) at OPEN time.
21.15 Modernization-Ready Standards
When writing new COBOL code or refactoring existing code, follow standards that facilitate future modernization efforts such as migration to Java, web service exposure, or microservices decomposition.
Encapsulate Business Logic in Subprograms
Business logic that is encapsulated in well-defined subprograms with clear LINKAGE SECTION interfaces can be wrapped as web services or called from non-COBOL languages more easily than monolithic programs:
* GOOD: Business logic in a callable subprogram
IDENTIFICATION DIVISION.
PROGRAM-ID. CALCPREM.
* Calculates insurance premium based on policy data.
* Can be called from batch, CICS, or web service wrapper.
DATA DIVISION.
LINKAGE SECTION.
01 LS-POLICY-DATA.
05 LS-AGE PIC 9(3).
05 LS-COVERAGE PIC S9(7)V99 COMP-3.
05 LS-RISK-CLASS PIC X.
01 LS-PREMIUM-RESULT.
05 LS-MONTHLY-PREM PIC S9(5)V99 COMP-3.
05 LS-ANNUAL-PREM PIC S9(7)V99 COMP-3.
01 LS-RETURN-CODE PIC S9(4) COMP.
PROCEDURE DIVISION USING LS-POLICY-DATA
LS-PREMIUM-RESULT
LS-RETURN-CODE.
Avoid Platform-Specific Dependencies
Where possible, use standard COBOL features rather than vendor-specific extensions. When vendor-specific features are necessary, isolate them in dedicated paragraphs or subprograms so they can be replaced:
* GOOD: Platform-specific code isolated
2500-GET-CURRENT-TIMESTAMP.
* On IBM: uses CURRENT-DATE intrinsic function
* On GnuCOBOL: same function, different implementation
MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP-RAW
.
Use Standard Data Formats
When defining data interfaces between programs, use standard formats that are recognizable across platforms:
- Dates: YYYY-MM-DD (ISO 8601)
- Timestamps: YYYY-MM-DD-HH.MM.SS.NNNNNN
- Monetary amounts: PIC S9(7)V99 COMP-3 with explicit sign
- Character data: Fixed-length PIC X fields
Exercises
-
Naming Convention Exercise: Take a program you wrote in a previous chapter and rename all variables and paragraphs to conform to the naming conventions in Section 21.1. Compare the readability before and after.
-
Restructure GO TO: The following code uses GO TO extensively. Rewrite it using structured programming (PERFORM, EVALUATE, IF/ELSE):
READ-LOOP.
READ INPUT-FILE AT END GO TO WRAP-UP.
IF REC-TYPE = 'A' GO TO HANDLE-A.
IF REC-TYPE = 'D' GO TO HANDLE-D.
ADD 1 TO ERR-COUNT.
GO TO READ-LOOP.
HANDLE-A.
ADD AMT TO TOTAL-A. GO TO READ-LOOP.
HANDLE-D.
SUBTRACT AMT FROM TOTAL-D. GO TO READ-LOOP.
WRAP-UP.
CLOSE INPUT-FILE. DISPLAY TOTAL-A. STOP RUN.
-
Code Review: Apply the checklist from Section 21.10 to one of your previous programs. Document every standard violation you find and correct each one.
-
Good vs. Bad Comparison: Write two versions of a program that reads a customer file, validates each record, and writes valid records to an output file. The first version should deliberately violate as many standards as possible (while still compiling). The second version should follow every standard in this chapter. Present both versions to a colleague and ask which one they would prefer to maintain.
-
Copybook Design: Design a set of copybooks for a banking application that includes customer records, account records, and transaction records. Follow all naming and data definition standards. Include appropriate 88-level conditions for status codes and account types.