> "Every COBOL program tells you exactly what it is, what it needs, what data it uses, and what it does — in that order. No other language is this organized. No other language forces you to be this deliberate."
In This Chapter
- 3.1 The Column Conventions: COBOL's Physical Structure
- 3.2 The IDENTIFICATION DIVISION
- 3.3 The ENVIRONMENT DIVISION
- 3.4 The DATA DIVISION
- 3.5 The PROCEDURE DIVISION
- 3.6 Copybooks: The COPY Statement
- 3.7 Putting It All Together: Anatomy of ACCT-MAINT
- 3.8 Common Anti-Patterns
- 3.9 The GlobalBank COBOL Coding Standards (Excerpt)
- 3.10 Looking Ahead
- Chapter Summary
Chapter 3: COBOL Program Structure Deep Dive
"Every COBOL program tells you exactly what it is, what it needs, what data it uses, and what it does — in that order. No other language is this organized. No other language forces you to be this deliberate." — Maria Chen, GlobalBank Senior Developer
Your introductory COBOL course taught you that a COBOL program has four divisions: IDENTIFICATION, ENVIRONMENT, DATA, and PROCEDURE. That is true. It is also about ten percent of the story.
In this chapter, we explore every corner of COBOL program structure — not just the divisions, but the sections within them, the paragraphs within those sections, the sentences and statements that compose the logic, and the column conventions that govern where every character must be placed. We examine copybooks, the COBOL mechanism for code reuse that production systems depend on. We study the SPECIAL-NAMES paragraph, the SOURCE-COMPUTER and OBJECT-COMPUTER clauses, and the configuration options that most introductory courses skip.
This chapter is long and detailed. That is intentional. Understanding program structure at this level is the difference between a student who can write COBOL and a developer who can read, maintain, and extend production COBOL. The programs you will encounter in the real world — programs with thousands of lines, dozens of copybooks, and decades of accumulated modifications — demand this depth of understanding.
By the end of this chapter, you will be able to read any COBOL program and understand its organization. You will know why every element is placed where it is. And you will have a template for writing well-structured programs of your own.
3.1 The Column Conventions: COBOL's Physical Structure
Before we examine the four divisions, we must understand the physical layout of a COBOL source line. COBOL was designed in the era of punched cards, where each card had 80 columns. That 80-column structure persists in COBOL today, and it is not optional — it is syntactically significant.
Fixed-Format Source (Traditional)
In traditional fixed-format COBOL, each line is divided into areas:
Column: 1234567 8901 234567890... 7890
| | | | | |
| Seq| | | | Area B |
| Num| |A | | (columns 12-72) |
| |I|re| | |Ident
| |n|a | | |Area
| |d|A | | |
📊 Column Map
| Columns | Name | Purpose |
|---|---|---|
| 1–6 | Sequence Number Area | Line numbering (ignored by compiler) |
| 7 | Indicator Area | Special characters: * (comment), - (continuation), / (page eject), D (debugging line) |
| 8–11 | Area A | Division headers, section headers, paragraph names, FD, SD, 01/77 levels |
| 12–72 | Area B | Statements, clauses, and subordinate data items |
| 73–80 | Identification Area | Program identification (ignored by compiler) |
Let us examine each area in detail.
Columns 1–6: Sequence Number Area
In the punched card era, these columns contained sequence numbers so that a dropped deck of cards could be reassembled in order. Today, they are typically blank or contain numbers generated by the editor. The compiler ignores them completely.
000100 IDENTIFICATION DIVISION.
000200 PROGRAM-ID. EXAMPLE.
000300 DATA DIVISION.
On modern systems, you will often see these columns left blank:
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
💡 Key Insight: When you see COBOL source with six leading spaces, those spaces are occupying the sequence number area. The actual code starts at column 7 (indicator) or column 8 (Area A). This is why all the code examples in this textbook have leading spaces — they are respecting the column layout.
Column 7: Indicator Area
Column 7 is arguably the most important single column in COBOL. Its value determines how the entire line is interpreted:
| Character | Meaning | Example |
|---|---|---|
| (space) | Normal source line | MOVE A TO B |
* |
Comment line (entire line is ignored) | *This is a comment |
- |
Continuation of previous line | - "continued text" |
/ |
Comment + page eject in listing | / New page in listing |
D |
Debugging line (compiled only with debugging mode) | D DISPLAY "DEBUG" |
Comments are any line with * in column 7. Unlike many languages, COBOL has no in-line comment syntax in the traditional standard. You cannot place a comment at the end of a code line. (Some compilers support *> as an inline comment, introduced in COBOL 2002, but this is not universally available.)
*================================================================*
* This is a comment block. Every line has * in column 7.
* Comments are essential for maintainability.
*================================================================*
MOVE WS-AMOUNT TO WS-TOTAL
* The line above moves the amount to the total field.
Continuation lines use - in column 7 to continue a long literal or statement from the previous line. The continued text begins in Area B of the continuation line:
01 WS-LONG-MESSAGE PIC X(80) VALUE "This is a ve
- "ry long string that continues on the next line"
- ".".
⚠️ Continuation Rules: When continuing a non-numeric literal (string), the closing quote of the first line is omitted, and the continuation line begins with - in column 7 followed by a quote in Area B that marks where the string resumes. This is a frequent source of errors for beginners. Modern practice is to avoid continuation lines when possible by using STRING or breaking the logic into shorter statements.
Debugging lines use D in column 7. These lines are only compiled when the WITH DEBUGGING MODE clause is specified in the SOURCE-COMPUTER paragraph:
SOURCE-COMPUTER. IBM-390 WITH DEBUGGING MODE.
...
D DISPLAY "DEBUG: WS-COUNTER = " WS-COUNTER
When compiled without debugging mode, lines with D in column 7 are treated as comments.
Columns 8–11: Area A
Area A is where structural elements begin. The following must start in Area A (columns 8–11):
- Division headers:
IDENTIFICATION DIVISION. - Section headers:
WORKING-STORAGE SECTION.,FILE SECTION. - Paragraph names:
100-MAIN-PROCESS.,200-READ-INPUT. - FD and SD entries:
FD ACCOUNT-FILE.,SD SORT-FILE. - 01 level and 77 level data items:
01 WS-ACCOUNT-REC.,77 WS-COUNTER PIC 9(5).
IDENTIFICATION DIVISION. <-- Division header, Area A
PROGRAM-ID. ACCT-MAINT. <-- Paragraph, Area A
ENVIRONMENT DIVISION. <-- Division header, Area A
CONFIGURATION SECTION. <-- Section header, Area A
INPUT-OUTPUT SECTION. <-- Section header, Area A
FILE-CONTROL. <-- Paragraph, Area A
SELECT ACCOUNT-FILE <-- Statement, Area B
ASSIGN TO ACCTFILE. <-- Clause, Area B
DATA DIVISION. <-- Division header, Area A
FILE SECTION. <-- Section header, Area A
FD ACCOUNT-FILE. <-- FD entry, Area A
01 ACCOUNT-RECORD. <-- 01 level, Area A
05 ACCT-NUMBER PIC X(10). <-- 05 level, Area B
05 ACCT-NAME PIC X(30). <-- 05 level, Area B
Columns 12–72: Area B
Area B is where the actual program logic and subordinate data definitions live. Everything that is not required to start in Area A goes in Area B:
- COBOL statements (
MOVE,ADD,IF,PERFORM, etc.) - Subordinate data items (levels 02–49, 66, 88)
- SELECT, ASSIGN, and other clauses
- PICTURE, VALUE, USAGE, and other data description clauses
Columns 73–80: Identification Area
Like the sequence number area, columns 73–80 are ignored by the compiler. They were originally used to identify the program or deck on each card. In modern practice, they are usually blank.
A Complete Source Line Example
Let us trace through a complete source line to cement your understanding. Consider this line, shown with column positions marked:
Columns: 1234567890123456789012345678901234567890...
Line: 000100 IF WS-BALANCE > 0
- Columns 1–6:
000100— Sequence number. Ignored by compiler. - Column 7: Space — Normal source line (not a comment, not a continuation).
- Columns 8–11: Spaces — Nothing starts in Area A (this line is a statement, not a structural element).
- Columns 12+:
IF WS-BALANCE > 0— The actual statement, in Area B.
Now compare with a paragraph name:
Columns: 1234567890123456789012345678901234567890...
Line: 000200 200-PROCESS-ACCOUNTS.
- Columns 1–6:
000200— Sequence number. - Column 7: Space — Normal source line.
- Columns 8–11:
200-— The paragraph name starts in Area A (column 8).
And a comment:
Columns: 1234567890123456789012345678901234567890...
Line: 000300*This line is entirely a comment
- Columns 1–6:
000300— Sequence number. - Column 7:
*— Indicator: this entire line is a comment. - Columns 8–72: Comment text. The compiler ignores everything.
This may seem pedantic, but when you are debugging a program that will not compile and the error message says "unexpected token in Area A," knowing exactly which columns constitute Area A is the difference between a five-second fix and a thirty-minute search.
The Practical Impact of Column Conventions
In modern practice, most developers do not count columns manually. The editor handles it. VS Code with a COBOL extension shows column rulers and automatically indents to the correct position. ISPF on z/OS respects the column layout implicitly.
However, you must understand the conventions because:
-
Reading legacy code. Production programs were written across decades using various editors with different habits. You will encounter code where the indentation is inconsistent, where someone used tab characters (which COBOL does not officially support), or where a critical line starts one column too far to the left or right.
-
Debugging compilation errors. "Syntax error at line 47" might really mean "the ELSE keyword starts in column 11 (Area A) instead of column 12 (Area B)." Without column awareness, you will stare at perfectly correct-looking code and not see the problem.
-
Copy-paste pitfalls. Copying COBOL code from a web page, PDF, or email into your editor can shift columns. The code looks identical but compiles differently because invisible whitespace has changed.
Free-Format Source
COBOL 2002 introduced free-format source, where column positions do not matter. In free format:
- There is no sequence number area
- There is no indicator area (comments use *>)
- Area A and Area B distinctions do not apply
- Lines can be up to 255 characters long
*> This is a free-format COBOL program
IDENTIFICATION DIVISION.
PROGRAM-ID. FREE-FORMAT-EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(30).
PROCEDURE DIVISION.
DISPLAY "Hello from free format!"
STOP RUN.
To compile free-format source with GnuCOBOL, use the -free flag:
cobc -x -free PROGRAM.cbl
⚠️ Production Reality: Despite free format being available since 2002, the vast majority of production COBOL code uses fixed format. If you work on existing systems, you will read and write fixed-format code. This textbook uses fixed format for all examples to prepare you for that reality. We mention free format so you know it exists, but we do not recommend using it until you are fully comfortable with fixed format.
🧪 Try It Yourself: Column Awareness
Write a COBOL program that intentionally places elements in the wrong area — for example, a paragraph name starting in column 12 (Area B) instead of column 8 (Area A). Compile it and observe the error message. Then fix it. This exercise builds the column awareness that separates COBOL programmers from COBOL students.
3.2 The IDENTIFICATION DIVISION
The IDENTIFICATION DIVISION is the simplest division and the only one that is strictly required (every COBOL program must have an IDENTIFICATION DIVISION with at least a PROGRAM-ID).
Structure
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCT-MAINT.
AUTHOR. MARIA CHEN.
INSTALLATION. GLOBALBANK DATA CENTER.
DATE-WRITTEN. 2024-01-15.
DATE-COMPILED.
SECURITY. CONFIDENTIAL - INTERNAL USE ONLY.
*================================================================*
* Program: ACCT-MAINT
* Purpose: Account maintenance - add, modify, delete accounts
* System: GlobalBank Core Banking
* Module: Account Management (AM)
* CICS Trans: AM01
*
* Change Log:
* Date Author Change ID Description
* ---------- -------------- ------------ ----------------------
* 2024-01-15 Maria Chen CHG-2024-001 Initial version
* 2024-06-20 Derek Wash. CHG-2024-045 Add email validation
* 2025-02-10 Maria Chen CHG-2025-012 New reg. requirement
*================================================================*
Paragraphs of the IDENTIFICATION DIVISION
| Paragraph | Required? | Purpose |
|---|---|---|
| PROGRAM-ID | Yes | Names the program (up to 30 characters) |
| AUTHOR | No (obsolete in 2002+) | Programmer name |
| INSTALLATION | No (obsolete) | Where the program runs |
| DATE-WRITTEN | No (obsolete) | When the program was originally written |
| DATE-COMPILED | No (obsolete) | Replaced by compiler with compilation date |
| SECURITY | No (obsolete) | Security classification |
| REMARKS | No (obsolete, removed in COBOL-85) | General comments |
💡 Key Insight: The AUTHOR, INSTALLATION, DATE-WRITTEN, DATE-COMPILED, and SECURITY paragraphs were marked as obsolete in COBOL 2002. However, you will see them constantly in production code because that code predates the 2002 standard. Most shops continue to use them by convention even in new programs. Do not be surprised to find them; do not be afraid to use them; but know that the standard considers them unnecessary because the same information can go in comments.
PROGRAM-ID Details
The PROGRAM-ID paragraph has more to it than most introductory courses reveal:
PROGRAM-ID. ACCT-MAINT IS INITIAL PROGRAM.
- IS INITIAL — Specifies that the program's working storage is re-initialized every time it is called. Without this clause, COBOL programs retain their working storage values between calls (this is called being "resident" or "quasi-reentrant" in CICS terms).
- IS COMMON — In nested programs, marks the program as accessible from sibling programs.
- IS RECURSIVE — Allows the program to call itself (available from COBOL 2002).
⚠️ The IS INITIAL Trap: In CICS programs, whether working storage is re-initialized between invocations has critical implications. A program that assumes its working storage starts clean but does not specify IS INITIAL (or does not run in a reinitialized environment) will carry data from the previous transaction — a common source of bugs that are maddeningly intermittent.
🔗 Cross-Reference: Chapter 31 covers CICS pseudo-conversational design, where the IS INITIAL clause and working storage behavior become critical concerns.
3.3 The ENVIRONMENT DIVISION
The ENVIRONMENT DIVISION describes the computing environment in which the program was developed and will run, and defines the connection between the program's logical files and the physical devices or files of the operating system. It has two sections: CONFIGURATION SECTION and INPUT-OUTPUT SECTION.
CONFIGURATION SECTION
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. IBM-390.
OBJECT-COMPUTER. IBM-390.
SPECIAL-NAMES.
DECIMAL-POINT IS COMMA.
SOURCE-COMPUTER Paragraph
Identifies the computer where the program is compiled. In practice, this paragraph is largely vestigial — the compiler knows what machine it is running on. However, it has one important clause:
SOURCE-COMPUTER. IBM-390 WITH DEBUGGING MODE.
The WITH DEBUGGING MODE clause activates debugging lines (those with D in column 7) and enables the USE FOR DEBUGGING declarative. Without this clause, debugging lines are treated as comments.
OBJECT-COMPUTER Paragraph
Identifies the computer where the program will execute. Its most useful clauses:
OBJECT-COMPUTER. IBM-390
PROGRAM COLLATING SEQUENCE IS NATL-SORT.
The PROGRAM COLLATING SEQUENCE clause specifies the character ordering for comparisons and sorting. This is relevant when you need non-standard collation — for example, sorting Scandinavian characters correctly or implementing case-insensitive comparisons.
SPECIAL-NAMES Paragraph
This is where the ENVIRONMENT DIVISION gets genuinely interesting. The SPECIAL-NAMES paragraph maps implementation-defined names to features that affect program behavior:
SPECIAL-NAMES.
DECIMAL-POINT IS COMMA
CURRENCY SIGN IS "EUR" WITH PICTURE SYMBOL "E"
CLASS VALID-AMOUNT IS "0" THROUGH "9"
"." ","
SYMBOLIC CHARACTERS
HORIZONTAL-TAB IS 10
NULL-CHAR IS 1
.
DECIMAL-POINT IS COMMA: Swaps the roles of the period and comma in numeric editing. With this clause, PIC 9.999,99 means the comma is the decimal separator and the period is the thousands separator — the European convention. Without it, PIC 9,999.99 is the American convention.
📊 DECIMAL-POINT IS COMMA Effect
| Without clause | With clause |
|---|---|
PIC Z,ZZZ,ZZ9.99 displays 1,234,567.89 |
PIC Z.ZZZ.ZZ9,99 displays 1.234.567,89 |
| Period is decimal separator | Comma is decimal separator |
| U.S./U.K. convention | European convention |
This is critically important for international banking applications. GlobalBank, with operations in both the U.S. and Europe, maintains separate copybooks with and without this clause for programs that handle currency display in different regions.
CURRENCY SIGN: Changes the currency symbol used in PICTURE clauses:
* Default behavior:
01 WS-USD-AMOUNT PIC $$$,$$9.99.
* Displays: $1,234.56
* With CURRENCY SIGN IS "EUR" WITH PICTURE SYMBOL "E":
01 WS-EUR-AMOUNT PIC EEE,EE9.99.
* Displays: EUR1,234.56
CLASS: Defines user-defined character classes for use with the CLASS condition:
SPECIAL-NAMES.
CLASS VALID-ACCT-CHAR IS
"0" THROUGH "9"
"A" THROUGH "Z"
"-".
* In PROCEDURE DIVISION:
IF WS-ACCT-ID IS VALID-ACCT-CHAR
CONTINUE
ELSE
DISPLAY "Invalid account ID characters"
END-IF
This is a powerful feature for input validation that introductory courses rarely cover. In production systems like MedClaim's claims processing, CLASS definitions are used to validate data at the point of receipt — before the data enters the processing pipeline. Sarah Kim, the business analyst, worked with James Okafor to define CLASS conditions for every input field in the claims record: valid diagnosis code characters, valid provider ID characters, valid member ID characters. This validation layer catches data quality issues early, before they can corrupt downstream processing.
Multiple CLASS definitions can be combined for layered validation:
SPECIAL-NAMES.
CLASS DIGIT IS "0" THROUGH "9"
CLASS ALPHA-UPPER IS "A" THROUGH "Z"
CLASS VALID-NAME-CHAR IS
"A" THROUGH "Z"
"a" THROUGH "z"
" " "'" "-".
* In PROCEDURE DIVISION:
IF WS-ZIP-CODE IS DIGIT
CONTINUE
ELSE
MOVE "INVALID ZIP" TO WS-ERROR-MSG
END-IF
IF WS-LAST-NAME IS VALID-NAME-CHAR
CONTINUE
ELSE
MOVE "INVALID NAME CHARS" TO WS-ERROR-MSG
END-IF
SYMBOLIC CHARACTERS: Names specific characters by their ordinal position in the character set:
SPECIAL-NAMES.
SYMBOLIC CHARACTERS
TAB-CHAR IS 10
LF-CHAR IS 11
IN NATIVE.
* In PROCEDURE DIVISION:
INSPECT WS-INPUT-LINE
REPLACING ALL TAB-CHAR BY SPACE
This is useful for handling special characters in data, such as tabs in imported files.
🧪 Try It Yourself: SPECIAL-NAMES Exploration
Write a program that uses the DECIMAL-POINT IS COMMA clause and displays currency amounts in both European and American format (hint: you will need to move the same numeric value to two differently formatted fields). Then try the CLASS clause to validate an account number.
INPUT-OUTPUT SECTION
The INPUT-OUTPUT SECTION defines the files the program will use. It contains one paragraph: FILE-CONTROL.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCOUNT-FILE
ASSIGN TO ACCTFILE
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS ACCT-KEY
ALTERNATE RECORD KEY IS ACCT-NAME-KEY
WITH DUPLICATES
FILE STATUS IS WS-ACCT-STATUS.
SELECT REPORT-FILE
ASSIGN TO RPTFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
SELECT SORT-FILE
ASSIGN TO SORTWORK.
Each SELECT statement defines one file and describes its organization, access method, keys (for indexed files), and status field. Let us examine the clauses:
ASSIGN TO: Maps the logical file name to a physical file. On z/OS, this connects to a JCL DD statement. On GnuCOBOL, it maps to an environment variable or file name.
ORGANIZATION: Specifies the file structure:
- SEQUENTIAL — Records are accessed in order (default)
- INDEXED — Records are accessed by key (VSAM KSDS)
- RELATIVE — Records are accessed by relative record number (VSAM RRDS)
- LINE SEQUENTIAL — ASCII text file with line delimiters (GnuCOBOL extension)
ACCESS MODE: How the program will read/write records:
- SEQUENTIAL — From beginning to end
- RANDOM — By key (indexed) or record number (relative)
- DYNAMIC — Both sequential and random in the same program
RECORD KEY: The primary key for indexed files.
ALTERNATE RECORD KEY: Additional keys for indexed files. WITH DUPLICATES allows multiple records with the same alternate key value.
FILE STATUS: A two-byte field that receives a status code after every file operation. This is the foundation of defensive file handling.
📊 Common FILE STATUS Values
| Status | Meaning |
|---|---|
| 00 | Successful |
| 10 | End of file |
| 22 | Duplicate key (indexed file) |
| 23 | Record not found (indexed file) |
| 30 | Permanent I/O error |
| 35 | File not found (OPEN) |
| 39 | File attributes conflict |
| 41 | File already open |
| 42 | File not open |
| 47 | READ on file not opened INPUT or I-O |
| 48 | WRITE on file not opened OUTPUT, I-O, or EXTEND |
⚠️ Defensive Programming Rule: Every SELECT statement should include a FILE STATUS clause, and every file operation should check the status code. Ignoring file status is the single most common source of silent data corruption in COBOL programs. James Okafor says: "If I review your code and find a READ without a FILE STATUS check, I send it back. No discussion."
🔗 Cross-Reference: Chapters 14–19 cover file handling in depth. Chapter 15 is entirely dedicated to VSAM and indexed file processing.
3.4 The DATA DIVISION
The DATA DIVISION is where COBOL's distinctive character emerges most clearly. No other widely-used language devotes this much structural attention to data definition. The DATA DIVISION is not just a place to declare variables — it is a comprehensive data architecture specification.
Sections of the DATA DIVISION
| Section | Purpose | When Used |
|---|---|---|
| FILE SECTION | Defines record layouts for files | When program reads/writes files |
| WORKING-STORAGE SECTION | Defines program variables | Nearly every program |
| LOCAL-STORAGE SECTION | Like WORKING-STORAGE but reinitialized per invocation | Reentrant programs, CICS |
| LINKAGE SECTION | Defines data received from calling programs | Called subprograms |
| REPORT SECTION | Defines report layouts for Report Writer | Report Writer programs |
| SCREEN SECTION | Defines screen layouts for terminal I/O | Interactive programs (non-CICS) |
| COMMUNICATION SECTION | Defines communication areas | Obsolete; was for MCS |
FILE SECTION
The FILE SECTION describes the record structure of each file declared in the SELECT statement:
DATA DIVISION.
FILE SECTION.
FD ACCOUNT-FILE
RECORDING MODE IS F
RECORD CONTAINS 200 CHARACTERS
BLOCK CONTAINS 0 RECORDS
LABEL RECORDS ARE STANDARD
DATA RECORD IS ACCOUNT-RECORD.
01 ACCOUNT-RECORD.
05 ACCT-KEY.
10 ACCT-BRANCH PIC X(4).
10 ACCT-NUMBER PIC X(10).
05 ACCT-NAME PIC X(30).
05 ACCT-TYPE PIC X(2).
88 ACCT-CHECKING VALUE "CH".
88 ACCT-SAVINGS VALUE "SV".
88 ACCT-MONEY-MKT VALUE "MM".
05 ACCT-BALANCE PIC S9(11)V99 COMP-3.
05 ACCT-OPEN-DATE PIC 9(8).
05 ACCT-STATUS PIC X(1).
88 ACCT-ACTIVE VALUE "A".
88 ACCT-CLOSED VALUE "C".
88 ACCT-FROZEN VALUE "F".
05 FILLER PIC X(131).
FD (File Description) Clauses:
- RECORDING MODE:
F(fixed),V(variable),U(undefined),S(spanned). Most COBOL files use fixed-length records. - RECORD CONTAINS n CHARACTERS: Specifies the record length.
- BLOCK CONTAINS 0 RECORDS: Tells the system to use optimal blocking (0 means "let the system decide"). On z/OS, this interacts with the DCB in JCL.
- LABEL RECORDS ARE STANDARD: A vestigial clause from the tape era. Still required by some compilers but effectively meaningless today.
- DATA RECORD IS: Names the 01-level record(s) under this FD. If an FD has multiple 01-level records, they are implicit redefinitions of the same physical space.
💡 Key Insight: Multiple 01 Levels Under One FD
A single FD can have multiple 01-level record descriptions. They all describe the same physical record area — they are different "views" of the same data. This is how programs handle files with multiple record types:
FD TRANSACTION-FILE.
01 TRANS-DEPOSIT.
05 TRANS-TYPE-D PIC X VALUE "D".
05 TRANS-ACCT-D PIC X(10).
05 TRANS-AMOUNT-D PIC S9(9)V99 COMP-3.
05 FILLER PIC X(77).
01 TRANS-WITHDRAWAL.
05 TRANS-TYPE-W PIC X VALUE "W".
05 TRANS-ACCT-W PIC X(10).
05 TRANS-AMOUNT-W PIC S9(9)V99 COMP-3.
05 TRANS-AUTH-W PIC X(8).
05 FILLER PIC X(69).
After a READ, you check the first byte to determine which record layout applies, then reference the appropriate 01-level name. This is a pattern you will see constantly in production COBOL.
WORKING-STORAGE SECTION
WORKING-STORAGE is the "general purpose" data area. It holds: - Program variables and accumulators - Constants and literal values - Formatted output fields - Status flags and switches - Work areas for string manipulation - Counters and indexes - Any data not associated with a file or linkage parameter
WORKING-STORAGE SECTION.
*--- Constants ---
01 WS-CONSTANTS.
05 WS-PROGRAM-NAME PIC X(12)
VALUE "ACCT-MAINT".
05 WS-VERSION PIC X(6)
VALUE "V02.01".
05 WS-MAX-RETRIES PIC 9 VALUE 3.
*--- Switches and flags ---
01 WS-FLAGS.
05 WS-EOF-FLAG PIC X VALUE "N".
88 END-OF-FILE VALUE "Y".
88 NOT-END-OF-FILE VALUE "N".
05 WS-ERROR-FLAG PIC X VALUE "N".
88 ERROR-FOUND VALUE "Y".
88 NO-ERROR VALUE "N".
*--- Counters ---
01 WS-COUNTERS.
05 WS-READ-COUNT PIC 9(7) COMP VALUE 0.
05 WS-WRITE-COUNT PIC 9(7) COMP VALUE 0.
05 WS-ERROR-COUNT PIC 9(5) COMP VALUE 0.
*--- File status fields ---
01 WS-STATUS-FIELDS.
05 WS-ACCT-STATUS PIC XX VALUE SPACES.
05 WS-RPT-STATUS PIC XX VALUE SPACES.
*--- Work areas ---
01 WS-WORK-AREAS.
05 WS-CURRENT-DATE-DATA.
10 WS-CURRENT-DATE PIC 9(8).
10 WS-CURRENT-TIME PIC 9(8).
10 WS-GMT-DIFF PIC X(5).
05 WS-FORMATTED-DATE PIC X(10).
Organization Matters: Notice how the working storage is organized into logical groups using 01-level group items. This is not just aesthetics — it is a maintainability practice. When Maria Chen reviews code, she expects to see working storage organized into clear categories: constants, flags, counters, status fields, work areas, and so forth. "If I cannot find a variable in three seconds," she says, "the working storage is poorly organized."
LOCAL-STORAGE SECTION
LOCAL-STORAGE is identical to WORKING-STORAGE in syntax but differs in behavior: LOCAL-STORAGE is re-initialized to its VALUE clauses every time the program is invoked. WORKING-STORAGE retains its values between invocations (when the program is called repeatedly).
LOCAL-STORAGE SECTION.
01 LS-INVOCATION-DATA.
05 LS-START-TIME PIC 9(8).
05 LS-RETURN-CODE PIC S9(4) COMP VALUE 0.
05 LS-TEMP-AMOUNT PIC S9(9)V99 VALUE 0.
This distinction is critical in CICS programming, where a single load module may serve thousands of concurrent transactions. Using LOCAL-STORAGE ensures each transaction starts with clean data.
🔗 Cross-Reference: Chapter 31 explores the WORKING-STORAGE vs. LOCAL-STORAGE choice in depth for CICS programs.
LINKAGE SECTION
The LINKAGE SECTION defines data that is passed to the program from its caller. The program does not own this data — it receives a pointer to data that resides in the calling program's memory.
LINKAGE SECTION.
01 LS-ACCOUNT-REQUEST.
05 LS-FUNCTION-CODE PIC X.
88 LS-FUNC-ADD VALUE "A".
88 LS-FUNC-MODIFY VALUE "M".
88 LS-FUNC-DELETE VALUE "D".
88 LS-FUNC-INQUIRE VALUE "I".
05 LS-ACCT-NUMBER PIC X(10).
05 LS-ACCT-DATA PIC X(200).
05 LS-RETURN-CODE PIC S9(4) COMP.
05 LS-ERROR-MESSAGE PIC X(80).
The LINKAGE SECTION data is connected to the program through the PROCEDURE DIVISION USING clause:
PROCEDURE DIVISION USING LS-ACCOUNT-REQUEST.
🔗 Cross-Reference: Chapters 20–24 cover subprograms and the LINKAGE SECTION in depth.
Level Numbers
COBOL uses level numbers to define data hierarchies. This is one of the language's most distinctive features:
| Level | Purpose | Area |
|---|---|---|
| 01 | Record or independent item | Area A |
| 02–49 | Subordinate items within a group | Area B |
| 66 | RENAMES (alternative grouping) | Area B |
| 77 | Independent elementary item (no subordinates) | Area A |
| 88 | Condition name (named value) | Area B |
Levels 02–49 define a hierarchy. The specific numbers do not matter; only the relative values matter for establishing parent-child relationships. By convention, most shops use increments of 5 (05, 10, 15) to allow insertion of intermediate levels without renumbering:
01 WS-CUSTOMER-RECORD.
05 WS-CUST-ID PIC X(10).
05 WS-CUST-NAME.
10 WS-FIRST-NAME PIC X(20).
10 WS-MIDDLE-INIT PIC X.
10 WS-LAST-NAME PIC X(25).
05 WS-CUST-ADDRESS.
10 WS-STREET PIC X(30).
10 WS-CITY PIC X(20).
10 WS-STATE PIC XX.
10 WS-ZIP-CODE.
15 WS-ZIP-5 PIC X(5).
15 WS-ZIP-4 PIC X(4).
Level 66 — RENAMES: Creates an alternative grouping of existing data items. This is rarely used in new code but appears in legacy programs:
01 WS-DATE-RECORD.
05 WS-YEAR PIC 9(4).
05 WS-MONTH PIC 9(2).
05 WS-DAY PIC 9(2).
66 WS-YEAR-MONTH RENAMES WS-YEAR THROUGH WS-MONTH.
Level 77 — Independent items: An elementary item with no subordinates. Functionally equivalent to a standalone 01-level elementary item. Some shops have coding standards that discourage 77-level items in favor of grouped 01-level items for better organization.
Level 88 — Condition names: One of COBOL's most elegant features. A level 88 associates a meaningful name with one or more values of its parent item:
05 WS-ACCT-TYPE PIC X(2).
88 CHECKING VALUE "CH".
88 SAVINGS VALUE "SV".
88 MONEY-MARKET VALUE "MM".
88 CERTIFICATE-OF-DEP VALUE "CD".
88 VALID-ACCT-TYPE VALUE "CH" "SV" "MM" "CD".
* In PROCEDURE DIVISION:
IF CHECKING
PERFORM 500-PROCESS-CHECKING
END-IF
IF VALID-ACCT-TYPE
PERFORM 600-PROCESS-ACCOUNT
ELSE
PERFORM 900-INVALID-TYPE
END-IF
* Setting a condition name:
SET CHECKING TO TRUE
* This moves "CH" to WS-ACCT-TYPE
💡 Key Insight: Level 88 condition names make code dramatically more readable. Compare IF WS-ACCT-TYPE = "CH" with IF CHECKING. The second version communicates intent; the first requires you to know that "CH" means checking. In production code with hundreds of such conditions, the readability difference is enormous.
The REDEFINES Clause
REDEFINES allows you to interpret the same area of memory in different ways. It is one of COBOL's most powerful — and most dangerous — features:
01 WS-RAW-DATE PIC X(8) VALUE "20260311".
01 WS-STRUCTURED-DATE REDEFINES WS-RAW-DATE.
05 WS-DATE-YEAR PIC 9(4).
05 WS-DATE-MONTH PIC 9(2).
05 WS-DATE-DAY PIC 9(2).
After the MOVE of "20260311" to WS-RAW-DATE, you can reference WS-DATE-YEAR to get 2026, WS-DATE-MONTH to get 03, and WS-DATE-DAY to get 11. Both names — WS-RAW-DATE and WS-STRUCTURED-DATE — refer to the same 8 bytes of memory. Changing one changes the other.
REDEFINES is commonly used for:
- Interpreting the same data in different formats (as above — string vs. structured date)
- Providing different views of a multi-format record (though multiple 01 levels under an FD achieve the same thing)
- Creating tables from literal values (defining a string of concatenated values and redefining it as an array)
- Converting between data types (redefining a COMP-3 field as alphanumeric for hex inspection)
⚠️ REDEFINES Dangers: The compiler does not validate that the data in a REDEFINES area is appropriate for the new interpretation. If you REDEFINE an alphanumeric field containing "ABCDEFGH" as a numeric PIC 9(8) field and then attempt arithmetic on it, you will get an S0C7 data exception. REDEFINES requires the programmer to guarantee that the data is meaningful in both interpretations. This is a common source of production bugs, especially when a REDEFINES overlay is based on assumptions about field positions that later change (as we saw in Case Study 1 of this chapter).
🔗 Cross-Reference: Chapter 4 explores REDEFINES in the context of data type manipulation, including safe patterns and common mistakes.
FILLER
The keyword FILLER is used for unnamed data items — positions in a record that exist for alignment or spacing but are not individually referenced:
01 WS-REPORT-HEADER.
05 FILLER PIC X(5) VALUE SPACES.
05 WS-HDR-TITLE PIC X(30)
VALUE "ACCOUNT SUMMARY REPORT".
05 FILLER PIC X(25) VALUE SPACES.
05 WS-HDR-DATE PIC X(10).
05 FILLER PIC X(10) VALUE SPACES.
In COBOL 2002 and later, the word FILLER is optional — you can simply omit the name:
01 WS-REPORT-HEADER.
05 PIC X(5) VALUE SPACES.
05 WS-HDR-TITLE PIC X(30)
VALUE "ACCOUNT SUMMARY REPORT".
3.5 The PROCEDURE DIVISION
The PROCEDURE DIVISION is where the program's logic lives. It is the part that does things. But it has its own organizational structure that intermediate programmers must understand thoroughly.
Structure Hierarchy
The PROCEDURE DIVISION is organized hierarchically:
PROCEDURE DIVISION
└── Sections (optional, named groups of paragraphs)
└── Paragraphs (named groups of sentences)
└── Sentences (one or more statements ending with a period)
└── Statements (individual COBOL verbs with operands)
Paragraphs
A paragraph is a named unit of code, identified by a name starting in Area A followed by a period:
200-PROCESS-ACCOUNTS.
PERFORM 210-READ-NEXT-ACCOUNT
PERFORM UNTIL END-OF-FILE
PERFORM 220-VALIDATE-ACCOUNT
IF NO-ERROR
PERFORM 230-UPDATE-ACCOUNT
END-IF
PERFORM 210-READ-NEXT-ACCOUNT
END-PERFORM
.
Paragraph naming conventions vary by shop, but common patterns include:
- Numeric prefix:
100-INITIALIZE,200-MAIN-PROCESS,300-FINALIZE - Hierarchical numbering:
200-MAIN,210-READ,220-VALIDATE,230-UPDATE - Verb-based:
READ-NEXT-RECORD,VALIDATE-ACCOUNT,WRITE-REPORT-LINE
Maria Chen's team at GlobalBank uses hierarchical numbering: the first digit indicates the major function, subsequent digits indicate subordinate functions. "I can tell from the paragraph number where it fits in the program's structure," she explains.
Sections in the PROCEDURE DIVISION
Sections group related paragraphs. They are optional but useful in larger programs:
PROCEDURE DIVISION.
0000-MAIN SECTION.
0000-MAIN-LOGIC.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS
PERFORM 3000-FINALIZE
STOP RUN
.
1000-INITIALIZE SECTION.
1000-INIT-START.
OPEN INPUT ACCOUNT-FILE
OUTPUT REPORT-FILE
PERFORM 1100-VERIFY-FILES
PERFORM 1200-INITIALIZE-COUNTERS
.
1100-VERIFY-FILES.
IF WS-ACCT-STATUS NOT = "00"
DISPLAY "ERROR OPENING ACCOUNT FILE: "
WS-ACCT-STATUS
PERFORM 9999-ABEND-PROGRAM
END-IF
.
1200-INITIALIZE-COUNTERS.
INITIALIZE WS-COUNTERS
.
⚠️ The Section/Paragraph Scope Rule: When you PERFORM a section, all paragraphs within that section execute sequentially. When you PERFORM a paragraph, only that paragraph executes. This distinction matters enormously:
* This performs ALL paragraphs in 1000-INITIALIZE SECTION:
PERFORM 1000-INITIALIZE
* This performs ONLY the 1100-VERIFY-FILES paragraph:
PERFORM 1100-VERIFY-FILES
In some older programs, you will see PERFORM section-name THRU paragraph-name, which performs from one point to another. This pattern, while still legal, is discouraged in modern COBOL because it creates implicit dependencies between paragraphs and makes the flow of control harder to follow.
Sentences and Statements
A statement is a single COBOL verb with its operands:
MOVE WS-AMOUNT TO WS-TOTAL
A sentence is one or more statements terminated by a period:
MOVE WS-AMOUNT TO WS-TOTAL.
In pre-COBOL-85 code, the period was the only way to terminate conditional scope:
* Pre-COBOL-85 style (dangerous):
IF WS-AMOUNT > 0
ADD WS-AMOUNT TO WS-TOTAL
MOVE "Y" TO WS-FOUND-FLAG.
MOVE SPACES TO WS-MESSAGE.
* The period after WS-FOUND-FLAG ends the IF scope.
* The MOVE SPACES is NOT inside the IF.
In COBOL-85 and later, scope terminators (END-IF, END-PERFORM, etc.) make periods within the PROCEDURE DIVISION largely unnecessary — you need a period only at the end of a paragraph. This is far safer:
* COBOL-85 style (safe):
IF WS-AMOUNT > 0
ADD WS-AMOUNT TO WS-TOTAL
MOVE "Y" TO WS-FOUND-FLAG
END-IF
MOVE SPACES TO WS-MESSAGE
.
⚠️ The Period Problem: In legacy code, a misplaced period can change program logic silently. Consider:
IF WS-BALANCE < 0
PERFORM 500-OVERDRAFT-HANDLING.
MOVE "O" TO WS-STATUS.
The period after 500-OVERDRAFT-HANDLING terminates the IF statement. The MOVE "O" TO WS-STATUS executes unconditionally — every account gets status "O", not just overdrawn ones. This type of bug was so common and so dangerous that the COBOL-85 scope terminators were developed specifically to prevent it.
💡 Key Insight: Modern COBOL best practice is to use exactly one period per paragraph — at the very end. All conditional and loop scope is controlled by END-IF, END-PERFORM, END-EVALUATE, and other scope terminators. This eliminates the period problem entirely.
The EVALUATE Statement
EVALUATE is COBOL's equivalent of a switch/case statement, but more powerful. It can evaluate multiple subjects simultaneously and supports both specific values and conditions:
* Basic EVALUATE (like switch/case):
EVALUATE WS-ACCT-TYPE
WHEN "CH"
PERFORM 500-PROCESS-CHECKING
WHEN "SV"
PERFORM 510-PROCESS-SAVINGS
WHEN "MM"
PERFORM 520-PROCESS-MONEY-MARKET
WHEN OTHER
PERFORM 900-INVALID-TYPE
END-EVALUATE
* EVALUATE TRUE (pattern matching with conditions):
EVALUATE TRUE
WHEN WS-BALANCE > 100000
MOVE "PLATINUM" TO WS-TIER
WHEN WS-BALANCE > 25000
MOVE "GOLD" TO WS-TIER
WHEN WS-BALANCE > 5000
MOVE "SILVER" TO WS-TIER
WHEN OTHER
MOVE "STANDARD" TO WS-TIER
END-EVALUATE
* EVALUATE with multiple subjects:
EVALUATE WS-ACCT-TYPE ALSO WS-ACCT-STATUS
WHEN "CH" ALSO "A"
PERFORM 500-ACTIVE-CHECKING
WHEN "CH" ALSO "F"
PERFORM 510-FROZEN-CHECKING
WHEN "SV" ALSO ANY
PERFORM 520-ANY-SAVINGS
WHEN OTHER
PERFORM 900-DEFAULT
END-EVALUATE
The EVALUATE TRUE pattern is particularly elegant because it allows complex conditional logic to be expressed as a clean, tabular structure. Compare the EVALUATE TRUE above with the equivalent nested IF:
* Equivalent nested IF (harder to read):
IF WS-BALANCE > 100000
MOVE "PLATINUM" TO WS-TIER
ELSE
IF WS-BALANCE > 25000
MOVE "GOLD" TO WS-TIER
ELSE
IF WS-BALANCE > 5000
MOVE "SILVER" TO WS-TIER
ELSE
MOVE "STANDARD" TO WS-TIER
END-IF
END-IF
END-IF
The EVALUATE version is not just more readable — it is also easier to modify. Adding a new tier requires adding one WHEN clause. In the nested IF version, you must carefully insert a new level of nesting in the correct position.
🔗 Cross-Reference: Chapter 9 covers EVALUATE patterns in depth, including advanced multi-subject evaluation and the EVALUATE TRUE ALSO TRUE pattern for complex decision tables.
PROCEDURE DIVISION USING and RETURNING
When a program is called by another program, the PROCEDURE DIVISION header specifies the data it receives and returns:
PROCEDURE DIVISION
USING LS-ACCOUNT-REQUEST
RETURNING LS-RETURN-CODE.
The USING clause lists LINKAGE SECTION items that correspond to parameters passed by the calling program. The RETURNING clause specifies a single item to be returned to the caller.
3.6 Copybooks: The COPY Statement
Copybooks are COBOL's primary mechanism for code reuse. A copybook is a file containing COBOL source code — typically data definitions, but sometimes procedure code — that is included in a program at compile time using the COPY statement.
Why Copybooks Matter
In a large system like GlobalBank's, the same record layout (say, the account record) is used by dozens of programs. Without copybooks, each program would have its own copy of the record definition, and any change to the record layout would require modifying every program individually. With copybooks, the layout is defined once and included everywhere:
*--- In the COBOL program: ---
WORKING-STORAGE SECTION.
01 WS-ACCOUNT-RECORD.
COPY ACCT-REC.
*--- In the copybook file ACCT-REC.cpy: ---
05 ACCT-KEY.
10 ACCT-BRANCH PIC X(4).
10 ACCT-NUMBER PIC X(10).
05 ACCT-NAME PIC X(30).
05 ACCT-TYPE PIC X(2).
88 ACCT-CHECKING VALUE "CH".
88 ACCT-SAVINGS VALUE "SV".
88 ACCT-MONEY-MKT VALUE "MM".
05 ACCT-BALANCE PIC S9(11)V99 COMP-3.
05 ACCT-OPEN-DATE PIC 9(8).
05 ACCT-STATUS PIC X(1).
88 ACCT-ACTIVE VALUE "A".
88 ACCT-CLOSED VALUE "C".
88 ACCT-FROZEN VALUE "F".
05 FILLER PIC X(131).
COPY with REPLACING
The REPLACING clause allows you to modify the copied text as it is included. This is commonly used to prefix data names, avoiding naming conflicts when the same copybook is used multiple times:
01 WS-OLD-ACCOUNT.
COPY ACCT-REC REPLACING ==ACCT-== BY ==OLD-ACCT-==.
01 WS-NEW-ACCOUNT.
COPY ACCT-REC REPLACING ==ACCT-== BY ==NEW-ACCT-==.
The == delimiters mark the text to be replaced and its replacement. After COPY REPLACING, the first copy would have fields like OLD-ACCT-KEY, OLD-ACCT-BRANCH, OLD-ACCT-NAME, etc., and the second would have NEW-ACCT-KEY, NEW-ACCT-BRANCH, NEW-ACCT-NAME.
📊 Copybook Usage at GlobalBank
GlobalBank's core banking system has approximately 1,200 copybooks. Some examples:
| Copybook | Contents | Used by (approx.) |
|---|---|---|
| ACCT-REC | Account master record layout | 340 programs |
| TRAN-REC | Transaction record layout | 280 programs |
| CUST-REC | Customer information record | 250 programs |
| ERR-CODES | Standard error code definitions | 500+ programs |
| RPT-HDR | Standard report header layout | 180 programs |
| DB2-SQLCA | SQL Communication Area (SQLCA) | 420 programs |
| CICS-DFHAID | CICS attention identifier definitions | 200 programs |
When the account record was extended by four bytes to add an email address field, the change was made once in the ACCT-REC copybook. All 340 programs that used it picked up the change automatically on their next compilation. Without copybooks, that would have been 340 individual edits — each an opportunity for error.
Copybook Location
On z/OS, copybooks are stored in PDSs referenced by the SYSLIB DD statement in the compile JCL. On GnuCOBOL, they are found in directories specified by the -I flag:
cobc -x -I /path/to/copybooks PROGRAM.cbl
Copybook Best Practices
- One record layout per copybook. Do not combine unrelated definitions.
- Use consistent naming. The copybook name should clearly indicate its contents.
- Document the copybook. Include a comment block with the layout's purpose, version, and change history.
- Do not include 01 levels in copybooks. The copybook should contain 05-level and below. The 01-level belongs in the program, so different programs can give the group item different names.
- Test after changes. Any copybook modification requires recompilation (and testing) of all programs that use it.
🧪 Try It Yourself: Working with Copybooks
Create a copybook called STUDENT-REC.cpy containing a student record layout (ID, name, major, GPA). Write two programs: one that writes student records to a file using the copybook, and one that reads them. Compile both with the -I flag pointing to your copybook directory.
3.7 Putting It All Together: Anatomy of ACCT-MAINT
Let us examine a complete, production-quality COBOL program — the kind of program you would find in GlobalBank's codebase. This is the ACCT-MAINT program referenced throughout this chapter: an account maintenance program that reads an indexed account file, processes transactions, and generates a report.
The program is presented with annotations explaining each structural element. Study it carefully; this is the template for well-organized COBOL.
*================================================================*
* Sequence area and identification area are blank in modern code
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCT-MAINT.
AUTHOR. MARIA CHEN.
DATE-WRITTEN. 2024-01-15.
*================================================================*
* Program: ACCT-MAINT
* Purpose: Process account maintenance transactions
* (add, modify, close) against the account
* master file.
* System: GlobalBank Core Banking
* Input: TRANFILE - Transaction file (sequential)
* I/O: ACCTFILE - Account master (indexed, VSAM KSDS)
* Output: RPTFILE - Maintenance report (sequential)
*
* File Status Codes Handled:
* ACCTFILE: 00, 22, 23, 35
* TRANFILE: 00, 10, 35
* RPTFILE: 00, 35
*
* Return Codes:
* 0 - Successful completion
* 4 - Completed with warnings
* 8 - Completed with errors
* 16 - Abend (critical error)
*================================================================*
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. IBM-390.
OBJECT-COMPUTER. IBM-390.
SPECIAL-NAMES.
CLASS VALID-FUNCTION IS "A" "M" "C".
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANSACTION-FILE
ASSIGN TO TRANFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-TRAN-STATUS.
SELECT ACCOUNT-FILE
ASSIGN TO ACCTFILE
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS ACCT-KEY
FILE STATUS IS WS-ACCT-STATUS.
SELECT REPORT-FILE
ASSIGN TO RPTFILE
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-RPT-STATUS.
DATA DIVISION.
FILE SECTION.
FD TRANSACTION-FILE
RECORDING MODE IS F
RECORD CONTAINS 100 CHARACTERS.
01 TRANSACTION-RECORD.
05 TRAN-FUNCTION PIC X.
88 TRAN-ADD VALUE "A".
88 TRAN-MODIFY VALUE "M".
88 TRAN-CLOSE VALUE "C".
05 TRAN-ACCT-KEY.
10 TRAN-BRANCH PIC X(4).
10 TRAN-NUMBER PIC X(10).
05 TRAN-DATA PIC X(85).
FD ACCOUNT-FILE
RECORDING MODE IS F
RECORD CONTAINS 200 CHARACTERS.
01 ACCOUNT-RECORD.
COPY ACCT-REC.
FD REPORT-FILE
RECORDING MODE IS F
RECORD CONTAINS 132 CHARACTERS.
01 REPORT-RECORD PIC X(132).
WORKING-STORAGE SECTION.
*--- Program identification ---
01 WS-PROGRAM-INFO.
05 WS-PROGRAM-NAME PIC X(10)
VALUE "ACCT-MAINT".
05 WS-VERSION PIC X(6)
VALUE "V02.01".
*--- File status fields ---
01 WS-STATUS-FIELDS.
05 WS-TRAN-STATUS PIC XX VALUE SPACES.
05 WS-ACCT-STATUS PIC XX VALUE SPACES.
05 WS-RPT-STATUS PIC XX VALUE SPACES.
*--- Switches and flags ---
01 WS-FLAGS.
05 WS-EOF-TRAN PIC X VALUE "N".
88 TRAN-EOF VALUE "Y".
88 TRAN-NOT-EOF VALUE "N".
*--- Counters ---
01 WS-COUNTERS.
05 WS-TRAN-READ PIC 9(7) COMP VALUE 0.
05 WS-TRAN-ADDED PIC 9(7) COMP VALUE 0.
05 WS-TRAN-MODIFIED PIC 9(7) COMP VALUE 0.
05 WS-TRAN-CLOSED PIC 9(7) COMP VALUE 0.
05 WS-TRAN-ERRORS PIC 9(7) COMP VALUE 0.
*--- Return code ---
01 WS-RETURN-CODE PIC S9(4) COMP VALUE 0.
*--- Report lines ---
01 WS-RPT-HEADER.
05 FILLER PIC X(5) VALUE SPACES.
05 FILLER PIC X(30)
VALUE "ACCOUNT MAINTENANCE REPORT".
05 FILLER PIC X(50) VALUE SPACES.
05 WS-RPT-DATE PIC X(10).
05 FILLER PIC X(37) VALUE SPACES.
01 WS-RPT-DETAIL.
05 FILLER PIC X(3) VALUE SPACES.
05 WS-DTL-FUNCTION PIC X(8).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-ACCOUNT PIC X(14).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-RESULT PIC X(20).
05 FILLER PIC X(2) VALUE SPACES.
05 WS-DTL-MESSAGE PIC X(60).
05 FILLER PIC X(21) VALUE SPACES.
PROCEDURE DIVISION.
*================================================================*
* MAIN CONTROL PARAGRAPH
*================================================================*
000-MAIN.
PERFORM 100-INITIALIZE
PERFORM 200-PROCESS-TRANSACTIONS
PERFORM 300-FINALIZE
MOVE WS-RETURN-CODE TO RETURN-CODE
STOP RUN
.
*================================================================*
* INITIALIZATION
*================================================================*
100-INITIALIZE.
OPEN INPUT TRANSACTION-FILE
I-O ACCOUNT-FILE
OUTPUT REPORT-FILE
PERFORM 110-CHECK-FILE-STATUS
PERFORM 120-WRITE-REPORT-HEADER
.
110-CHECK-FILE-STATUS.
IF WS-TRAN-STATUS NOT = "00"
DISPLAY "ERROR OPENING TRANSACTION FILE: "
WS-TRAN-STATUS
MOVE 16 TO WS-RETURN-CODE
PERFORM 300-FINALIZE
STOP RUN
END-IF
IF WS-ACCT-STATUS NOT = "00"
DISPLAY "ERROR OPENING ACCOUNT FILE: "
WS-ACCT-STATUS
MOVE 16 TO WS-RETURN-CODE
PERFORM 300-FINALIZE
STOP RUN
END-IF
IF WS-RPT-STATUS NOT = "00"
DISPLAY "ERROR OPENING REPORT FILE: "
WS-RPT-STATUS
MOVE 16 TO WS-RETURN-CODE
PERFORM 300-FINALIZE
STOP RUN
END-IF
.
120-WRITE-REPORT-HEADER.
MOVE FUNCTION CURRENT-DATE(1:10) TO WS-RPT-DATE
WRITE REPORT-RECORD FROM WS-RPT-HEADER
.
*================================================================*
* MAIN PROCESSING LOOP
*================================================================*
200-PROCESS-TRANSACTIONS.
PERFORM 210-READ-TRANSACTION
PERFORM UNTIL TRAN-EOF
ADD 1 TO WS-TRAN-READ
EVALUATE TRUE
WHEN TRAN-ADD
PERFORM 220-ADD-ACCOUNT
WHEN TRAN-MODIFY
PERFORM 230-MODIFY-ACCOUNT
WHEN TRAN-CLOSE
PERFORM 240-CLOSE-ACCOUNT
WHEN OTHER
PERFORM 250-INVALID-FUNCTION
END-EVALUATE
PERFORM 210-READ-TRANSACTION
END-PERFORM
.
210-READ-TRANSACTION.
READ TRANSACTION-FILE
AT END SET TRAN-EOF TO TRUE
NOT AT END CONTINUE
END-READ
IF WS-TRAN-STATUS NOT = "00"
AND WS-TRAN-STATUS NOT = "10"
DISPLAY "READ ERROR ON TRANSACTION FILE: "
WS-TRAN-STATUS
MOVE 8 TO WS-RETURN-CODE
SET TRAN-EOF TO TRUE
END-IF
.
220-ADD-ACCOUNT.
MOVE TRAN-ACCT-KEY TO ACCT-KEY
READ ACCOUNT-FILE
INVALID KEY CONTINUE
NOT INVALID KEY
PERFORM 260-WRITE-ERROR
"DUPLICATE - ACCOUNT EXISTS"
GO TO 220-EXIT
END-READ
INITIALIZE ACCOUNT-RECORD
MOVE TRAN-ACCT-KEY TO ACCT-KEY
MOVE TRAN-DATA TO ACCT-NAME
MOVE "A" TO ACCT-STATUS
WRITE ACCOUNT-RECORD
IF WS-ACCT-STATUS = "00"
ADD 1 TO WS-TRAN-ADDED
PERFORM 270-WRITE-SUCCESS "ADDED"
ELSE
PERFORM 260-WRITE-ERROR
"WRITE FAILED"
END-IF
.
220-EXIT.
EXIT
.
230-MODIFY-ACCOUNT.
MOVE TRAN-ACCT-KEY TO ACCT-KEY
READ ACCOUNT-FILE
INVALID KEY
PERFORM 260-WRITE-ERROR
"ACCOUNT NOT FOUND"
GO TO 230-EXIT
END-READ
MOVE TRAN-DATA TO ACCT-NAME
REWRITE ACCOUNT-RECORD
IF WS-ACCT-STATUS = "00"
ADD 1 TO WS-TRAN-MODIFIED
PERFORM 270-WRITE-SUCCESS "MODIFIED"
ELSE
PERFORM 260-WRITE-ERROR
"REWRITE FAILED"
END-IF
.
230-EXIT.
EXIT
.
240-CLOSE-ACCOUNT.
MOVE TRAN-ACCT-KEY TO ACCT-KEY
READ ACCOUNT-FILE
INVALID KEY
PERFORM 260-WRITE-ERROR
"ACCOUNT NOT FOUND"
GO TO 240-EXIT
END-READ
IF ACCT-CLOSED
PERFORM 260-WRITE-ERROR
"ACCOUNT ALREADY CLOSED"
GO TO 240-EXIT
END-IF
SET ACCT-CLOSED TO TRUE
REWRITE ACCOUNT-RECORD
IF WS-ACCT-STATUS = "00"
ADD 1 TO WS-TRAN-CLOSED
PERFORM 270-WRITE-SUCCESS "CLOSED"
ELSE
PERFORM 260-WRITE-ERROR
"REWRITE FAILED"
END-IF
.
240-EXIT.
EXIT
.
250-INVALID-FUNCTION.
ADD 1 TO WS-TRAN-ERRORS
MOVE 4 TO WS-RETURN-CODE
MOVE "INVALID" TO WS-DTL-FUNCTION
MOVE TRAN-ACCT-KEY TO WS-DTL-ACCOUNT
MOVE "REJECTED" TO WS-DTL-RESULT
MOVE "INVALID FUNCTION CODE" TO WS-DTL-MESSAGE
WRITE REPORT-RECORD FROM WS-RPT-DETAIL
.
260-WRITE-ERROR.
ADD 1 TO WS-TRAN-ERRORS
MOVE 4 TO WS-RETURN-CODE
.
270-WRITE-SUCCESS.
CONTINUE
.
*================================================================*
* FINALIZATION
*================================================================*
300-FINALIZE.
PERFORM 310-WRITE-SUMMARY
CLOSE TRANSACTION-FILE
ACCOUNT-FILE
REPORT-FILE
.
310-WRITE-SUMMARY.
DISPLAY "==============================="
DISPLAY " ACCT-MAINT PROCESSING SUMMARY"
DISPLAY "==============================="
DISPLAY " Transactions read: "
WS-TRAN-READ
DISPLAY " Accounts added: "
WS-TRAN-ADDED
DISPLAY " Accounts modified: "
WS-TRAN-MODIFIED
DISPLAY " Accounts closed: "
WS-TRAN-CLOSED
DISPLAY " Errors: "
WS-TRAN-ERRORS
DISPLAY " Return code: "
WS-RETURN-CODE
DISPLAY "==============================="
.
Understanding the Program Flow
Before we analyze the structural elements, let us trace the program's execution flow:
-
000-MAINcalls100-INITIALIZE, which opens all three files, checks that they opened successfully (aborting if any failed), and writes the report header. -
000-MAINcalls200-PROCESS-TRANSACTIONS, which reads the first transaction, then enters a loop that continues until end-of-file. For each transaction, it uses EVALUATE TRUE to dispatch to the appropriate processing paragraph based on the transaction type. -
Each processing paragraph (220 through 240) follows the same pattern: read the account file to verify the account exists (or does not exist, for adds), perform the operation, check the file status, update counters, write a detail line to the report, and handle error conditions.
-
000-MAINcalls300-FINALIZE, which writes a summary to the console and closes all files. -
The program sets RETURN-CODE and executes STOP RUN.
This initialize-process-finalize pattern is universal in batch COBOL programs. You will see it in every example throughout this textbook, with variations in the processing logic but the same overall structure. Derek Washington calls it "the COBOL three-act play": setup, action, cleanup.
Notice how the program handles multiple error conditions: - File open failures (checked in 110-CHECK-FILE-STATUS) result in immediate termination with return code 16. - Transaction processing errors (duplicate account, account not found, already closed) result in error counter increment and return code 4 (warnings). - File I/O errors during processing result in return code 8 (errors).
This graduated error handling — where different types of problems produce different return codes — allows the JCL that runs this program to take appropriate action: continue the batch cycle on return code 4, investigate on return code 8, and alert operations on return code 16.
What Makes This Program "Production Quality"
Let us identify the structural elements that distinguish this from a student exercise:
-
Comprehensive comment header: Purpose, inputs, outputs, error handling, return codes — everything a maintenance programmer needs to understand the program without reading the code.
-
Meaningful paragraph names with hierarchical numbering: 000-MAIN, 100-INITIALIZE, 200-PROCESS-TRANSACTIONS, 300-FINALIZE. The structure is immediately apparent.
-
FILE STATUS checking after every file operation: No file operation goes unchecked. This is defensive programming at its most fundamental.
-
Level 88 condition names everywhere: TRAN-ADD, TRAN-MODIFY, TRAN-CLOSE, TRAN-EOF, ACCT-ACTIVE, ACCT-CLOSED — the code reads like English.
-
EVALUATE TRUE pattern: The EVALUATE TRUE with level 88 conditions is the COBOL equivalent of a clean switch statement.
-
Organized WORKING-STORAGE: Grouped into program info, status fields, flags, counters, and report lines. Every item has a clear purpose.
-
Return codes: The program communicates its status to the calling JCL through the RETURN-CODE special register (0=success, 4=warnings, 8=errors, 16=critical).
-
Copybook usage: The ACCT-REC copybook ensures the account record definition is shared across all programs that access the account file.
3.8 Common Anti-Patterns
Knowing what good structure looks like is half the battle. Knowing what bad structure looks like is the other half. Here are anti-patterns that Maria Chen flags in code reviews:
Anti-Pattern 1: The Monolithic Paragraph
* BAD: One paragraph does everything
100-DO-EVERYTHING.
OPEN INPUT TRANS-FILE
OPEN OUTPUT REPORT-FILE
READ TRANS-FILE AT END MOVE "Y" TO EOF-FLAG
PERFORM UNTIL EOF-FLAG = "Y"
IF TRANS-TYPE = "A"
WRITE ACCOUNT-RECORD
END-IF
IF TRANS-TYPE = "D"
DELETE ACCOUNT-RECORD
END-IF
READ TRANS-FILE AT END MOVE "Y" TO EOF-FLAG
END-PERFORM
CLOSE TRANS-FILE REPORT-FILE
STOP RUN.
Why it is bad: No separation of concerns. Initialization, processing, and finalization are mixed together. Individual operations cannot be tested or reused.
Anti-Pattern 2: Meaningless Names
* BAD: What does this mean?
01 WS-A PIC X.
01 WS-B PIC 9(5).
01 WS-X PIC S9(7)V99.
PARA-1.
MOVE "Y" TO WS-A.
PARA-2.
ADD WS-X TO WS-B.
Why it is bad: No one — including you, three months from now — will know what WS-A, WS-B, and WS-X represent. COBOL's verbosity is a feature when it is used for clarity.
Anti-Pattern 3: Missing FILE STATUS
* BAD: No file status checking
SELECT ACCOUNT-FILE ASSIGN TO ACCTFILE
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS ACCT-KEY.
* Later:
READ ACCOUNT-FILE
INVALID KEY DISPLAY "NOT FOUND"
END-READ
* What if the read fails for a reason OTHER than invalid key?
* Without FILE STATUS, you will never know.
Anti-Pattern 4: Periods Inside Conditional Logic
* BAD: Period inside IF causes logic error
IF WS-BALANCE < 0
PERFORM 500-OVERDRAFT.
ADD 1 TO WS-OVERDRAFT-COUNT.
* The ADD always executes, not just for overdrafts!
3.9 The GlobalBank COBOL Coding Standards (Excerpt)
To give you a sense of how production shops formalize these principles, here is a summary of GlobalBank's COBOL coding standards — the rules that Maria Chen enforces in code reviews:
- All programs must use COBOL-85 scope terminators. No reliance on periods for scope control within the PROCEDURE DIVISION.
- One period per paragraph (at the end of the paragraph).
- Paragraph names use three-digit hierarchical numbering: 000-MAIN, 100-xxx, 200-xxx, etc.
- All data names use a two-character prefix indicating their section: WS- for WORKING-STORAGE, LS- for LINKAGE, FS- for FILE SECTION.
- Level 88 condition names are required for all status flags and type codes.
- FILE STATUS is mandatory on every SELECT statement.
- Copybooks are used for all shared record layouts. Programs must not duplicate record definitions.
- Comment header required with program purpose, inputs, outputs, and change log.
- INITIALIZE preferred over MOVE SPACES/ZEROS for clearing group items.
- No GO TO except for controlled exit patterns (e.g., GO TO paragraph-EXIT).
💡 Key Insight: These standards exist not because COBOL requires them, but because decades of experience have shown that programs following these patterns are dramatically easier to maintain. The standards represent accumulated wisdom about what works and what causes problems over the lifetime of a program.
3.10 Looking Ahead
You now have a thorough understanding of COBOL program structure — from the physical layout of a source line to the organization of a production program. This knowledge is foundational for everything that follows.
In Part II (Chapters 4–8), we dive into data mastery: PICTURE clause details you have not seen, numeric data types and their performance implications, table handling with SEARCH and SEARCH ALL, string manipulation with STRING, UNSTRING, and INSPECT, and reference modification. These chapters build directly on the DATA DIVISION knowledge from this chapter.
The pattern of the program — four divisions, organized sections, meaningful names, defensive checks, copybook reuse — will appear in every example from here forward. You have learned the template. Now it is time to fill it with increasingly sophisticated content.
Chapter Summary
- COBOL source lines follow strict column conventions: columns 1–6 (sequence), column 7 (indicator), columns 8–11 (Area A), columns 12–72 (Area B), columns 73–80 (identification).
- Column 7 indicators include
*(comment),-(continuation),/(page eject), andD(debugging line). - The IDENTIFICATION DIVISION requires only PROGRAM-ID; other paragraphs (AUTHOR, DATE-WRITTEN, etc.) are obsolete but widely used by convention.
- The ENVIRONMENT DIVISION's SPECIAL-NAMES paragraph provides powerful features: DECIMAL-POINT IS COMMA, custom CURRENCY SIGN, user-defined CLASS, and SYMBOLIC CHARACTERS.
- The DATA DIVISION has six sections; FILE SECTION, WORKING-STORAGE, LOCAL-STORAGE, and LINKAGE SECTION are the most commonly used.
- Level numbers (01–49, 66, 77, 88) define data hierarchies. Level 88 condition names are one of COBOL's most powerful readability features.
- Multiple 01-level records under one FD provide different views of the same physical record area.
- The PROCEDURE DIVISION is organized into sections (optional), paragraphs (named code blocks), sentences, and statements.
- The period problem — misplaced periods changing logic silently — is solved by COBOL-85 scope terminators (END-IF, END-PERFORM, etc.). Modern best practice: one period per paragraph, at the end.
- Copybooks (COPY statement) are essential for code reuse in production COBOL systems. COPY REPLACING allows text substitution during inclusion.
- Production-quality COBOL programs are characterized by: comprehensive headers, hierarchical paragraph naming, FILE STATUS checking, level 88 conditions, organized working storage, and consistent coding standards.