Case Study 1: GlobalBank's Hybrid Architecture in ACCT-MAINT
Background
After modularizing ACCT-MAINT into external subprograms (Chapter 22), Maria Chen noticed a secondary problem: the driver program still contained several small utility routines as inline PERFORMed paragraphs. These routines — account number formatting, step logging, and summary printing — were used only within ACCT-MAINT but cluttered its PROCEDURE DIVISION.
"The driver program was clean at the top level — call ACCTREAD, call ACCTVAL, call ACCTCALC, call ACCTUPD — but it also had 200 lines of formatting and logging logic mixed in," Maria explains. "Those routines didn't deserve to be external subprograms — nobody else needed them. But leaving them as paragraphs in the driver made the code harder to read."
Derek Washington suggested nested programs: "I read about them in college but never saw them used. They seemed perfect for this — small utilities that belong to one program and nobody else."
The Refactoring
Maria identified four internal utilities to convert from paragraphs to nested programs:
| Utility | Lines | Purpose | Why Nested? |
|---|---|---|---|
| FORMAT-ACCT | 15 | Format account ID with dashes | Only used in ACCT-MAINT display logic |
| LOG-STEP | 25 | Write timestamped audit entry | Uses ACCT-MAINT's specific audit file |
| PRINT-TOTALS | 30 | Display processing summary | ACCT-MAINT-specific summary format |
| CHECK-LIMITS | 40 | Validate processing thresholds | ACCT-MAINT-specific error limits |
Design Decisions
Decision 1: GLOBAL for the audit file. The audit file is opened and closed by ACCT-MAINT. LOG-STEP needs to write to it. Rather than passing a file handle or status code through every call, Maria made the FD GLOBAL.
Decision 2: GLOBAL for counters. Processing counters (records read, updated, errors) are tracked by the outer program and used by PRINT-TOTALS and CHECK-LIMITS. Making them GLOBAL avoided adding three parameters to each call.
Decision 3: COMMON for LOG-STEP and FORMAT-ACCT. If any nested program needs to log or format, it can call these siblings directly.
Decision 4: Parameters for everything else. Specific data items (account records, return codes) are passed explicitly via CALL USING to maintain clear data flow.
Before: Paragraph-Based
PROCEDURE DIVISION.
MAIN-LOGIC.
OPEN EXTEND AUDIT-LOG
PERFORM LOG-JOB-START
PERFORM PROCESS-ACCOUNTS
UNTIL END-OF-INPUT
PERFORM PRINT-SUMMARY
PERFORM LOG-JOB-END
CLOSE AUDIT-LOG
STOP RUN.
FORMAT-ACCT-DISPLAY.
STRING WS-ACCT-ID(1:4) '-' ...
...
LOG-JOB-START.
MOVE FUNCTION CURRENT-DATE TO WS-TS
STRING WS-TS ... 'Job started' ...
WRITE AUDIT-RECORD
...
PRINT-SUMMARY.
DISPLAY 'Records read: ' WS-READ-COUNT
...
CHECK-ERROR-LIMITS.
IF WS-ERROR-COUNT > WS-ERROR-MAX ...
...
All of these paragraphs shared WORKING-STORAGE with the rest of the program, creating implicit coupling.
After: Nested Program-Based
PROCEDURE DIVISION.
MAIN-LOGIC.
OPEN EXTEND AUDIT-LOG
CALL 'LOG-STEP' USING BY CONTENT 'Job started'
PERFORM PROCESS-ACCOUNTS
UNTIL END-OF-INPUT
CALL 'PRINT-TOTALS' USING WS-READ-COUNT
WS-OK-COUNT
WS-ERR-COUNT
CALL 'CHECK-LIMITS' USING WS-ERR-COUNT
WS-ERR-MAX
WS-RETURN-CODE
CALL 'LOG-STEP' USING BY CONTENT 'Job completed'
CLOSE AUDIT-LOG
STOP RUN.
* (Nested programs follow, each with its own
* WORKING-STORAGE and LINKAGE SECTION)
The outer program's PROCEDURE DIVISION became pure orchestration. Each utility has its own isolated WORKING-STORAGE, reducing the risk of accidental data corruption.
Results
| Metric | Paragraphs | Nested Programs |
|---|---|---|
| Driver PROCEDURE DIVISION size | 450 lines | 250 lines |
| Shared WORKING-STORAGE items | 47 | 12 (GLOBAL only) |
| Risk of accidental data corruption | Moderate | Low |
| Readability (team survey, 1-5) | 3.2 | 4.4 |
| Time to understand driver logic | 30 minutes | 10 minutes |
Derek Washington, who reviewed the refactored code: "Now when I read the PROCEDURE DIVISION, I see the algorithm — open, log, process, summarize, check, log, close. The 'how' of each step is inside the nested programs, out of sight until I need it."
Lessons Learned
-
Nested programs are ideal for program-specific utilities that do not need external visibility.
-
GLOBAL works best for shared infrastructure (files, counters) rather than business data. Business data should be passed explicitly.
-
The hybrid model is natural: external subprograms for reusable business logic, nested programs for internal plumbing.
-
Each nested program should be small (under 50 lines). If a nested program grows large, it may deserve to be external.
-
COMMON should be used judiciously — mark a program COMMON only if siblings actually need to call it.
Discussion Questions
-
Maria chose to make counters GLOBAL rather than passing them as parameters. What are the risks? Under what circumstances would parameters be better?
-
If CHECK-LIMITS needs to call LOG-STEP to log a threshold violation, what attribute must LOG-STEP have? What attribute must CHECK-LIMITS have?
-
Suppose a new requirement arises: another program (TXN-PROC) needs the same LOG-STEP functionality. How would you handle this? Would you extract LOG-STEP from ACCT-MAINT?
-
The team survey showed readability improved from 3.2 to 4.4. What specific aspects of nested programs contribute to this improvement?
-
If the total source file for ACCT-MAINT (with nested programs) exceeds 3,000 lines, what would you recommend? At what size does a single source file become a liability?