Coding Standards and Best Practices

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), in which explicit scope terminators and condition names come from COBOL 85 and the in-line PERFORM and free-form source come from COBOL 2002; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which the program on it was compiled with cobc -x -Wall (and with -std=ibm) and run on 2026-10-20 without warnings. There is no official COBOL style standard: the conventions below are the common practice of shops and courses, restated here, and a team should agree its own list and apply it with a review checklist.

COBOL programs live for decades and are read far more often than written, usually by someone who did not write them. The rules that matter most are therefore the ones that make a program easy to read top to bottom, hard to break by a small edit, and safe against bad input. The page gives those rules, then one complete program that follows them, and a checklist for review. The statements are described on PERFORM and Structured Programming, Conditions: IF and EVALUATE, VALUE, INITIALIZE and Condition Names and Copybooks and REPLACE; testing the result is on Testing COBOL Programs.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 4) teaches the language elements in the order of the divisions and shows GO TO and paragraph fall-through among the control-flow forms. They are valid and still compile, but modern practice is the structured style below; old constructs and their replacements are collected, labelled "old code you may meet", on Legacy Features and Migration.

Layout and structure

  • One idea per paragraph. A paragraph or section does one named thing (2100-READ-LOAN, 2200-VALIDATE-LOAN). If its name needs "and", split it.

  • One period per paragraph. Write statements without full stops inside a paragraph and put one period at the end. A stray period in the middle of an IF ends it and makes the rest unconditional, which is the oldest bug in COBOL.

  • Explicit scope terminators. Close IF with END-IF, READ with END-READ, EVALUATE with END-EVALUATE, PERFORM with END-PERFORM, COMPUTE with END-COMPUTE and so on, so that nesting is visible and an inserted statement lands where it looks.

  • Structured control flow. Use PERFORM of paragraphs and in-line PERFORM ... END-PERFORM, EVALUATE and IF. Do not use GO TO and ALTER for new code; they are covered only as old code on the legacy page.

  • Numbered paragraphs in reading order. A numeric prefix (1000-, 2000-) shows the structure and the order in listings, and a main paragraph at the top reads as the table of contents of the program.

  • One entry, one exit. Leave the program in one place (STOP RUN or GOBACK), after closing files and setting RETURN-CODE; the one exception worth making is a hard stop on an I/O failure, as the example does.

  • Keep to the format. In fixed format, code starts in area A for divisions, sections and paragraph names and in area B for statements (column 12), and nothing is written past column 72; see Reference Format: Fixed and Free.

Names

Convention Example

Prefix by storage area

WS- for WORKING-STORAGE, LS- for LOCAL-STORAGE, LK- for LINKAGE, FD- or the file name for record areas.

Prefix by kind

WS-...-COUNT, WS-...-FLAG, WS-...-STATUS for counters, switches and file-status items.

Names say what, not how

WS-REJECT-COUNT, not WS-CTR2.

Condition names read like a question

LOAN-CHECKOUT, WS-AT-END, BOOK-ACTIVE.

Copybook names are the record names

BOOKREC, MEMBREC, LOANREC; the fields keep one prefix inside (BOOK-, MEMBER-, LOAN-).

One spelling per concept

MEMBER-ID everywhere, never MEMB-NO in one program and MBR-ID in another.

Names up to 30 characters are allowed in Enterprise COBOL (GnuCOBOL allows longer ones); use them to be clear and abbreviate only by a rule written down. Do not reuse reserved words, and avoid names that differ only in a hyphen.

Data

  • Flags are PIC X with 88-levels, not literals. SET WS-AT-END TO TRUE and IF WS-AT-END replace tests against "Y" scattered through the program, and the values are defined once.

  • Record layouts come from copybooks. Every program that reads LOAN-RECORD copies LOANREC, so a change to the file format is one edit and one recompilation of the programs that use it. Share a copybook, not a pasted copy; see the review rules at the end for changing a shared copybook.

  • Initialise explicitly. Give VALUE clauses to counters and switches, and use INITIALIZE for work areas that are reused; INITCHECK on z/OS and the GnuCOBOL -Wall warnings find the ones that were missed.

  • Size numerics for the largest value including totals, and give money a sign and decimals: PIC S9(7)V99. A counter that overflows its picture silently wraps.

  • Group items for related fields, 77-levels never. A 01 group with 05 items documents which fields belong together, and REDEFINES and MOVE CORRESPONDING can use the group.

  • No magic numbers. Put the fine rate and cap, and table sizes, in named items.

Defensive input and output

  • Always code FILE STATUS on every file, and test it after OPEN, READ, WRITE and CLOSE. A program without a status item stops with a run-time error when the file is wrong; a program with one and no test carries on with bad data. The codes are in File Status Codes.

  • Treat end of file as normal and everything else as an error. The example below accepts status 00 and 10 and stops on any other value after printing it.

  • Set RETURN-CODE deliberately (0 success, 4 warnings such as rejected records, 8 or 12 failure) and document the meaning in the program header, so the job’s COND/IF tests are reliable.

  • Validate numerics before arithmetic. Test numeric fields from files with the class condition IF field IS NUMERIC before computing with them; non-numeric data is the cause of S0C7 abends on z/OS (see Debugging and Troubleshooting).

  • Code ON SIZE ERROR on divisions and on additions that can exceed the receiving field, or the result is silently truncated.

  • Use EVALUATE for decision tables. A table of conditions and actions (here: type, member, book) is a flat EVALUATE TRUE with WHEN branches and a WHEN OTHER, not nested `IF`s.

  • Reject, do not repair. Write bad records to a rejects file or the report with the reason, and let the nightly run continue; do not guess a corrected value.

A complete example

The program validates the loan transactions: a record is good when the type is O (checkout) or R (return) and both identifiers are numeric. It follows the rules above: numbered single-purpose paragraphs, a priming read, 88-level flags, a copybook for the layout, a status test after every file operation, an EVALUATE for the rules, and a return code of 4 when something was rejected.

      * VALLOAN - validates the daily loan transactions.
       IDENTIFICATION DIVISION.
       PROGRAM-ID. VALLOAN.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-LOAN-STATUS.
       DATA DIVISION.
       FILE SECTION.
       FD  LOAN-FILE.
       COPY LOANREC.
       WORKING-STORAGE SECTION.
       01  WS-LOAN-STATUS     PIC XX.
           88  WS-LOAN-OK         VALUE "00".
           88  WS-LOAN-EOF        VALUE "10".
       01  WS-SWITCHES.
           05  WS-EOF-FLAG    PIC X VALUE "N".
               88  WS-AT-END      VALUE "Y".
               88  WS-NOT-AT-END  VALUE "N".
       01  WS-COUNTERS.
           05  WS-READ-COUNT  PIC 9(5) VALUE 0.
           05  WS-GOOD-COUNT  PIC 9(5) VALUE 0.
           05  WS-REJECT-COUNT PIC 9(5) VALUE 0.
       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-OPEN-FILE
           PERFORM 2000-PROCESS-LOANS UNTIL WS-AT-END
           PERFORM 3000-CLOSE-AND-REPORT
           IF WS-REJECT-COUNT > 0
               MOVE 4 TO RETURN-CODE
           END-IF
           STOP RUN.
       1000-OPEN-FILE.
           OPEN INPUT LOAN-FILE
           IF NOT WS-LOAN-OK
               DISPLAY "OPEN FAILED, STATUS " WS-LOAN-STATUS
               MOVE 12 TO RETURN-CODE
               STOP RUN
           END-IF
           PERFORM 2100-READ-LOAN.
       2000-PROCESS-LOANS.
           PERFORM 2200-VALIDATE-LOAN
           PERFORM 2100-READ-LOAN.
       2100-READ-LOAN.
           READ LOAN-FILE
               AT END SET WS-AT-END TO TRUE
               NOT AT END ADD 1 TO WS-READ-COUNT
           END-READ
           IF NOT WS-LOAN-OK AND NOT WS-LOAN-EOF
               DISPLAY "READ FAILED, STATUS " WS-LOAN-STATUS
               MOVE 12 TO RETURN-CODE
               STOP RUN
           END-IF.
       2200-VALIDATE-LOAN.
           EVALUATE TRUE
               WHEN NOT LOAN-CHECKOUT AND NOT LOAN-RETURN
                   PERFORM 2900-REJECT
               WHEN LOAN-MEMBER-ID IS NOT NUMERIC
                   PERFORM 2900-REJECT
               WHEN LOAN-BOOK-ID IS NOT NUMERIC
                   PERFORM 2900-REJECT
               WHEN OTHER
                   ADD 1 TO WS-GOOD-COUNT
           END-EVALUATE.
       2900-REJECT.
           ADD 1 TO WS-REJECT-COUNT
           DISPLAY "REJECT " LOAN-TYPE " " LOAN-MEMBER-ID
               " " LOAN-BOOK-ID.
       3000-CLOSE-AND-REPORT.
           CLOSE LOAN-FILE
           DISPLAY "READ " WS-READ-COUNT " GOOD " WS-GOOD-COUNT
               " REJECTED " WS-REJECT-COUNT.

The input is the four-line LOANTRAN of the Circulation scenario plus a fifth line whose type is X. Compiled with cobc -x -Wall -I. VALLOAN.cob (and with -std=ibm), no warnings, it prints the following and ends with exit status 4:

REJECT X 000103 000002
READ 00005 GOOD 00004 REJECTED 00001

Run in a directory without LOANTRAN the program prints OPEN FAILED, STATUS 35 and ends with status 12. The structure is the point: 0000-MAIN shows the whole flow in five lines, and a change to one rule is a change to one WHEN.

Comments and documentation

  • A header comment per program: purpose, inputs and outputs (data sets or files and their copybooks), return codes, the author or team and the change history with the ticket reference. The version-control history is the authority for who changed what; the header says why the program exists.

  • Comment the why, not the what. ADD 1 TO WS-GOOD-COUNT needs no comment; "loyalty members joined before 2021 get the discount" does.

  • Section comments for the divisions are noise; put the effort in the paragraph names.

  • Keep comments and code together. An out-of-date comment is worse than none. In fixed format a comment has * in column 7; in free format *> anywhere.

  • Do not leave dead code in comments; version control remembers it.

Review checklist

  1. Does every file have FILE STATUS, and is it tested after each operation?

  2. Does each IF, READ, EVALUATE, PERFORM and arithmetic statement have its scope terminator, and is there exactly one period per paragraph?

  3. Are numeric fields from outside tested before use, and does arithmetic that can overflow have ON SIZE ERROR?

  4. Are layouts from copybooks, and do the edits to a shared copybook list every program that uses it so that all of them are recompiled and tested? A copybook used by many programs should be changed only with a review of its users.

  5. Are flags 88-levels and are literal codes ("O", "R") only in the copybook?

  6. Does the program set a documented RETURN-CODE?

  7. Does it compile with -Wall (GnuCOBOL) and with the project’s standard Enterprise COBOL options without new warnings?

  8. Is there a test for the new rule (Testing COBOL Programs)?

Common mistakes

  • Nesting IF`s without `END-IF and a stray period. The statements after the period are outside every IF.

  • Editing a paragraph in the middle of a PERFORM THRU range. A new paragraph inserted between the named ends becomes part of the range; use PERFORM of one paragraph.

  • Reusing a work field for two meanings. A field that is a count in one paragraph and a date in another hides defects; define one field per meaning.

  • Testing a flag with a literal in one place and an 88-level in another.

  • Style fixes mixed with logic changes in one commit. Reviewers cannot see the logic change; make the formatting change on its own.