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

  1. Nested programs are ideal for program-specific utilities that do not need external visibility.

  2. GLOBAL works best for shared infrastructure (files, counters) rather than business data. Business data should be passed explicitly.

  3. The hybrid model is natural: external subprograms for reusable business logic, nested programs for internal plumbing.

  4. Each nested program should be small (under 50 lines). If a nested program grows large, it may deserve to be external.

  5. COMMON should be used judiciously — mark a program COMMON only if siblings actually need to call it.

Discussion Questions

  1. Maria chose to make counters GLOBAL rather than passing them as parameters. What are the risks? Under what circumstances would parameters be better?

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

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

  4. The team survey showed readability improved from 3.2 to 4.4. What specific aspects of nested programs contribute to this improvement?

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