16 min read

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...

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

  1. Use meaningful names: Every data name and paragraph name should describe its purpose without requiring a comment.
  2. Use hyphens to separate words: COBOL uses hyphens, not underscores or camelCase.
  3. Be consistent: Once you establish a naming pattern, follow it throughout the program.
  4. 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:

  1. One logical change per commit: Do not mix unrelated changes.
  2. Meaningful commit messages: "Fix S0C7 in CUSTUPDT paragraph 2200 by adding NUMERIC check on AMOUNT field (CHG0087)" is good. "Fix bug" is not.
  3. Branch naming: Use the change number or feature name: feature/CHG0087-zip-validation.

Code Review Process

  1. All code changes should be reviewed by at least one other programmer.
  2. The reviewer should use the checklist from Section 21.10.
  3. Review comments should reference the specific standard being violated.
  4. 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

  1. 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.

  2. 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.
  1. 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.

  2. 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.

  3. 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.