> "Programs must be written for people to read, and only incidentally for machines to execute."
In This Chapter
- 8.1 Structured Programming in COBOL — A Brief History
- 8.2 Paragraph Naming Conventions
- 8.3 Sections vs. Paragraphs — When to Use Each
- 8.4 The GO TO Debate: Evil or Pragmatic?
- 8.5 Top-Down Program Design with PERFORM
- 8.6 Program Flow Patterns
- 8.7 Cohesion and Coupling
- 8.8 Self-Documenting Code Through Naming
- 8.9 Working Through a Design: From Requirements to Paragraphs
- 8.10 The PERFORM THRU Debate — A Deep Dive
- 8.10 Error Handling Patterns
- 8.11 Documentation Within Program Structure
- 8.12 GlobalBank Case Study: Refactoring ACCT-MAINT
- 8.11 MedClaim Case Study: Designing CLM-ADJUD Program Flow
- 8.12 Measuring Paragraph Quality
- 8.13 Well-Structured vs. Poorly Structured: A Comparison
- 8.14 Refactoring Legacy Code: A Step-by-Step Method
- 8.15 Guidelines for Paragraph and Section Design
- 8.16 A Word on Modernization and Program Design
- Chapter Summary
- Key Terms
Chapter 8: Paragraph and Section Design
"Programs must be written for people to read, and only incidentally for machines to execute." — Harold Abelson, Structure and Interpretation of Computer Programs
A COBOL program is not a sequence of statements — it is a hierarchy of named units of work. Paragraphs and sections are the fundamental building blocks of that hierarchy. How you design them — what you name them, how you scope them, what responsibilities you assign to each — determines whether your program is a maintainable asset or a maintenance nightmare.
In the previous two chapters, you learned the tools: conditional logic (Chapter 6) and iteration patterns (Chapter 7). In this chapter, you learn the architecture — how to assemble those tools into programs that are coherent, readable, and professionally structured.
This chapter engages two of our core themes simultaneously. Readability is a Feature drives our discussion of naming conventions, self-documenting code, and paragraph cohesion. The Human Factor reminds us that programs are maintained by people — people who may be reading your code at 2 AM during a production incident, people who joined the team six months after you wrote the code, people who will make decisions about your career based on the quality of the code you leave behind.
8.1 Structured Programming in COBOL — A Brief History
Before COBOL-85 introduced END-IF, END-PERFORM, and inline PERFORM, COBOL programmers had limited structural tools. Programs were organized into paragraphs separated by periods, control flow was managed through GO TO, and the result was often what Edsger Dijkstra memorably called "spaghetti code" — programs where the flow of control twisted and tangled like strands of pasta.
The structured programming movement, pioneered by Dijkstra, Böhm, Jacopini, and Harlan Mills in the late 1960s and 1970s, proved that any algorithm could be expressed using only three control structures: sequence (do A then B), selection (if condition then A else B), and iteration (while condition do A). No GO TO required.
This was revolutionary. IBM's Harlan Mills applied these ideas to large-scale COBOL development at the corporation's Federal Systems Division, producing dramatically better results: fewer bugs, faster development, easier maintenance. The structured programming approach became the foundation of COBOL best practices, and COBOL-85's language additions (END-IF, END-PERFORM, END-EVALUATE, inline PERFORM) made it practical to write structured COBOL without relying on coding conventions alone.
Today, structured programming in COBOL means:
- Top-down design — Start with a high-level main paragraph and decompose into progressively more detailed subordinate paragraphs.
- One entry, one exit — Each paragraph has one way in (the PERFORM call) and one way out (the end of the paragraph or the exit paragraph in a THRU range).
- Limited scope — Each paragraph does one thing and does it well.
- Meaningful names — Paragraph names describe what they do.
- Controlled flow — PERFORM replaces GO TO as the primary flow control mechanism.
📊 Historical Note: The transition from unstructured to structured COBOL was not instantaneous. Many organizations have programs written in the 1970s and early 1980s that predate structured programming practices. You will encounter GO TO-heavy, paragraph-less code in production. Understanding the historical context helps you maintain it — and, when given the opportunity, refactor it.
8.2 Paragraph Naming Conventions
A paragraph name is the first thing a maintenance programmer sees. A good name tells them what the paragraph does without reading its contents. A bad name tells them nothing — or worse, tells them something wrong.
The Verb-Noun Convention
The most widely adopted convention names paragraphs with a verb-noun pattern:
2100-VALIDATE-ACCOUNT
2200-CALCULATE-INTEREST
2300-WRITE-TRANSACTION-RECORD
3100-READ-NEXT-CLAIM
9100-LOG-ERROR
The verb tells you the action; the noun tells you the object of that action. A maintenance programmer seeing PERFORM 2300-WRITE-TRANSACTION-RECORD knows exactly what that call does.
💡 Maria Chen's Rule: "If you cannot express what a paragraph does in a verb-noun name, the paragraph is doing too much. Split it."
Hierarchical Numbering
Most COBOL shops use a numbering scheme where the numeric prefix indicates the paragraph's level in the program hierarchy:
1000-1999 Main-level paragraphs (program flow control)
2000-2999 Primary processing paragraphs
3000-3999 Detail processing paragraphs
4000-4999 Sub-detail processing
8000-8999 Utility/helper paragraphs
9000-9999 Error handling and termination
Sub-numbering within a range indicates logical grouping:
2000-PROCESS-TRANSACTION *> Controller
2100-VALIDATE-TRANSACTION *> First sub-step
2110-CHECK-ACCOUNT-STATUS *> Detail of validation
2120-CHECK-BALANCE *> Detail of validation
2130-CHECK-DAILY-LIMITS *> Detail of validation
2200-APPLY-TRANSACTION *> Second sub-step
2300-UPDATE-ACCOUNT-MASTER *> Third sub-step
2400-WRITE-AUDIT-TRAIL *> Fourth sub-step
This numbering tells you at a glance that 2100, 2200, 2300, and 2400 are siblings (sub-steps of 2000), and that 2110, 2120, and 2130 are children of 2100. You can understand the program's structure by reading the paragraph names alone.
Exit Paragraphs
When using PERFORM THRU, exit paragraphs follow a consistent naming pattern:
2100-VALIDATE-ACCOUNT.
... validation logic ...
.
2199-VALIDATE-ACCOUNT-EXIT.
EXIT.
The convention of using xx99 for the exit paragraph (where xx matches the parent paragraph's first two digits) makes it easy to identify THRU ranges visually.
Anti-Patterns in Naming
Avoid these common naming mistakes:
| Bad Name | Why It's Bad | Better Name |
|---|---|---|
2100-PROCESS |
Too vague — process what? | 2100-PROCESS-WITHDRAWAL |
2100-DO-STUFF |
Meaningless | 2100-VALIDATE-AND-ROUTE |
2100-PARA-1 |
Sequential numbering without description | 2100-READ-NEXT-RECORD |
2100-MISC |
Unclear scope — what belongs here? | Split into specific paragraphs |
2100-HANDLE-ALL-ERRORS |
Too broad — violates single responsibility | 2100-LOG-VALIDATION-ERROR |
2100-CHECK-STUFF-AND-DO-THINGS |
Two responsibilities in one name | Split into two paragraphs |
⚠️ Warning: The worst naming anti-pattern is misleading names. A paragraph named 2100-VALIDATE-ACCOUNT that also updates the account balance and writes an audit record is lying to the reader. This is more dangerous than a vague name, because the reader thinks they understand what the paragraph does and makes decisions accordingly.
🧪 Try It Yourself: Paragraph Naming
You encounter a program with these paragraph names. For each, suggest a better name and explain why:
PARA-A.
PROCESS-IT.
2000-DO-PROCESSING.
CHECK-AND-UPDATE-AND-WRITE.
ROUTINE-1.
9999-END.
8.3 Sections vs. Paragraphs — When to Use Each
COBOL has two levels of program organization in the PROCEDURE DIVISION: sections and paragraphs. A section contains one or more paragraphs. A paragraph is a named sequence of statements terminated by the next paragraph or section header (or by the end of the program).
2000-PROCESSING SECTION.
2100-VALIDATE.
... validation logic ...
.
2200-APPLY.
... application logic ...
.
2300-UPDATE.
... update logic ...
.
When you PERFORM a section, all paragraphs within it are executed in sequence:
PERFORM 2000-PROCESSING
* Executes 2100, then 2200, then 2300
When to Use Sections
Sections serve three purposes in modern COBOL:
-
Grouping related paragraphs — Sections make the program's logical structure visible in the source code.
-
PERFORM range scoping — Instead of
PERFORM 2100-VALIDATE THRU 2300-UPDATE, you canPERFORM 2000-PROCESSING. The section boundaries define the PERFORM range. -
Declaratives — The DECLARATIVES portion of the PROCEDURE DIVISION requires sections (for USE AFTER ERROR procedures, etc.).
When to Use Paragraphs Alone
Many modern COBOL shops use paragraphs exclusively, without sections:
PROCEDURE DIVISION.
1000-MAIN.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 1300-PROCESS-ALL
PERFORM 1400-CLOSE-FILES
STOP RUN.
1100-INITIALIZE.
...
1200-OPEN-FILES.
...
This is simpler, avoids the complexities of section scoping, and is perfectly adequate for most programs. The hierarchical numbering scheme provides the grouping that sections would provide structurally.
The Section Debate
| Use Sections When... | Use Paragraphs Only When... |
|---|---|
| Shop standards require them | Shop standards permit paragraph-only design |
| Program needs DECLARATIVES | Program has no DECLARATIVES |
| Logical grouping aids readability in very large programs (>2000 lines) | Program is moderate size (<2000 lines) |
| You want to PERFORM a group of related paragraphs as a unit | Each paragraph is PERFORMed individually |
⚖️ Shop Standards: This is predominantly a shop-standard decision. Some large financial institutions mandate sections because their programs are enormous (10,000+ lines) and sections provide a necessary organizational layer. Other shops find sections add complexity without benefit. Follow your shop's standard; if starting a new project, paragraph-only design is the simpler choice.
8.4 The GO TO Debate: Evil or Pragmatic?
No topic in COBOL programming generates more heat and less light than GO TO. Dijkstra's 1968 letter "Go To Statement Considered Harmful" became one of the most cited papers in computer science, and the structured programming movement largely succeeded in making GO TO disreputable. Yet GO TO remains in the COBOL standard, and many production COBOL programs use it — some well, some badly.
Let us examine the arguments honestly.
The Case Against GO TO
* Spaghetti code — real production example (anonymized)
1000-START.
READ INPUT-FILE AT END GO TO 9000-DONE.
IF STATUS = 'A' GO TO 2000-PROCESS.
IF STATUS = 'B' GO TO 3000-SPECIAL.
GO TO 4000-ERROR.
2000-PROCESS.
ADD AMOUNT TO TOTAL.
IF AMOUNT > 1000 GO TO 2500-BIG.
GO TO 1000-START.
2500-BIG.
ADD 1 TO BIG-COUNT.
IF BIG-COUNT > 10 GO TO 3000-SPECIAL.
GO TO 1000-START.
3000-SPECIAL.
PERFORM 3100-SPECIAL-PROCESS.
IF ERROR-FLAG = 'Y' GO TO 4000-ERROR.
GO TO 1000-START.
4000-ERROR.
ADD 1 TO ERROR-COUNT.
IF ERROR-COUNT > 100 GO TO 9000-DONE.
GO TO 1000-START.
9000-DONE.
...
This code is a maze. Try to answer: What happens if STATUS is 'A', AMOUNT is 1500, BIG-COUNT is 11, and the special processing sets ERROR-FLAG to 'Y'? You have to trace through five GO TO jumps to find out. This is the spaghetti that Dijkstra warned about.
Arguments against GO TO: 1. Makes control flow non-linear and hard to follow 2. Creates implicit coupling between distant parts of the program 3. Makes it difficult to reason about program state at any point 4. Complicates testing — you cannot test a paragraph in isolation if GO TO can jump into or out of it 5. Every use of GO TO can be replaced with structured constructs (IF, PERFORM, EVALUATE)
The Case For GO TO (In Limited, Disciplined Use)
* Guard clause pattern — disciplined GO TO
2100-VALIDATE-AND-PROCESS.
IF NOT ACCT-IS-ACTIVE
MOVE 'ACCOUNT NOT ACTIVE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2199-VALIDATE-EXIT
END-IF
IF NOT TXN-IS-VALID
MOVE 'INVALID TRANSACTION' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2199-VALIDATE-EXIT
END-IF
IF WS-BALANCE < WS-TXN-AMOUNT
MOVE 'INSUFFICIENT FUNDS' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2199-VALIDATE-EXIT
END-IF
PERFORM 3100-PROCESS-TRANSACTION
.
2199-VALIDATE-EXIT.
EXIT.
Arguments for disciplined GO TO:
1. The guard clause pattern produces flatter, more readable code than nested IFs
2. GO TO the exit paragraph of a PERFORM THRU range is a controlled, predictable jump — not arbitrary spaghetti
3. It maps directly to "early return" in other languages (return in C/Java functions)
4. The alternative (condition flags with repeated checking) can be more verbose and harder to follow
5. In some legacy codebases, replacing GO TO with structured constructs would require a major rewrite
The Consensus Position
Most modern COBOL coding standards take a middle position:
-
GO TO is acceptable only to jump to an exit paragraph within a PERFORM THRU range. This is the guard clause / early exit pattern.
-
GO TO is never acceptable for jumping backward (creating loops), jumping forward past significant logic, or jumping between unrelated paragraphs.
-
If GO TO is prohibited entirely, the condition flag pattern is the accepted alternative, even though it is more verbose.
🔴 Production Reality: Derek Washington's observation after six months at GlobalBank: "I was taught in school that GO TO is always wrong. Then I looked at the codebase and found 14,000 GO TO statements across 800 programs. About 200 of them are spaghetti — random jumps in old programs from the 1980s. The other 13,800 are guard clause exits, and they make the code more readable, not less. Context matters."
🧪 Try It Yourself: GO TO Refactoring
Take the spaghetti code example from "The Case Against GO TO" and rewrite it using only structured constructs (PERFORM, EVALUATE, IF). Compare the two versions for: - Readability: Which is easier to understand? - Testability: Which is easier to unit test? - Line count: Which is more concise?
8.5 Top-Down Program Design with PERFORM
Top-down design is the fundamental methodology for structured COBOL programming. You start with the program's overall purpose, decompose it into major steps, decompose each step into sub-steps, and continue until each paragraph is small enough to be understood at a glance.
The Standard COBOL Program Structure
Nearly every batch COBOL program follows this high-level pattern:
1000-MAIN-PROCESS.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 1300-PROCESS-ALL-RECORDS
PERFORM 1400-WRITE-SUMMARY
PERFORM 1500-CLOSE-FILES
STOP RUN.
This is the Initialize-Process-Terminate (IPT) pattern. It appears in virtually every COBOL batch program because it maps directly to the batch processing lifecycle:
- Initialize — Set counters to zero, initialize flags, load reference tables
- Open files — Open all input and output files, verify FILE STATUS
- Process — Read and process all records (the main loop)
- Summarize — Write totals, statistics, control reports
- Terminate — Close files, set return code
Decomposition Example: GlobalBank TXN-PROC
Let us trace the decomposition of GlobalBank's transaction processing program:
Level 0 — Program purpose: Process all daily transactions and update account balances.
Level 1 — Major steps:
1000-MAIN-PROCESS.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 1300-PROCESS-ALL-TRANSACTIONS
PERFORM 1400-WRITE-BATCH-REPORT
PERFORM 1500-CLOSE-FILES
STOP RUN.
Level 2 — Process decomposition:
1300-PROCESS-ALL-TRANSACTIONS.
PERFORM 2100-READ-FIRST-TXN
PERFORM 2000-PROCESS-ONE-TXN
UNTIL END-OF-FILE
OR WS-MAX-RECORDS-REACHED
PERFORM 1310-VERIFY-BATCH-TOTALS
.
Level 3 — Single transaction processing:
2000-PROCESS-ONE-TXN.
PERFORM 2100-VALIDATE-TXN
IF TXN-IS-VALID
PERFORM 2200-ROUTE-TXN
PERFORM 2300-UPDATE-ACCOUNT
PERFORM 2400-WRITE-AUDIT
ELSE
PERFORM 2500-WRITE-REJECT
END-IF
PERFORM 2600-UPDATE-COUNTERS
PERFORM 2100-READ-NEXT-TXN
.
Level 4 — Validation detail:
2100-VALIDATE-TXN.
SET TXN-IS-VALID TO TRUE
PERFORM 2110-CHECK-ACCT-STATUS
IF TXN-IS-VALID
PERFORM 2120-CHECK-TXN-TYPE
END-IF
IF TXN-IS-VALID
PERFORM 2130-CHECK-AMOUNT
END-IF
IF TXN-IS-VALID
PERFORM 2140-CHECK-REGULATORY
END-IF
.
Notice how each level adds detail without adding complexity. A reader examining Level 1 understands the program's overall flow. A reader examining Level 3 understands how a single transaction is processed. A reader examining Level 4 understands the validation checks. No level forces the reader to hold more than 5-7 concepts in their mind simultaneously.
📊 The Rule of 7 (±2): Cognitive science research by George Miller established that humans can hold approximately 7 (plus or minus 2) items in working memory simultaneously. Well-designed COBOL paragraphs respect this limit — no paragraph should PERFORM more than about 7 subordinate paragraphs, and no paragraph body should be longer than about 50 lines (one or two screens).
8.6 Program Flow Patterns
Pattern 1: Initialize-Process-Terminate (IPT)
We have already seen this pattern. It is the foundation of every batch program:
1000-MAIN.
PERFORM 1100-INITIALIZE
PERFORM 1200-PROCESS
PERFORM 1300-TERMINATE
STOP RUN.
Pattern 2: Read-Process Loop
The standard sequential file processing pattern:
1200-PROCESS-FILE.
PERFORM 2100-READ-RECORD
PERFORM 2000-PROCESS-RECORD
UNTIL END-OF-FILE
.
2000-PROCESS-RECORD.
PERFORM 2200-APPLY-BUSINESS-LOGIC
PERFORM 2100-READ-RECORD
.
Pattern 3: Control Break
When processing sorted data, the control break pattern detects when a grouping key changes:
2000-PROCESS-RECORD.
IF WS-CURRENT-DEPT NOT = WS-PREV-DEPT
IF WS-PREV-DEPT NOT = SPACES
PERFORM 3100-DEPT-BREAK
END-IF
MOVE WS-CURRENT-DEPT TO WS-PREV-DEPT
PERFORM 3200-DEPT-HEADER
END-IF
PERFORM 2200-PROCESS-DETAIL
PERFORM 2100-READ-RECORD
.
We will cover control break processing in depth in Chapter 14.
Pattern 4: Validate-Route-Process
For programs that process different record types:
2000-PROCESS-RECORD.
PERFORM 2100-VALIDATE
IF RECORD-IS-VALID
EVALUATE TRUE
WHEN REC-TYPE-NEW
PERFORM 3100-ADD-RECORD
WHEN REC-TYPE-UPDATE
PERFORM 3200-UPDATE-RECORD
WHEN REC-TYPE-DELETE
PERFORM 3300-DELETE-RECORD
END-EVALUATE
ELSE
PERFORM 9100-WRITE-ERROR
END-IF
.
Pattern 5: Error Cascade
For comprehensive error handling:
9000-ERROR-HANDLING SECTION.
9100-LOG-ERROR.
ADD 1 TO WS-ERROR-COUNT
WRITE ERROR-RECORD FROM WS-ERROR-LINE
IF WS-ERROR-COUNT >= WS-MAX-ERRORS
PERFORM 9200-TOO-MANY-ERRORS
END-IF
.
9200-TOO-MANY-ERRORS.
DISPLAY 'FATAL: Error limit exceeded'
MOVE 16 TO RETURN-CODE
PERFORM 1500-CLOSE-FILES
STOP RUN.
8.7 Cohesion and Coupling
Two principles from software engineering — cohesion and coupling — are as relevant to COBOL paragraphs as they are to methods and classes in object-oriented languages.
Cohesion: Do One Thing Well
A paragraph has high cohesion when all its statements contribute to a single, well-defined purpose. A paragraph has low cohesion when it does multiple unrelated things.
* HIGH COHESION: Does one thing (validates account status)
2110-VALIDATE-ACCT-STATUS.
IF NOT ACCT-IS-ACTIVE AND NOT ACCT-IS-PENDING
SET TXN-IS-INVALID TO TRUE
MOVE 'ACCOUNT NOT PROCESSABLE'
TO WS-ERROR-MSG
PERFORM 9100-LOG-VALIDATION-ERROR
END-IF
.
* LOW COHESION: Does three unrelated things
2100-DO-STUFF.
IF NOT ACCT-IS-ACTIVE
SET TXN-IS-INVALID TO TRUE
END-IF
COMPUTE WS-INTEREST =
WS-BALANCE * WS-RATE / 365
WRITE RPT-LINE FROM WS-DETAIL-LINE
.
The low-cohesion paragraph validates, calculates, and writes — three unrelated operations crammed together. Each should be its own paragraph.
The Litmus Test for Cohesion
Ask yourself: "Can I describe what this paragraph does in a single sentence without using 'and'?" If you need "and," the paragraph probably has low cohesion and should be split.
- ✅ "Validates the account status for transaction processing."
- ❌ "Validates the account and calculates interest and writes the report line."
Coupling: Minimize Dependencies
Two paragraphs are loosely coupled when each can be understood (and, ideally, tested) independently. They are tightly coupled when one cannot function without intimate knowledge of the other.
* TIGHT COUPLING: 2200 depends on 2100's internal state
2100-VALIDATE.
MOVE 'A' TO WS-INTERNAL-FLAG
IF condition-1
MOVE 'B' TO WS-INTERNAL-FLAG
END-IF
.
2200-PROCESS.
* Must know that 'A' means valid, 'B' means invalid
IF WS-INTERNAL-FLAG = 'A'
PERFORM 3100-DO-WORK
END-IF
.
* LOOSE COUPLING: Contract is clear via 88-level
2100-VALIDATE.
SET VALIDATION-PASSED TO TRUE
IF condition-1
SET VALIDATION-FAILED TO TRUE
MOVE 'Reason text' TO WS-ERROR-MSG
END-IF
.
2200-PROCESS.
IF VALIDATION-PASSED
PERFORM 3100-DO-WORK
END-IF
.
In the tightly coupled version, 2200 needs to know that 'A' means valid — an implementation detail of 2100. In the loosely coupled version, 2200 only needs to know about the 88-level condition name VALIDATION-PASSED, which is a contract, not an implementation detail.
💡 Design Insight: 88-level condition names are one of COBOL's most powerful tools for reducing coupling. They create a semantic contract between producer and consumer paragraphs: "I set VALIDATION-PASSED when valid; you check VALIDATION-PASSED before proceeding." The underlying value ('Y', 'P', '1', whatever) is an implementation detail that neither paragraph's logic depends on.
8.8 Self-Documenting Code Through Naming
The best code documentation is the code itself. When paragraph names, data names, and condition names are chosen well, the code reads like a narrative:
2000-PROCESS-CLAIM.
PERFORM 2100-CHECK-MEMBER-ELIGIBILITY
IF MEMBER-IS-ELIGIBLE
PERFORM 2200-VERIFY-PROVIDER-STATUS
IF PROVIDER-IS-VALID
PERFORM 2300-CHECK-AUTHORIZATION
IF AUTHORIZATION-ON-FILE OR
AUTHORIZATION-NOT-REQUIRED
PERFORM 2400-ADJUDICATE-CLAIM-LINES
PERFORM 2500-CALCULATE-PAYMENT
SET CLAIM-APPROVED TO TRUE
ELSE
SET CLAIM-PENDED TO TRUE
SET PEND-NEED-AUTHORIZATION TO TRUE
END-IF
ELSE
SET CLAIM-DENIED TO TRUE
SET DENY-INVALID-PROVIDER TO TRUE
END-IF
ELSE
SET CLAIM-DENIED TO TRUE
SET DENY-MEMBER-INELIGIBLE TO TRUE
END-IF
.
Read that paragraph out loud. It is almost English:
"Process the claim. Check member eligibility. If the member is eligible, verify provider status. If the provider is valid, check authorization. If authorization is on file or not required, adjudicate the claim lines and calculate payment — the claim is approved. Otherwise, pend the claim because authorization is needed. If the provider is not valid, deny the claim. If the member is not eligible, deny the claim."
Sarah Kim, MedClaim's business analyst, can read this code and verify that it matches the business rules. That is the gold standard of self-documenting code.
Naming Principles for Self-Documenting Code
-
Paragraph names: verb-noun, describing what the paragraph does -
2100-CHECK-MEMBER-ELIGIBILITY(not2100-ELIG-CHK) -
Data names: noun, describing what the data represents -
WS-CLAIM-TOTAL-AMOUNT(notWS-AMT1) -WS-MEMBER-BIRTH-DATE(notWS-DT) -
Condition names (88-levels): adjective or state, describing when the condition is true -
MEMBER-IS-ELIGIBLE(notFLAG-1-ON) -CLAIM-APPROVED(notSTATUS-OK) -
Prefixes indicate scope: -
WS-for WORKING-STORAGE items -LS-for LINKAGE SECTION items -FD-or file-specific prefixes for FILE SECTION items
⚠️ The Abbreviation Trap: COBOL's 30-character name limit (raised to 60 in some implementations) can tempt you to abbreviate heavily. Resist this temptation. WS-CUST-ACCT-BAL is less readable than WS-CUSTOMER-ACCOUNT-BALANCE but more readable than WS-C-A-B. Use consistent, recognizable abbreviations and document them in a project glossary if necessary.
8.9 Working Through a Design: From Requirements to Paragraphs
To make program design concrete, let us work through the entire process of turning a set of requirements into a paragraph structure. This is the skill that separates a programmer who can write COBOL from a programmer who can design COBOL programs.
The Requirements
A retail chain needs a program to process daily sales returns. For each return: 1. Validate the original receipt (must exist, must be within 90 days) 2. Verify the item is returnable (some items like custom orders are non-returnable) 3. Calculate the refund amount (original price minus any restocking fee) 4. Determine the refund method (original payment method for credit cards; store credit for cash over $50; cash for cash under $50) 5. Update inventory (add the returned item back to stock) 6. Write a refund transaction record
Step 1: Identify the Major Phases
Every batch program has the IPT structure. Within the Process phase, identify the major steps:
INITIALIZE → OPEN FILES → PROCESS ALL RETURNS → WRITE SUMMARY → CLOSE FILES
Step 2: Decompose the Processing Step
The "Process one return" step breaks down into the six requirements above, plus reading the next record:
VALIDATE RECEIPT → CHECK RETURNABLE → CALCULATE REFUND →
DETERMINE REFUND METHOD → UPDATE INVENTORY → WRITE REFUND TXN → READ NEXT
Step 3: Identify Sub-Steps
Some steps are complex enough to warrant further decomposition: - "Validate receipt" needs: look up receipt, check date range - "Calculate refund" needs: retrieve original price, determine restocking fee, compute net refund - "Determine refund method" needs: check original payment type, check amount threshold
Step 4: Assign Paragraph Numbers and Names
1000-MAIN-PROCESS
1100-INITIALIZE
1200-OPEN-FILES
1300-PROCESS-ALL-RETURNS
1400-WRITE-SUMMARY
1500-CLOSE-FILES
2000-PROCESS-ONE-RETURN
2100-VALIDATE-RECEIPT
2110-LOOKUP-ORIGINAL-RECEIPT
2120-CHECK-RETURN-WINDOW
2200-CHECK-ITEM-RETURNABLE
2300-CALCULATE-REFUND
2310-RETRIEVE-ORIGINAL-PRICE
2320-DETERMINE-RESTOCKING-FEE
2330-COMPUTE-NET-REFUND
2400-DETERMINE-REFUND-METHOD
2500-UPDATE-INVENTORY
2600-WRITE-REFUND-TRANSACTION
2700-READ-NEXT-RETURN
9100-LOG-ERROR
9200-WRITE-REJECT-RECORD
Step 5: Define the Contracts (88-Levels)
Before writing any logic, define the condition names that paragraphs will use to communicate:
01 WS-RECEIPT-CHECK PIC X(01).
88 RECEIPT-VALID VALUE 'Y'.
88 RECEIPT-INVALID VALUE 'N'.
01 WS-RETURN-CHECK PIC X(01).
88 ITEM-RETURNABLE VALUE 'Y'.
88 ITEM-NOT-RETURNABLE VALUE 'N'.
01 WS-REFUND-METHOD PIC X(01).
88 REFUND-TO-CARD VALUE 'C'.
88 REFUND-STORE-CREDIT VALUE 'S'.
88 REFUND-CASH VALUE 'X'.
Step 6: Write the Flow Logic
2000-PROCESS-ONE-RETURN.
PERFORM 2100-VALIDATE-RECEIPT
IF RECEIPT-VALID
PERFORM 2200-CHECK-ITEM-RETURNABLE
END-IF
IF RECEIPT-VALID AND ITEM-RETURNABLE
PERFORM 2300-CALCULATE-REFUND
PERFORM 2400-DETERMINE-REFUND-METHOD
PERFORM 2500-UPDATE-INVENTORY
PERFORM 2600-WRITE-REFUND-TRANSACTION
ADD 1 TO WS-RETURNS-PROCESSED
ELSE
PERFORM 9200-WRITE-REJECT-RECORD
ADD 1 TO WS-RETURNS-REJECTED
END-IF
PERFORM 2700-READ-NEXT-RETURN
.
This paragraph is 13 lines. It has a single responsibility (orchestrating the processing of one return). It uses 88-level condition names for inter-paragraph communication. Its nesting depth is 2. It PERFORMs 7 subordinate paragraphs. Every metric is within the guidelines.
🧪 Try It Yourself: Design Your Own
Choose one of these scenarios and design the paragraph structure (numbers, names, and flow logic only — no business logic implementation):
- Library book reservation system: Process reservation requests, check availability, place holds, send notifications
- Student grade report generator: Read grades, calculate GPAs, determine honors/probation status, generate transcripts
- Hotel room assignment: Process reservations, match room preferences, handle overbooking, generate confirmations
8.10 The PERFORM THRU Debate — A Deep Dive
We touched on PERFORM THRU in Chapter 7. Here we examine it thoroughly as a design choice.
The Standard PERFORM THRU Pattern
PERFORM 2100-VALIDATE-ACCOUNT
THRU 2199-VALIDATE-ACCOUNT-EXIT
...
2100-VALIDATE-ACCOUNT.
IF NOT ACCT-IS-ACTIVE
MOVE 'INACTIVE' TO WS-ERROR-MSG
GO TO 2199-VALIDATE-ACCOUNT-EXIT
END-IF
IF ACCT-ON-HOLD
MOVE 'ON HOLD' TO WS-ERROR-MSG
GO TO 2199-VALIDATE-ACCOUNT-EXIT
END-IF
IF NOT BALANCE-IS-SUFFICIENT
MOVE 'INSUF FUNDS' TO WS-ERROR-MSG
GO TO 2199-VALIDATE-ACCOUNT-EXIT
END-IF
SET ACCOUNT-VALIDATED TO TRUE
.
2199-VALIDATE-ACCOUNT-EXIT.
EXIT.
Arguments For PERFORM THRU
-
Enables the guard clause pattern — Each check fails fast with GO TO to the exit. The "happy path" is the last statement. This produces flat, readable code.
-
Single exit point — All paths through the paragraph converge at the exit paragraph. This satisfies the "one entry, one exit" principle of structured programming.
-
IBM heritage — IBM's coding standards for decades recommended PERFORM THRU with EXIT paragraphs. Many large enterprise codebases use this pattern exclusively.
-
Practical readability — For validation paragraphs with many sequential checks, the guard clause pattern is demonstrably more readable than deeply nested IFs or repeated condition flag checks.
Arguments Against PERFORM THRU
-
Fragile to maintenance — If a programmer adds a paragraph between 2100 and 2199, it becomes part of the THRU range. This is a real risk in large teams.
-
Hidden GO TO — Some argue that GO TO within a THRU range is still GO TO, and the arguments against GO TO still apply.
-
Not needed with modern constructs — Inline PERFORM with EXIT PERFORM (COBOL 2002+) achieves the same result without GO TO:
PERFORM
IF NOT ACCT-IS-ACTIVE
MOVE 'INACTIVE' TO WS-ERROR-MSG
EXIT PERFORM
END-IF
IF ACCT-ON-HOLD
MOVE 'ON HOLD' TO WS-ERROR-MSG
EXIT PERFORM
END-IF
IF NOT BALANCE-IS-SUFFICIENT
MOVE 'INSUF FUNDS' TO WS-ERROR-MSG
EXIT PERFORM
END-IF
SET ACCOUNT-VALIDATED TO TRUE
END-PERFORM
- Naming discipline required — The
xx99exit paragraph convention must be strictly followed to prevent accidental inclusion of unrelated paragraphs.
The Verdict
There is no universal answer. Both patterns work. The choice depends on:
| Factor | Use PERFORM THRU | Use Alternatives |
|---|---|---|
| Shop standard | If standard mandates it | If standard prohibits GO TO |
| Compiler version | Pre-COBOL 2002 | COBOL 2002+ (EXIT PERFORM available) |
| Team discipline | Strong naming conventions enforced | Naming discipline inconsistent |
| Program size | Large programs (>1000 lines) | Smaller programs |
8.10 Error Handling Patterns
How you organize error handling in your paragraph structure has a significant impact on program maintainability. Here are the most common patterns.
Centralized Error Handler
A single error logging paragraph used by all other paragraphs:
9100-LOG-ERROR.
ADD 1 TO WS-ERROR-COUNT
MOVE WS-CURRENT-RECORD-KEY TO WS-ERR-KEY
MOVE WS-ERROR-MSG TO WS-ERR-MESSAGE
MOVE FUNCTION CURRENT-DATE TO WS-ERR-TIMESTAMP
WRITE ERROR-RECORD FROM WS-ERROR-LINE
IF WS-ERROR-COUNT >= WS-MAX-ERRORS
PERFORM 9200-ABORT-PROCESSING
END-IF
.
9200-ABORT-PROCESSING.
DISPLAY 'FATAL: Error limit exceeded ('
WS-MAX-ERRORS ')'
DISPLAY 'Last error: ' WS-ERROR-MSG
DISPLAY 'Last key: ' WS-CURRENT-RECORD-KEY
MOVE 16 TO RETURN-CODE
PERFORM 1500-CLOSE-FILES
STOP RUN.
The advantage of centralization: every error goes through the same path, ensuring consistent formatting, counting, and threshold checking. No error can slip through unlogged.
Error Severity Levels
For more sophisticated error handling, define severity levels:
01 WS-ERROR-SEVERITY PIC X(01).
88 SEVERITY-INFO VALUE 'I'.
88 SEVERITY-WARNING VALUE 'W'.
88 SEVERITY-ERROR VALUE 'E'.
88 SEVERITY-FATAL VALUE 'F'.
88 SEVERITY-IS-CRITICAL VALUE 'E' 'F'.
9100-LOG-ERROR.
ADD 1 TO WS-ERROR-COUNT
EVALUATE TRUE
WHEN SEVERITY-INFO
ADD 1 TO WS-INFO-COUNT
WHEN SEVERITY-WARNING
ADD 1 TO WS-WARNING-COUNT
WHEN SEVERITY-ERROR
ADD 1 TO WS-ERROR-ONLY-COUNT
IF WS-ERROR-ONLY-COUNT >= WS-MAX-ERRORS
SET SEVERITY-FATAL TO TRUE
END-IF
WHEN SEVERITY-FATAL
PERFORM 9200-ABORT-PROCESSING
END-EVALUATE
WRITE ERROR-RECORD FROM WS-ERROR-LINE
.
Error Recovery Pattern
Some errors should be logged but not halt processing — the current record is skipped, and the next record is processed:
2000-PROCESS-ONE-RECORD.
PERFORM 2100-VALIDATE-RECORD
IF RECORD-IS-VALID
PERFORM 2200-APPLY-BUSINESS-LOGIC
ELSE
PERFORM 2300-WRITE-REJECT-RECORD
END-IF
PERFORM 2400-READ-NEXT-RECORD
.
The key insight: the processing loop in 1300-PROCESS-ALL continues regardless of whether individual records are valid or invalid. One bad record does not stop the batch. This is the error isolation principle — a single bad input should not bring down processing for all inputs.
Sarah Kim's rule from the MedClaim case study in Chapter 7 applies here: "No single claim should be able to block processing of all other claims." The paragraph structure must support this by keeping error handling within the record-processing scope, not at the program level.
Cascading Errors and the RETURN-CODE
At the end of a batch job, the RETURN-CODE should summarize the overall outcome:
1400-SET-RETURN-CODE.
EVALUATE TRUE
WHEN WS-ERROR-COUNT = 0
AND WS-WARNING-COUNT = 0
MOVE 0 TO RETURN-CODE
WHEN WS-ERROR-COUNT = 0
AND WS-WARNING-COUNT > 0
MOVE 4 TO RETURN-CODE
WHEN WS-ERROR-COUNT > 0
AND WS-ERROR-COUNT < WS-REJECT-THRESHOLD
MOVE 8 TO RETURN-CODE
WHEN WS-ERROR-COUNT >= WS-REJECT-THRESHOLD
MOVE 12 TO RETURN-CODE
END-EVALUATE
DISPLAY 'JOB RETURN CODE: ' RETURN-CODE
.
The return code values follow IBM conventions: - 0: Successful completion, no issues - 4: Completed with warnings - 8: Completed with errors (some records rejected) - 12: Completed with severe errors (high reject rate) - 16: Fatal error, processing aborted
💡 Design Insight: Notice how the return code setting paragraph uses EVALUATE TRUE with the threshold-based conditions. This is a high-cohesion paragraph — its single responsibility is to determine and set the return code. It does not log errors, close files, or perform any other action. Those are separate paragraphs' responsibilities.
8.11 Documentation Within Program Structure
While self-documenting code reduces the need for comments, well-placed comments still play important roles.
Program-Level Documentation
Every program should have a header comment block:
******************************************************************
* Program: TXN-PROC
* Purpose: Process daily transactions against account master
* Author: Maria Chen
* Date: 2024-03-15
* Inputs: TXN-DAILY (sequential, daily transactions)
* ACCT-MASTER (VSAM KSDS, account records)
* Outputs: ACCT-MASTER (updated)
* TXN-AUDIT (sequential, audit trail)
* TXN-REJECT (sequential, rejected transactions)
* Return Codes:
* 0 - Normal completion
* 4 - Warnings (some records had minor issues)
* 8 - Errors (records rejected, processing continued)
* 16 - Fatal error (processing aborted)
* Modification History:
* 2024-03-15 MC Initial version
* 2024-06-01 DW Added restricted account handling
* 2024-09-10 MC Performance optimization (binary search)
******************************************************************
Section-Level Comments
Use separator comments between major sections of the PROCEDURE DIVISION:
*===============================================================
* 1000-LEVEL: Program control (IPT pattern)
*===============================================================
*===============================================================
* 2000-LEVEL: Record processing
*===============================================================
*===============================================================
* 3000-LEVEL: Detail processing
*===============================================================
*===============================================================
* 9000-LEVEL: Error handling and utilities
*===============================================================
Business Rule Comments
The most valuable comments explain why, not what:
* Per Federal Reserve Regulation CC (12 CFR 229), funds from
* check deposits must be available by the second business day.
* Exception: new accounts (open < 30 days) have extended hold.
2300-APPLY-HOLD-PERIOD.
IF ACCOUNT-IS-NEW
MOVE 5 TO WS-HOLD-DAYS
ELSE
MOVE 2 TO WS-HOLD-DAYS
END-IF
.
Without the comment, a maintenance programmer might wonder why the hold period differs for new accounts and might inadvertently change it. With the comment, they know it is a regulatory requirement and can look up the regulation for details.
Avoiding Comment Rot
Comments that contradict the code are worse than no comments at all. When you modify code, always update the associated comments. Better yet, use 88-level condition names that make the code self-explanatory and minimize the need for comments:
* BAD: Comment says one thing, code does another
* Check if account is active
IF WS-ACCT-STATUS = 'A' OR WS-ACCT-STATUS = 'P'
* (comment says "active" but code also checks "pending")
* GOOD: Self-documenting, no comment needed
IF ACCT-IS-PROCESSABLE
8.12 GlobalBank Case Study: Refactoring ACCT-MAINT
GlobalBank's account maintenance program, ACCT-MAINT, was originally written in 1989. Over 35 years of modifications, it had grown to 4,200 lines with 127 paragraphs — many poorly named, poorly scoped, and tightly coupled. Maria Chen and Derek Washington were assigned to refactor it.
The Original Structure
A representative fragment of the original program:
PROCESS-IT.
READ ACCT-FILE.
IF STATUS-OK PERFORM NEXT-STEP.
IF NOT STATUS-OK GO TO ERR-RTN.
NEXT-STEP.
IF A-CODE = 'A' PERFORM ADD-IT.
IF A-CODE = 'C' PERFORM CHG-IT.
IF A-CODE = 'D' PERFORM DEL-IT.
IF A-CODE NOT = 'A' AND NOT = 'C' AND NOT = 'D'
GO TO ERR-RTN.
ADD-IT.
... 85 lines of add logic ...
CHG-IT.
... 120 lines of change logic with 7 GO TOs ...
DEL-IT.
... 45 lines ...
ERR-RTN.
ADD 1 TO ERR-CT.
GO TO PROCESS-IT.
Problems:
- No hierarchical numbering
- Meaningless names (PROCESS-IT, NEXT-STEP, ADD-IT)
- ERR-RTN uses GO TO to jump backward (creates a loop without PERFORM)
- CHG-IT is 120 lines with 7 GO TO statements
- No 88-level condition names
- No END-IF (pre-85 style with periods)
The Refactored Structure
1000-MAIN-PROCESS.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 1300-PROCESS-ALL-ACCOUNTS
PERFORM 1400-WRITE-SUMMARY
PERFORM 1500-CLOSE-FILES
STOP RUN.
1300-PROCESS-ALL-ACCOUNTS.
PERFORM 2100-READ-ACCOUNT-RECORD
PERFORM 2000-PROCESS-ONE-ACCOUNT
UNTIL END-OF-FILE
OR WS-RECORDS-READ > WS-MAX-RECORDS
.
2000-PROCESS-ONE-ACCOUNT.
PERFORM 2100-VALIDATE-ACTION-CODE
IF ACTION-CODE-VALID
EVALUATE TRUE
WHEN ACTION-IS-ADD
PERFORM 3100-ADD-ACCOUNT
WHEN ACTION-IS-CHANGE
PERFORM 3200-CHANGE-ACCOUNT
WHEN ACTION-IS-DELETE
PERFORM 3300-DELETE-ACCOUNT
END-EVALUATE
ELSE
PERFORM 9100-LOG-INVALID-ACTION
END-IF
PERFORM 2600-UPDATE-COUNTERS
PERFORM 2100-READ-ACCOUNT-RECORD
.
3200-CHANGE-ACCOUNT.
PERFORM 3210-VALIDATE-CHANGE-DATA
IF CHANGE-DATA-VALID
PERFORM 3220-APPLY-NAME-CHANGE
PERFORM 3230-APPLY-ADDRESS-CHANGE
PERFORM 3240-APPLY-STATUS-CHANGE
PERFORM 3250-WRITE-AUDIT-TRAIL
ADD 1 TO WS-CHANGE-COUNT
ELSE
PERFORM 9200-LOG-INVALID-CHANGE
END-IF
.
What Changed
| Aspect | Before | After |
|---|---|---|
| Paragraph names | PROCESS-IT, ADD-IT |
2000-PROCESS-ONE-ACCOUNT, 3100-ADD-ACCOUNT |
| Numbering | None | Hierarchical (1000s → 2000s → 3000s) |
| Largest paragraph | 120 lines (CHG-IT) |
15 lines (3200-CHANGE-ACCOUNT) |
| GO TO count | 14 | 0 (team chose no-GO-TO standard) |
| 88-level usage | 0 | 23 condition names |
| END-IF usage | 0 (period-delimited) | 100% |
| Cohesion | Low (paragraphs did multiple things) | High (each paragraph has single purpose) |
The refactoring took two weeks, including thorough regression testing. The total line count increased from 4,200 to 4,600 (more paragraphs, but each is smaller and clearer). The number of production incidents related to ACCT-MAINT dropped by 70% in the following year.
🔵 The Human Factor: Derek Washington reflected: "The old code wasn't wrong — it worked fine for 35 years. But it was written in a style where knowledge of the program lived in the programmer's head, not in the code. When that programmer retired in 2005, the knowledge retired with him. The refactored code puts the knowledge back in the code where anyone can find it."
8.11 MedClaim Case Study: Designing CLM-ADJUD Program Flow
When MedClaim decided to modernize their claims adjudication program, James Okafor took a top-down approach from the start. Rather than evolving the old program, he designed the new program's structure before writing a single line of business logic.
The Design Process
Step 1: James and Sarah Kim documented the adjudication workflow as a list of high-level steps:
- Read next claim
- Validate member eligibility
- Check for duplicate claims
- Verify provider status
- Check authorization requirements
- Price each line item
- Apply benefits (copay, coinsurance, deductible)
- Calculate payment amount
- Write adjudication decision
- Update statistics
Step 2: James created the paragraph skeleton:
1000-MAIN-PROCESS.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 1300-LOAD-REFERENCE-TABLES
PERFORM 1400-PROCESS-ALL-CLAIMS
PERFORM 1500-WRITE-BATCH-REPORT
PERFORM 1600-CLOSE-FILES
STOP RUN.
1400-PROCESS-ALL-CLAIMS.
PERFORM 2100-READ-NEXT-CLAIM
PERFORM 2000-ADJUDICATE-ONE-CLAIM
UNTIL END-OF-FILE
.
2000-ADJUDICATE-ONE-CLAIM.
PERFORM 2100-VALIDATE-MEMBER
PERFORM 2200-CHECK-DUPLICATE
PERFORM 2300-VERIFY-PROVIDER
PERFORM 2400-CHECK-AUTHORIZATION
PERFORM 2500-PRICE-LINE-ITEMS
PERFORM 2600-APPLY-BENEFITS
PERFORM 2700-CALCULATE-PAYMENT
PERFORM 2800-WRITE-DECISION
PERFORM 2900-UPDATE-STATS
PERFORM 2100-READ-NEXT-CLAIM
.
Step 3: Sarah reviewed the skeleton for business correctness. She identified that the steps should short-circuit: if the member is ineligible, do not check duplicates; if it is a duplicate, do not verify the provider; etc. James revised:
2000-ADJUDICATE-ONE-CLAIM.
SET CLAIM-PROCESSABLE TO TRUE
PERFORM 2100-VALIDATE-MEMBER
IF CLAIM-PROCESSABLE
PERFORM 2200-CHECK-DUPLICATE
END-IF
IF CLAIM-PROCESSABLE
PERFORM 2300-VERIFY-PROVIDER
END-IF
IF CLAIM-PROCESSABLE
PERFORM 2400-CHECK-AUTHORIZATION
END-IF
IF CLAIM-PROCESSABLE
PERFORM 2500-PRICE-LINE-ITEMS
END-IF
IF CLAIM-PROCESSABLE
PERFORM 2600-APPLY-BENEFITS
END-IF
IF CLAIM-PROCESSABLE
PERFORM 2700-CALCULATE-PAYMENT
END-IF
PERFORM 2800-WRITE-DECISION
PERFORM 2900-UPDATE-STATS
PERFORM 2100-READ-NEXT-CLAIM
.
Each validation paragraph sets CLAIM-PROCESSABLE to false if the claim should not continue. The decision is always written (approved, denied, or pended), and statistics are always updated, regardless of the outcome.
Step 4: James wrote stub paragraphs for each step:
2100-VALIDATE-MEMBER.
* TODO: Implement member eligibility check
DISPLAY 'STUB: Validate member'
.
2200-CHECK-DUPLICATE.
* TODO: Implement duplicate detection
DISPLAY 'STUB: Check duplicate'
.
This allowed him to compile, link, and test the program structure before any business logic was written. The structure was proven correct first; the logic was filled in later.
💡 Design Principle: This approach — designing the paragraph structure, reviewing it with stakeholders, testing it with stubs, and then filling in the logic — is top-down design in practice. It catches structural problems early, when they are cheap to fix, rather than late, when they require redesigning half the program.
8.12 Measuring Paragraph Quality
How do you know whether your paragraphs are well-designed? Here are quantitative guidelines:
Size Metrics
| Metric | Target | Warning | Red Flag |
|---|---|---|---|
| Lines per paragraph | 10-30 | 30-50 | >50 |
| Statements per paragraph | 5-15 | 15-25 | >25 |
| PERFORM calls per paragraph | 3-7 | 7-10 | >10 |
| Nesting depth | 1-2 | 3 | >3 |
Structural Metrics
| Metric | Good | Needs Review |
|---|---|---|
| Every paragraph is PERFORMed | Yes | Unreachable paragraphs exist |
| Every paragraph has a verb-noun name | Yes | Generic/numbered names |
| Every EVALUATE has WHEN OTHER | Yes | Missing WHEN OTHER |
| No paragraph exceeds 3 nesting levels | Yes | Deep nesting found |
| 88-levels used for all status/flag fields | Yes | Raw value comparisons |
McCabe Cyclomatic Complexity
Cyclomatic complexity measures the number of independent paths through a unit of code. For a COBOL paragraph, it is approximately: 1 + (number of IF/WHEN/AND/OR conditions). A paragraph with complexity > 10 is hard to test and maintain.
* Complexity = 1 + 4 = 5 (manageable)
2100-VALIDATE.
IF condition-1 *> +1
PERFORM ...
END-IF
IF condition-2 *> +1
PERFORM ...
END-IF
EVALUATE TRUE
WHEN cond-3 *> +1
PERFORM ...
WHEN cond-4 *> +1
PERFORM ...
WHEN OTHER
PERFORM ...
END-EVALUATE
.
8.13 Well-Structured vs. Poorly Structured: A Comparison
Let us put it all together with a side-by-side comparison. Both programs process employee records and calculate bonuses. The first is poorly structured; the second follows all the principles in this chapter.
Poorly Structured
PARA-1.
OPEN INPUT EMP-FILE OUTPUT RPT-FILE.
READ EMP-FILE AT END MOVE 'Y' TO EOF-SW.
LOOP.
IF EOF-SW = 'Y' GO TO FINISH.
IF E-STAT = 'A'
IF E-YRS > 5
IF E-RATING > 3
COMPUTE E-BONUS = E-SAL * .10
ELSE
COMPUTE E-BONUS = E-SAL * .05
END-IF
ELSE
COMPUTE E-BONUS = E-SAL * .03
END-IF
ADD E-BONUS TO TOT
ADD 1 TO CT
WRITE RPT-REC FROM DTL-LINE
END-IF.
READ EMP-FILE AT END MOVE 'Y' TO EOF-SW.
GO TO LOOP.
FINISH.
WRITE RPT-REC FROM TOT-LINE.
CLOSE EMP-FILE RPT-FILE.
STOP RUN.
Well Structured
01 WS-EMPLOYEE-STATUS PIC X(01).
88 EMPLOYEE-IS-ACTIVE VALUE 'A'.
01 WS-YEARS-OF-SERVICE PIC 9(02).
88 SENIOR-EMPLOYEE VALUE 6 THRU 99.
01 WS-PERFORMANCE-RATING PIC 9(01).
88 HIGH-PERFORMER VALUE 4 5.
88 STANDARD-PERFORMER VALUE 1 THRU 3.
01 WS-SALARY PIC S9(07)V99.
01 WS-BONUS PIC S9(07)V99.
01 WS-BONUS-TOTAL PIC S9(09)V99 VALUE 0.
01 WS-EMPLOYEE-COUNT PIC 9(05) VALUE 0.
01 WS-EOF-FLAG PIC X(01) VALUE 'N'.
88 END-OF-FILE VALUE 'Y'.
1000-MAIN-PROCESS.
PERFORM 1100-INITIALIZE
PERFORM 1200-OPEN-FILES
PERFORM 2100-READ-EMPLOYEE
PERFORM 2000-PROCESS-EMPLOYEE
UNTIL END-OF-FILE
PERFORM 1300-WRITE-TOTALS
PERFORM 1400-CLOSE-FILES
STOP RUN.
1100-INITIALIZE.
MOVE 0 TO WS-BONUS-TOTAL
WS-EMPLOYEE-COUNT
.
1200-OPEN-FILES.
OPEN INPUT EMPLOYEE-FILE
OUTPUT REPORT-FILE
.
2000-PROCESS-EMPLOYEE.
IF EMPLOYEE-IS-ACTIVE
PERFORM 2200-CALCULATE-BONUS
ADD WS-BONUS TO WS-BONUS-TOTAL
ADD 1 TO WS-EMPLOYEE-COUNT
PERFORM 2300-WRITE-DETAIL-LINE
END-IF
PERFORM 2100-READ-EMPLOYEE
.
2100-READ-EMPLOYEE.
READ EMPLOYEE-FILE INTO WS-EMPLOYEE-RECORD
AT END SET END-OF-FILE TO TRUE
END-READ
.
2200-CALCULATE-BONUS.
EVALUATE TRUE
WHEN SENIOR-EMPLOYEE AND HIGH-PERFORMER
COMPUTE WS-BONUS =
WS-SALARY * 0.10
WHEN SENIOR-EMPLOYEE AND STANDARD-PERFORMER
COMPUTE WS-BONUS =
WS-SALARY * 0.05
WHEN OTHER
COMPUTE WS-BONUS =
WS-SALARY * 0.03
END-EVALUATE
.
2300-WRITE-DETAIL-LINE.
MOVE WS-EMPLOYEE-NAME TO RPT-NAME
MOVE WS-BONUS TO RPT-BONUS
WRITE REPORT-RECORD FROM WS-DETAIL-LINE
.
1300-WRITE-TOTALS.
MOVE WS-EMPLOYEE-COUNT TO RPT-TOT-COUNT
MOVE WS-BONUS-TOTAL TO RPT-TOT-BONUS
WRITE REPORT-RECORD FROM WS-TOTAL-LINE
.
1400-CLOSE-FILES.
CLOSE EMPLOYEE-FILE REPORT-FILE
.
What to Look For
When comparing these two programs, pay attention to these specific differences:
Naming: The poorly structured program uses cryptic names like REC, ST, NM, SAL, BNS, TOT, CT, SK, and HI. A maintenance programmer encountering IF ST = 'A' has no idea what 'A' means without looking up the data definition. In the well-structured program, IF EMPLOYEE-IS-ACTIVE is instantly understandable.
Control flow: The poorly structured program uses GO TO READ-IT to create a loop. This is the pre-structured-programming idiom for iteration. The well-structured program uses PERFORM 2000-PROCESS-ONE-EMPLOYEE UNTIL END-OF-FILE — immediately conveying the loop's purpose and termination condition.
Nesting depth: The poorly structured program nests 5 levels deep in the bonus calculation. The well-structured program uses EVALUATE TRUE to flatten the decision to a single level.
Separation of concerns: The poorly structured program does everything — display header, read, validate, calculate, display detail, accumulate totals — in two paragraphs. The well-structured program separates each concern into its own paragraph: 2200-CHECK-ELIGIBILITY, 2300-CALCULATE-BONUS, 2400-DISPLAY-DETAIL, 2500-UPDATE-STATISTICS.
Testability: In the poorly structured program, you cannot test the bonus calculation in isolation — it is embedded in a loop with file I/O and display logic. In the well-structured program, 2300-CALCULATE-BONUS can be tested independently by setting the relevant data items and calling it directly.
Data definitions: The poorly structured program defines ST PIC X(01) with no 88-levels. The well-structured program defines WS-EMP-STATUS PIC X(01) with six 88-level names including group conditions like EMP-IS-ELIGIBLE. Adding a new status code to the well-structured program requires modifying one data definition; adding it to the poorly structured program requires finding and modifying every IF ST = ... comparison.
The well-structured version is longer (more paragraphs), but each paragraph is small, named, and single-purpose. A new developer can understand the program by reading 1000-MAIN-PROCESS and drilling into whichever step they need to understand. The bonus calculation logic is isolated in 2200-CALCULATE-BONUS, where it can be reviewed, tested, and modified independently.
8.14 Refactoring Legacy Code: A Step-by-Step Method
Many COBOL programmers spend more time modifying existing programs than writing new ones. When you inherit poorly structured code, you need a systematic approach to refactoring that improves the structure without breaking the behavior.
The Safe Refactoring Method
Rule Zero: Always have a baseline. Before changing anything, capture the program's output for a representative set of test data. This is your regression test suite. After every refactoring step, compare the output to the baseline. Any difference is either a bug in your refactoring or a preexisting bug you have exposed.
Step 1: Add END-IF and END-EVALUATE. If the program uses period-terminated conditionals, add explicit scope terminators. This is a mechanical transformation that does not change behavior but makes the structure visible:
* Before (period-terminated):
IF WS-STATUS = 'A'
PERFORM 3100-PROCESS.
* After (scope-terminated):
IF WS-STATUS = 'A'
PERFORM 3100-PROCESS
END-IF
Step 2: Define 88-level condition names. Identify every data item that is compared against literal values and define 88-levels for it. Replace all raw value comparisons with condition name references. This is behavior-preserving but dramatically improves readability.
Step 3: Replace backward GO TO with PERFORM UNTIL. Any GO TO that jumps backward is creating a loop. Replace it with a proper PERFORM UNTIL. This is the highest-risk refactoring step because it changes the control flow, so test thoroughly.
Step 4: Replace forward GO TO with structured alternatives. Where GO TO jumps forward, determine what it is trying to accomplish: - Skipping to the end of a validation sequence? Use guard clause + PERFORM THRU, or condition flags. - Routing to different processing? Use EVALUATE. - Jumping past error handling? Restructure with IF-ELSE.
Step 5: Extract large paragraphs. Any paragraph over 50 lines should be broken into smaller, single-purpose paragraphs with verb-noun names.
Step 6: Rename paragraphs. Replace non-descriptive names with verb-noun names following hierarchical numbering.
Tracking Your Refactoring
Keep a refactoring log that documents each change:
Date Change Test Result
2024-03-15 Added END-IF to all IF statements BASELINE MATCH
2024-03-15 Defined 88-levels for ACCT-STATUS BASELINE MATCH
2024-03-16 Replaced backward GO TO in LOOP BASELINE MATCH
2024-03-16 Extracted 2100-VALIDATE from DO-IT BASELINE MATCH
Each row represents a commit-worthy change. If a future change breaks the baseline, you can identify exactly which refactoring step introduced the problem.
When NOT to Refactor
Not every old program needs refactoring. Consider the cost-benefit:
-
Do refactor when the program is frequently modified, when modifications regularly introduce bugs, when new team members cannot understand it, or when a regulatory change requires touching large portions of the code.
-
Do not refactor when the program is stable and rarely modified, when the risk of introducing bugs outweighs the readability benefit, or when the program is scheduled for replacement.
Maria Chen's guideline: "Refactor code that people read. Leave alone code that nobody touches."
🧪 Try It Yourself: Refactoring Exercise
Take the POORLY-STRUCTURED.cbl code example from this chapter's code directory and apply the Safe Refactoring Method step by step. After each step, verify that the program produces the same output. Document each change in a refactoring log. Compare your result to WELL-STRUCTURED.cbl.
8.15 Guidelines for Paragraph and Section Design
Here is a comprehensive checklist for well-designed COBOL programs:
Naming
- [ ] All paragraphs use verb-noun naming
- [ ] Hierarchical numbering reflects program structure
- [ ] Exit paragraphs follow
xx99convention - [ ] Names are descriptive, not abbreviated to the point of obscurity
Structure
- [ ]
1000-MAINfollows Initialize-Process-Terminate pattern - [ ] Maximum nesting depth of 3 within any paragraph
- [ ] No paragraph exceeds 50 lines
- [ ] No paragraph PERFORMs more than 7 subordinate paragraphs
- [ ] Every paragraph is PERFORMed (no unreachable code)
Cohesion and Coupling
- [ ] Each paragraph has a single, well-defined purpose
- [ ] Inter-paragraph communication uses 88-level condition names
- [ ] No paragraph depends on another's implementation details
- [ ] Data items used as contracts between paragraphs are clearly named
Control Flow
- [ ] GO TO (if used) only jumps to exit paragraphs within PERFORM THRU
- [ ] No backward GO TO (no GO TO-based loops)
- [ ] PERFORM THRU ranges use strict numbering to prevent accidental inclusion
- [ ] Every EVALUATE has WHEN OTHER
Documentation
- [ ] Paragraph names serve as primary documentation
- [ ] Brief comments explain why, not what (the code shows what)
- [ ] Complex business rules reference requirements documents
8.16 A Word on Modernization and Program Design
The principles in this chapter are not just about writing new code well — they are about preparing COBOL programs for the future. Modernization efforts, whether they involve wrapping COBOL programs as web services, integrating with Java or Python through APIs, or migrating to cloud-based mainframe platforms, all benefit enormously from well-structured programs.
Why Structure Matters for Modernization
When a modernization team evaluates a COBOL program for refactoring, wrapping, or replacement, the first thing they assess is the program's structure:
-
Can individual paragraphs be isolated and tested? If yes, they can be wrapped as individual service operations. If paragraphs are tightly coupled with GO TO spaghetti, isolation is impractical.
-
Can the program's data flow be traced? If 88-level condition names and clear data contracts are used, automated analysis tools can map the data flow. If raw value comparisons and single-character flags are scattered throughout, the analysis is manual and expensive.
-
Can the business logic be separated from the infrastructure logic? If file I/O, validation, business rules, and error handling are in separate, well-named paragraphs, the business logic can be extracted and reused. If everything is mixed together in large monolithic paragraphs, extraction requires manual decomposition.
Priya Kapoor at GlobalBank has led three modernization projects. Her experience: "The programs that took weeks to modernize were the well-structured ones. The programs that took months — or that we ultimately decided not to modernize — were the ones with no structure. The time you invest in good design today saves exponentially more time during modernization."
The Modernization Spectrum Applied to Program Design
In Chapter 1, we introduced the concept of the Modernization Spectrum — the range of strategies from maintaining COBOL as-is to full rewrite in another language. Program design quality determines where on this spectrum a program can practically land:
| Program Quality | Modernization Options |
|---|---|
| Well-structured (88-levels, small paragraphs, IPT, clear naming) | All options available: wrap as service, partial rewrite, full rewrite, or maintain as-is |
| Moderately structured (some large paragraphs, some GO TO, mixed naming) | Refactor first, then all options available |
| Poorly structured (spaghetti GO TO, no 88-levels, no naming convention) | Requires extensive refactoring before any modernization; full rewrite may be cheaper |
| Extremely poor (backward GO TO, overlapping PERFORM THRU ranges, undocumented data formats) | Full rewrite is the only practical option; maintenance risk is high |
This is why Readability is a Feature and The Human Factor are not just stylistic preferences — they are strategic decisions that affect the long-term viability and modernization potential of every program you write or maintain.
🧪 Try It Yourself: Modernization Assessment
Take any COBOL program you have access to (from your textbook exercises, your Student Mainframe Lab, or a sample program from the GnuCOBOL distribution). Apply the quality checklist from Section 8.15 and rate the program. Based on your rating, determine which modernization options would be practical. Write a one-page assessment report identifying the program's structural strengths and weaknesses.
Chapter Summary
This chapter has taken you from the mechanics of writing COBOL statements to the art of designing COBOL programs. You have learned that paragraph naming is not a cosmetic concern — it is a design decision that determines whether your program communicates its intent or obscures it. You have explored the section vs. paragraph debate, navigated the GO TO controversy with nuance rather than dogma, and learned to apply cohesion and coupling principles to COBOL paragraph design.
The central lesson is that program structure is for humans, not machines. The compiler does not care whether your paragraphs are well-named or whether your nesting depth is 3 or 13. But the maintenance programmer at 2 AM cares deeply. The business analyst reviewing your code for regulatory compliance cares. Your future self, revisiting this code in six months, cares.
The Compound Interest of Good Design
Consider the economics. A well-structured program takes perhaps 20% longer to write initially — the time spent on naming, decomposition, 88-level definitions, and documentation. But that 20% investment pays dividends every time the program is maintained:
- A bug fix that takes 4 hours in a well-structured program might take 16 hours in a poorly structured one (because the developer must spend 12 hours understanding the code before they can fix the bug).
- A new feature that requires 2 days in a well-structured program might require 2 weeks in a poorly structured one (because the developer must untangle existing logic to add new logic).
- A code review that takes 30 minutes for a well-structured program might take 3 hours for a poorly structured one (because the reviewer cannot quickly verify correctness).
Over a 30-year program lifetime with perhaps 200 modifications, the compound savings of good structure versus poor structure can easily amount to thousands of person-hours. This is why the principles in this chapter are not academic — they are financial.
As Priya Kapoor tells the GlobalBank architecture review board: "We do not write programs that run once. We write programs that run for thirty years. The investment in good structure pays dividends every day of those thirty years."
As a final thought, consider what happens when the principles from this chapter are applied consistently across an entire application system. A single well-structured program is valuable. Fifty well-structured programs that share naming conventions, 88-level definitions, and paragraph design patterns form a coherent, learnable system. A new developer joining the team can learn the conventions from any program and apply them to every program. This consistency reduces the cognitive load of maintenance across the entire portfolio.
Conversely, fifty programs each written in a different style — some with sections, some without; some with GO TO, some without; some with 88-levels, some with raw comparisons — create a maintenance environment where every program requires relearning the local conventions. This inconsistency is the silent productivity killer in large COBOL shops.
The solution is not rigid enforcement of one "right" way, but rather team agreement on a shared standard and consistent application of that standard. The guidelines in this chapter provide a starting point. Your team's agreed-upon standard — documented, reviewed, and enforced through code reviews — is what transforms individual programming skill into organizational capability.
In the next chapter, we will move to Chapter 9: Copybooks and Code Reuse, where the organizational principles from this chapter extend beyond a single program to shared code across an entire application system.
Key Terms
| Term | Definition |
|---|---|
| Top-down design | A program design approach that starts with the overall purpose and decomposes into progressively more detailed subordinate paragraphs |
| IPT pattern | Initialize-Process-Terminate: the standard structure for batch COBOL programs |
| Cohesion | The degree to which a paragraph's statements contribute to a single, well-defined purpose |
| Coupling | The degree of interdependency between paragraphs; lower coupling is better |
| Guard clause | A conditional check at the start of a paragraph that exits early for invalid conditions |
| Verb-noun naming | The convention of naming paragraphs with a verb (action) followed by a noun (object) |
| Hierarchical numbering | A numbering scheme where the numeric prefix indicates the paragraph's level in the program hierarchy |
| Section | A COBOL PROCEDURE DIVISION organizational unit that contains one or more paragraphs |
| Self-documenting code | Code where the names and structure communicate the intent without requiring external documentation |
| McCabe cyclomatic complexity | A metric measuring the number of independent paths through a unit of code |
| Structured programming | A programming paradigm using only sequence, selection, and iteration — no arbitrary GO TO |