Appendix G: Answers to Selected Exercises

This appendix provides worked solutions for selected exercises from each chapter. For coding exercises, complete or near-complete COBOL programs are provided with explanatory comments. For conceptual and analysis exercises, the reasoning behind the answer is shown, not just the conclusion.

These solutions represent one correct approach — in many cases, other valid solutions exist. Where alternative approaches are worth noting, they are mentioned briefly.

Note on compilation: Code solutions are written for IBM Enterprise COBOL for z/OS. Minor adjustments (e.g., removing COMP-3, changing file assignments) may be needed for GnuCOBOL. All programs follow the conventions established in Chapter 3.


Chapter 1: The COBOL Landscape Today

Exercise 1.2 — Industry Analysis

Question: Identify three industries where COBOL replacement would carry the highest risk. For each, explain why the risk is elevated and what characteristics of the existing COBOL systems contribute to that risk.

Answer:

  1. Banking and Financial Services. Risk is elevated because COBOL systems process real-time financial transactions (ATM withdrawals, wire transfers, account debits/credits) where even seconds of downtime can cause monetary losses and regulatory violations. The existing systems embody decades of accumulated business rules — interest calculation methods, regulatory compliance formulas, fee structures — that are often undocumented outside the code itself. The interconnection between core banking, payments, fraud detection, and reporting systems means that replacing one component can cascade into failures across the entire ecosystem.

  2. Government Benefits Administration (Social Security, Unemployment Insurance, Tax Processing). These systems serve millions of citizens and are subject to legislative mandates that change frequently. The 2020 unemployment crisis demonstrated that these COBOL systems are deeply embedded in critical public infrastructure. Risk factors include: massive scale (hundreds of millions of records), complex eligibility rules encoded over decades, integration with other government databases, and the political and social consequences of system failure.

  3. Insurance (Life, Health, Property & Casualty). Insurance COBOL systems manage policy administration, claims processing, and actuarial calculations that span decades — a life insurance policy issued in 1985 must still be administered correctly today. The risk is compounded by the regulatory requirement to maintain historical calculation accuracy; a modernized system must produce exactly the same results as the legacy system for policies that may have been in force for 40+ years.

Exercise 1.5 — Career Planning

Question: A junior developer asks you whether learning COBOL is a good career move. Construct a balanced argument that acknowledges both the opportunities and the risks.

Answer: The opportunity side: COBOL developers are in high demand with limited supply. The average age of the existing COBOL workforce exceeds 50, creating accelerating retirement-driven attrition. Salaries for COBOL developers with DB2 and CICS skills consistently exceed the median for software developers generally. Organizations running COBOL (banks, insurers, government) offer stable employment with strong benefits.

The risk side: the total number of COBOL positions is shrinking slowly as some organizations complete modernization projects. The skills are not broadly transferable — deep COBOL/mainframe expertise does not directly translate to cloud-native development roles. The work can be maintenance-heavy, with less greenfield development than roles in modern stacks. There is a perception problem: COBOL on a resume may be viewed negatively by hiring managers outside the mainframe ecosystem.

The balanced recommendation: COBOL is an excellent addition to a developer's skill set, not necessarily the only skill to develop. A developer who combines COBOL/mainframe skills with modern skills (REST APIs, SQL, Python, DevOps practices) is extraordinarily valuable in the modernization space — arguably more valuable than a developer with only one of those skill sets.


Chapter 2: Development Environment Setup

Exercise 2.3 — JCL Compilation

Question: Write a JCL job that compiles a COBOL program named PAYROLL with the following compiler options: RENT, MAP, LIST, OFFSET, TEST(NOALL,SYM), SSRANGE. The source is in USER01.COBOL.SRC(PAYROLL) and the load module should go to USER01.LOAD.

Answer:

//PAYCOMP  JOB (ACCT),'COMPILE PAYROLL',
//             CLASS=A,MSGCLASS=H,NOTIFY=&SYSUID
//*
//COMPILE  EXEC PGM=IGYCRCTL,
//             PARM='RENT,MAP,LIST,OFFSET,TEST(NOALL,SYM),SSRANGE'
//STEPLIB  DD  DSN=IGY.V6R4M0.SIGYCOMP,DISP=SHR
//SYSIN    DD  DSN=USER01.COBOL.SRC(PAYROLL),DISP=SHR
//SYSLIB   DD  DSN=USER01.COBOL.COPYLIB,DISP=SHR
//SYSPRINT DD  SYSOUT=*
//SYSLIN   DD  DSN=&&OBJMOD,DISP=(MOD,PASS),
//             SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT1   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT2   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT3   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT4   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT5   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT6   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//SYSUT7   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA
//*
//LKED     EXEC PGM=IEWBLINK,COND=(8,LT,COMPILE),
//             PARM='MAP,LIST,RENT'
//SYSLIB   DD  DSN=CEE.SCEELKED,DISP=SHR
//SYSLIN   DD  DSN=&&OBJMOD,DISP=(OLD,DELETE)
//SYSLMOD  DD  DSN=USER01.LOAD(PAYROLL),DISP=SHR
//SYSPRINT DD  SYSOUT=*
//SYSUT1   DD  SPACE=(TRK,(5,5)),UNIT=SYSDA

Key points: The COMPILE step invokes IGYCRCTL (the Enterprise COBOL compiler). SYSLIB provides the copybook library. The LKED step invokes the binder (IEWBLINK) to produce the load module, conditioned on the compiler returning a condition code less than 8. RENT is specified on both the compiler and binder to produce a reentrant load module, required for CICS and recommended for batch.


Chapter 3: COBOL Program Structure Deep Dive

Exercise 3.2 — Division Identification

Question: For each of the following items, identify which COBOL division and section it belongs to: (a) FILE STATUS clause, (b) FD entry, (c) PERFORM statement, (d) PROGRAM-ID, (e) WORKING-STORAGE variable definition, (f) COPY statement for a record layout.

Answer: (a) FILE STATUS — ENVIRONMENT DIVISION, INPUT-OUTPUT SECTION, FILE-CONTROL paragraph. (b) FD entry — DATA DIVISION, FILE SECTION. (c) PERFORM — PROCEDURE DIVISION. (d) PROGRAM-ID — IDENTIFICATION DIVISION. (e) WORKING-STORAGE variable — DATA DIVISION, WORKING-STORAGE SECTION. (f) COPY for a record layout — This depends on where the COPY appears. A COPY of a record layout (01 level with subordinate fields) typically appears in the DATA DIVISION, either in the FILE SECTION (under the FD) or in the WORKING-STORAGE SECTION. COPY is a compiler-directing statement that can appear anywhere the copied text would be valid.


Chapter 4: Data Description Mastery

Exercise 4.3 — REDEFINES

Question: Design a record layout for a transaction file where each record starts with a 2-byte record type. Type "CR" is a credit with an amount field (PIC 9(7)V99); type "DB" is a debit with an amount and a reason code (PIC X(4)); type "XF" is a transfer with source and destination account numbers (PIC 9(10) each).

Answer:

       01  TRANSACTION-RECORD.
           05  TXN-TYPE                PIC X(02).
               88  TXN-CREDIT         VALUE 'CR'.
               88  TXN-DEBIT          VALUE 'DB'.
               88  TXN-TRANSFER       VALUE 'XF'.
           05  TXN-DATA               PIC X(78).
           05  TXN-CREDIT-DATA REDEFINES TXN-DATA.
               10  CR-AMOUNT          PIC 9(07)V99.
               10  FILLER             PIC X(69).
           05  TXN-DEBIT-DATA REDEFINES TXN-DATA.
               10  DB-AMOUNT          PIC 9(07)V99.
               10  DB-REASON-CODE     PIC X(04).
               10  FILLER             PIC X(65).
           05  TXN-TRANSFER-DATA REDEFINES TXN-DATA.
               10  XF-SOURCE-ACCT     PIC 9(10).
               10  XF-DEST-ACCT       PIC 9(10).
               10  FILLER             PIC X(58).

The key principle: all REDEFINES must specify the same data item (TXN-DATA), and no redefined structure can exceed the size of the original item (78 bytes). The 88-level condition names on TXN-TYPE enable readable IF/EVALUATE logic. FILLER pads each variant to the full 78 bytes.


Chapter 5: Numeric Precision and Arithmetic

Exercise 5.4 — ON SIZE ERROR

Question: Write a COBOL paragraph that calculates compound interest using the formula A = P(1 + r/n)^(nt), where P = principal, r = annual rate, n = compounding periods per year, t = years. Use ON SIZE ERROR to handle overflow.

Answer:

       COMPUTE-COMPOUND-INTEREST.
           COMPUTE WS-RATE-PER-PERIOD =
               WS-ANNUAL-RATE / WS-PERIODS-PER-YEAR
           END-COMPUTE

           COMPUTE WS-TOTAL-PERIODS =
               WS-PERIODS-PER-YEAR * WS-YEARS
           END-COMPUTE

           COMPUTE WS-GROWTH-FACTOR =
               (1 + WS-RATE-PER-PERIOD)
               ** WS-TOTAL-PERIODS
               ON SIZE ERROR
                   MOVE 'Y' TO WS-OVERFLOW-FLAG
                   DISPLAY 'ERROR: Growth factor overflow'
                       ' - check input values'
               NOT ON SIZE ERROR
                   COMPUTE WS-FINAL-AMOUNT =
                       WS-PRINCIPAL * WS-GROWTH-FACTOR
                       ON SIZE ERROR
                           MOVE 'Y' TO WS-OVERFLOW-FLAG
                           DISPLAY 'ERROR: Final amount'
                               ' exceeds field capacity'
                       NOT ON SIZE ERROR
                           MOVE 'N' TO WS-OVERFLOW-FLAG
                   END-COMPUTE
           END-COMPUTE
           .

Two nested COMPUTEs are used because the exponentiation (growth factor) may overflow separately from the final multiplication. Each has its own ON SIZE ERROR handler. The intermediate variables should be defined with sufficient precision — WS-GROWTH-FACTOR as PIC 9(7)V9(10) COMP-3 and WS-FINAL-AMOUNT as PIC 9(13)V99 COMP-3 for typical financial calculations.


Chapter 6: Advanced Conditional Logic

Exercise 6.2 — EVALUATE TRUE

Question: Rewrite the following nested IF as an EVALUATE statement:

IF WS-SCORE >= 90
    MOVE 'A' TO WS-GRADE
ELSE IF WS-SCORE >= 80
    MOVE 'B' TO WS-GRADE
ELSE IF WS-SCORE >= 70
    MOVE 'C' TO WS-GRADE
ELSE IF WS-SCORE >= 60
    MOVE 'D' TO WS-GRADE
ELSE
    MOVE 'F' TO WS-GRADE.

Answer:

           EVALUATE TRUE
               WHEN WS-SCORE >= 90
                   MOVE 'A' TO WS-GRADE
               WHEN WS-SCORE >= 80
                   MOVE 'B' TO WS-GRADE
               WHEN WS-SCORE >= 70
                   MOVE 'C' TO WS-GRADE
               WHEN WS-SCORE >= 60
                   MOVE 'D' TO WS-GRADE
               WHEN OTHER
                   MOVE 'F' TO WS-GRADE
           END-EVALUATE

EVALUATE TRUE tests each WHEN condition in order and executes the first one that is true. This is clearer than nested IFs because all branches are at the same indentation level and the structure explicitly shows that exactly one path will execute. WHEN OTHER serves as the default/else case.


Chapter 7: Iteration Patterns

Exercise 7.3 — PERFORM VARYING

Question: Write a PERFORM VARYING loop that processes a table of 100 employee records, accumulating a total salary. Skip any employee whose status is 'T' (terminated).

Answer:

           MOVE ZEROS TO WS-TOTAL-SALARY

           PERFORM VARYING WS-EMP-IDX FROM 1 BY 1
               UNTIL WS-EMP-IDX > 100

               IF EMP-STATUS(WS-EMP-IDX) NOT = 'T'
                   ADD EMP-SALARY(WS-EMP-IDX)
                       TO WS-TOTAL-SALARY
                       ON SIZE ERROR
                           DISPLAY 'Salary accumulator'
                               ' overflow at index '
                               WS-EMP-IDX
                           MOVE 999999999 TO WS-TOTAL-SALARY
                   END-ADD
               END-IF

           END-PERFORM

The CONTINUE statement is not needed here — we test the positive condition (NOT = 'T') and only add when the employee is active. The ON SIZE ERROR protects against accumulator overflow when summing 100 salaries.


Chapter 8: Paragraph and Section Design

Exercise 8.2 — Paragraph Cohesion

Question: A colleague has written a paragraph named PROCESS-DATA that reads a file record, validates three fields, calculates a discount, formats an output line, and writes to a report file. Identify the cohesion problems and propose a revised paragraph structure.

Answer: The paragraph PROCESS-DATA has logical cohesion at best — it groups multiple unrelated operations that happen to execute together. Each operation (read, validate, calculate, format, write) is a distinct responsibility. A revised structure:

  • READ-INPUT-RECORD — Reads one record and checks file status.
  • VALIDATE-INPUT-FIELDS — Performs all field-level validation, sets an error flag.
  • CALCULATE-DISCOUNT — Applies business rules to compute the discount; only called if validation passed.
  • FORMAT-REPORT-LINE — Moves computed values to the output line layout.
  • WRITE-REPORT-LINE — Writes the formatted line, handles page overflow.
  • PROCESS-SINGLE-RECORD — Orchestrates the above paragraphs in sequence, implementing the skip-on-error pattern.

This achieves functional cohesion — each paragraph does exactly one thing. The orchestrating paragraph (PROCESS-SINGLE-RECORD) has sequential cohesion, which is acceptable for a coordinating routine.


Chapter 9: Copybooks and Code Reuse

Exercise 9.3 — COPY REPLACING

Question: Given a copybook ACCT-REC that defines a record with fields prefixed by ACCT-, write COPY statements that include it twice: once for a "source account" (prefix SRC-ACCT-) and once for a "target account" (prefix TGT-ACCT-).

Answer:

       01  SOURCE-ACCOUNT-RECORD.
           COPY ACCT-REC
               REPLACING ==ACCT-== BY ==SRC-ACCT-==.

       01  TARGET-ACCOUNT-RECORD.
           COPY ACCT-REC
               REPLACING ==ACCT-== BY ==TGT-ACCT-==.

The pseudo-text delimiter (== ==) allows partial-word replacement. After expansion, fields like ACCT-NUMBER become SRC-ACCT-NUMBER and TGT-ACCT-NUMBER respectively. This technique avoids maintaining separate copybooks for identical structures used in different roles.


Chapter 10: Defensive Programming

Exercise 10.2 — File Status Checking

Question: Write a file-open routine that handles the most common open failures gracefully, providing meaningful error messages.

Answer:

       OPEN-MASTER-FILE.
           OPEN I-O MASTER-FILE
           EVALUATE WS-MASTER-STATUS
               WHEN '00'
                   CONTINUE
               WHEN '05'
                   DISPLAY 'WARNING: Master file opened '
                       'but not closed properly last run'
               WHEN '35'
                   DISPLAY 'ERROR: Master file not found - '
                       'check DD statement MASTFILE'
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN '37'
                   DISPLAY 'ERROR: Master file - '
                       'organization mismatch'
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN '41'
                   DISPLAY 'ERROR: Master file already open'
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN '96'
                   DISPLAY 'ERROR: Master file - '
                       'no DD statement found'
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
               WHEN OTHER
                   DISPLAY 'ERROR: Master file open failed,'
                       ' status=' WS-MASTER-STATUS
                   MOVE 12 TO RETURN-CODE
                   STOP RUN
           END-EVALUATE
           .

Status '05' (opened but may not be positioned) is treated as a warning rather than an error because the file was successfully opened. All true error conditions set a non-zero RETURN-CODE before STOP RUN so that JCL condition code testing can detect the failure.


Chapter 11: Sequential File Processing

Exercise 11.3 — Sequential File Merge Logic

Question: Write the core logic for merging two sorted sequential files (FILE-A and FILE-B, both sorted ascending by ACCOUNT-NUMBER) into a single sorted output file.

Answer:

       MERGE-FILES.
           PERFORM READ-FILE-A
           PERFORM READ-FILE-B

           PERFORM UNTIL WS-A-EOF = 'Y' AND WS-B-EOF = 'Y'
               EVALUATE TRUE
                   WHEN WS-A-EOF = 'Y'
                       MOVE B-RECORD TO OUT-RECORD
                       PERFORM WRITE-OUTPUT
                       PERFORM READ-FILE-B
                   WHEN WS-B-EOF = 'Y'
                       MOVE A-RECORD TO OUT-RECORD
                       PERFORM WRITE-OUTPUT
                       PERFORM READ-FILE-A
                   WHEN A-ACCOUNT-NUMBER <
                        B-ACCOUNT-NUMBER
                       MOVE A-RECORD TO OUT-RECORD
                       PERFORM WRITE-OUTPUT
                       PERFORM READ-FILE-A
                   WHEN A-ACCOUNT-NUMBER >
                        B-ACCOUNT-NUMBER
                       MOVE B-RECORD TO OUT-RECORD
                       PERFORM WRITE-OUTPUT
                       PERFORM READ-FILE-B
                   WHEN OTHER
                       PERFORM HANDLE-DUPLICATE
                       PERFORM READ-FILE-A
                       PERFORM READ-FILE-B
               END-EVALUATE
           END-PERFORM
           .

The WHEN OTHER case handles duplicate keys — the action depends on business requirements (take one, take both, merge fields). This classic two-file merge pattern is the foundation of much batch processing. The key insight is handling the four states: both active, only A active, only B active, and both at EOF (which terminates the loop).


Chapter 12: Indexed File Processing (VSAM KSDS)

Exercise 12.2 — Random Read with Error Handling

Question: Write a routine that reads a customer record by account number, handling not-found, file-not-open, and other error conditions.

Answer:

       READ-CUSTOMER-BY-KEY.
           MOVE WS-SEARCH-ACCT TO CUST-ACCOUNT-NUMBER
           READ CUSTOMER-FILE INTO WS-CUSTOMER-RECORD
               KEY IS CUST-ACCOUNT-NUMBER
               INVALID KEY
                   EVALUATE WS-CUST-STATUS
                       WHEN '23'
                           MOVE 'N' TO WS-RECORD-FOUND
                       WHEN OTHER
                           DISPLAY 'READ error, status='
                               WS-CUST-STATUS
                           MOVE 'E' TO WS-RECORD-FOUND
                   END-EVALUATE
               NOT INVALID KEY
                   MOVE 'Y' TO WS-RECORD-FOUND
           END-READ
           .

File status '23' means record not found — a normal business condition, not an error. Other statuses during a read (e.g., '47' for file not open for input) indicate genuine problems. The three-value flag ('Y', 'N', 'E') lets the caller distinguish between found, not found, and error.


Chapter 13: Relative Files and RRDS

Exercise 13.2 — Direct Access by Slot

Question: Explain why RRDS is ideal for a lookup table where the key is a sequential integer (e.g., state codes 01–50) and write the read logic.

Answer: RRDS stores records in numbered slots with O(1) direct access — no index traversal is needed. When the key is a sequential integer, the key maps directly to the relative record number, making RRDS faster than KSDS for this pattern.

       READ-STATE-BY-CODE.
           MOVE WS-STATE-CODE TO STATE-RELATIVE-KEY
           READ STATE-FILE INTO WS-STATE-RECORD
               INVALID KEY
                   MOVE SPACES TO WS-STATE-NAME
                   MOVE 'N' TO WS-STATE-FOUND
               NOT INVALID KEY
                   MOVE 'Y' TO WS-STATE-FOUND
           END-READ
           .

The limitation: if the key values are sparse (e.g., product IDs 100000–100050), RRDS wastes space for all empty slots below the highest key. RRDS is best when keys are dense and start near 1.


Chapter 14: Advanced File Techniques

Exercise 14.2 — GDG Processing

Question: Describe the JCL needed to read yesterday's daily transaction file (GDG generation -1), process it, and create today's file (generation +1).

Answer:

//DAILY    EXEC PGM=TXNPROC
//INPUT    DD  DSN=PROD.DAILY.TXNS(-1),DISP=SHR
//OUTPUT   DD  DSN=PROD.DAILY.TXNS(+1),
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),UNIT=SYSDA,
//             DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)

The (-1) relative reference resolves to the previous generation; (+1) creates a new generation. The GDG base (PROD.DAILY.TXNS) must be pre-defined using IDCAMS DEFINE GDG. DISP=(NEW,CATLG,DELETE) means: create new, catalog on success, delete on failure. BLKSIZE=0 lets the system choose an optimal block size.


Chapter 15: Sort and Merge

Exercise 15.3 — INPUT PROCEDURE

Question: Write a SORT statement with an INPUT PROCEDURE that filters out inactive records before sorting.

Answer:

       SORT-ACTIVE-RECORDS.
           SORT SORT-FILE
               ON ASCENDING KEY SORT-ACCOUNT-NUMBER
               INPUT PROCEDURE IS FILTER-INACTIVE
               GIVING OUTPUT-FILE
           .

       FILTER-INACTIVE SECTION.
           OPEN INPUT RAW-FILE
           PERFORM UNTIL WS-RAW-EOF = 'Y'
               READ RAW-FILE INTO WS-RAW-RECORD
                   AT END
                       MOVE 'Y' TO WS-RAW-EOF
                   NOT AT END
                       IF RAW-STATUS-CODE NOT = 'I'
                           MOVE WS-RAW-RECORD
                               TO SORT-RECORD
                           RELEASE SORT-RECORD
                       END-IF
               END-READ
           END-PERFORM
           CLOSE RAW-FILE
           .

The INPUT PROCEDURE reads from the raw file, skips records where the status code is 'I' (inactive), and RELEASEs qualifying records to the sort. RELEASE is the SORT equivalent of WRITE. This approach is more efficient than sorting the entire file and then filtering, because fewer records enter the sort.


Chapter 16: Report Writer

Exercise 16.2 — Control Break

Question: Describe (with pseudocode or COBOL) the control break logic needed to produce a report with department subtotals and a grand total.

Answer:

       PROCESS-REPORT.
           PERFORM READ-SORTED-INPUT
           MOVE EMP-DEPT TO WS-PREV-DEPT
           MOVE ZEROS TO WS-DEPT-TOTAL
           MOVE ZEROS TO WS-GRAND-TOTAL

           PERFORM UNTIL WS-EOF = 'Y'
               IF EMP-DEPT NOT = WS-PREV-DEPT
                   PERFORM PRINT-DEPT-SUBTOTAL
                   MOVE ZEROS TO WS-DEPT-TOTAL
                   MOVE EMP-DEPT TO WS-PREV-DEPT
               END-IF
               ADD EMP-SALARY TO WS-DEPT-TOTAL
               ADD EMP-SALARY TO WS-GRAND-TOTAL
               PERFORM FORMAT-AND-PRINT-DETAIL
               PERFORM READ-SORTED-INPUT
           END-PERFORM

           PERFORM PRINT-DEPT-SUBTOTAL
           PERFORM PRINT-GRAND-TOTAL
           .

The critical detail that beginners often miss: after the main loop ends (EOF), you must print the last department subtotal and the grand total. The file must be pre-sorted by department. Report Writer automates this logic declaratively, but understanding the procedural version is essential for maintaining legacy code.


Chapter 17: String Handling

Exercise 17.3 — UNSTRING with Multiple Delimiters

Question: Parse a CSV record that may contain commas within quoted fields. For simplicity, assume no escaped quotes.

Answer: Standard UNSTRING cannot handle quoted CSV fields natively. The solution requires a character-by-character scan:

       PARSE-CSV-RECORD.
           MOVE 1 TO WS-POS
           MOVE 1 TO WS-FIELD-NUM
           MOVE SPACES TO WS-CURRENT-FIELD
           MOVE 1 TO WS-FIELD-POS
           MOVE 'N' TO WS-IN-QUOTES

           PERFORM VARYING WS-POS FROM 1 BY 1
               UNTIL WS-POS > FUNCTION LENGTH(WS-INPUT)

               EVALUATE TRUE
                   WHEN WS-INPUT(WS-POS:1) = '"'
                       IF WS-IN-QUOTES = 'N'
                           MOVE 'Y' TO WS-IN-QUOTES
                       ELSE
                           MOVE 'N' TO WS-IN-QUOTES
                       END-IF

                   WHEN WS-INPUT(WS-POS:1) = ','
                    AND WS-IN-QUOTES = 'N'
                       PERFORM STORE-FIELD
                       ADD 1 TO WS-FIELD-NUM
                       MOVE SPACES TO WS-CURRENT-FIELD
                       MOVE 1 TO WS-FIELD-POS

                   WHEN OTHER
                       MOVE WS-INPUT(WS-POS:1)
                           TO WS-CURRENT-FIELD
                               (WS-FIELD-POS:1)
                       ADD 1 TO WS-FIELD-POS
               END-EVALUATE

           END-PERFORM

           PERFORM STORE-FIELD
           .

This is a state-machine approach: the WS-IN-QUOTES flag tracks whether the current position is inside a quoted field. Commas inside quotes are treated as data; commas outside quotes are field delimiters. STORE-FIELD would move WS-CURRENT-FIELD to the appropriate element in a field array based on WS-FIELD-NUM.


Chapter 18: Table Handling and Searching

Exercise 18.2 — SEARCH ALL

Question: Define a state lookup table with 50 entries and write the SEARCH ALL logic to find a state by its two-character code.

Answer:

       01  STATE-TABLE.
           05  STATE-ENTRY OCCURS 50 TIMES
                   ASCENDING KEY IS ST-CODE
                   INDEXED BY ST-IDX.
               10  ST-CODE         PIC X(02).
               10  ST-NAME         PIC X(20).
               10  ST-TAX-RATE     PIC 9V9(4).

       FIND-STATE.
           SET ST-IDX TO 1
           SEARCH ALL STATE-ENTRY
               AT END
                   MOVE 'N' TO WS-STATE-FOUND
                   MOVE SPACES TO WS-STATE-NAME
               WHEN ST-CODE(ST-IDX) = WS-SEARCH-CODE
                   MOVE 'Y' TO WS-STATE-FOUND
                   MOVE ST-NAME(ST-IDX) TO WS-STATE-NAME
                   MOVE ST-TAX-RATE(ST-IDX)
                       TO WS-STATE-TAX-RATE
           END-SEARCH
           .

For SEARCH ALL to work, the table must be defined with ASCENDING (or DESCENDING) KEY, and the data must actually be in that order when the search executes. SEARCH ALL performs a binary search (O(log n)), which matters when tables are large.


Chapter 19: Pointer and Reference Modification

Exercise 19.2 — Reference Modification

Question: Extract the year, month, and day from a date field stored as PIC X(10) in the format "YYYY-MM-DD".

Answer:

           MOVE WS-DATE-STRING(1:4) TO WS-YEAR
           MOVE WS-DATE-STRING(6:2) TO WS-MONTH
           MOVE WS-DATE-STRING(9:2) TO WS-DAY

Reference modification uses a 1-based position and a length: (start:length). Position 1 = first character. The hyphens at positions 5 and 8 are skipped. This is simpler than UNSTRING for fixed-format strings where the positions are known.


Chapter 20: Intrinsic Functions

Exercise 20.3 — Multiple Functions

Question: Using intrinsic functions, convert a string to uppercase, calculate its length (excluding trailing spaces), and determine the ordinal position of its first character.

Answer:

           MOVE FUNCTION UPPER-CASE(WS-INPUT-STRING)
               TO WS-UPPER-STRING

           COMPUTE WS-TRIMMED-LENGTH =
               FUNCTION LENGTH(
                   FUNCTION TRIM(WS-UPPER-STRING
                       TRAILING))

           COMPUTE WS-FIRST-CHAR-ORD =
               FUNCTION ORD(WS-UPPER-STRING(1:1))

FUNCTION TRIM (Enterprise COBOL 6.3+) removes leading and/or trailing spaces. FUNCTION ORD returns the ordinal position of a character in the collating sequence (1-based). These can be nested, as shown with LENGTH(TRIM(...)).


Chapter 21: Date and Time Processing

Exercise 21.2 — Date Arithmetic

Question: Calculate the number of days between two dates stored as PIC 9(8) in YYYYMMDD format.

Answer:

           COMPUTE WS-START-INTEGER =
               FUNCTION INTEGER-OF-DATE(WS-START-DATE)

           COMPUTE WS-END-INTEGER =
               FUNCTION INTEGER-OF-DATE(WS-END-DATE)

           COMPUTE WS-DAYS-BETWEEN =
               WS-END-INTEGER - WS-START-INTEGER

INTEGER-OF-DATE converts a Gregorian date (YYYYMMDD) to an integer representing the number of days since a base date. Subtracting two such integers gives the number of days between the dates. DATE-OF-INTEGER performs the reverse conversion. This is far more reliable than manual calculations that must account for leap years and varying month lengths.


Chapter 22: CALL and Subprogram Linkage

Exercise 22.3 — Dynamic vs. Static Call

Question: Modify a static CALL to be dynamic and explain the runtime implications.

Answer:

Static call:

           CALL 'CALCUTIL' USING WS-INPUT WS-OUTPUT

Dynamic call (approach 1 — identifier):

           MOVE 'CALCUTIL' TO WS-PROGRAM-NAME
           CALL WS-PROGRAM-NAME USING WS-INPUT WS-OUTPUT
               ON EXCEPTION
                   DISPLAY 'Program CALCUTIL not found'
                   MOVE 12 TO RETURN-CODE
           END-CALL

Dynamic call (approach 2 — compiler option): Compile with the DYNAM option; the CALL 'CALCUTIL' literal is then resolved dynamically at run time.

Runtime implications: (1) The subprogram is loaded from the load library on first CALL, adding overhead. (2) The subprogram can be updated independently without re-linking the caller. (3) ON EXCEPTION can catch "program not found" errors. (4) CANCEL can release the subprogram's storage. (5) Working-storage in the subprogram persists between calls unless CANCEL is issued or the program has INITIAL.


Chapter 23: Parameter Passing Patterns

Exercise 23.2 — BY CONTENT vs. BY REFERENCE

Question: Explain with a code example when BY CONTENT is preferred over BY REFERENCE.

Answer: BY CONTENT is preferred when the caller wants to pass data to the subprogram but must ensure the subprogram cannot modify the caller's original value.

      *> Caller
           MOVE WS-CUSTOMER-BALANCE TO WS-CALC-INPUT
           CALL 'FEECALC' USING
               BY CONTENT WS-CUSTOMER-BALANCE
               BY REFERENCE WS-FEE-RESULT
           END-CALL
      *> WS-CUSTOMER-BALANCE is guaranteed unchanged
      *> WS-FEE-RESULT contains the computed fee

The subprogram FEECALC receives a copy of WS-CUSTOMER-BALANCE. Even if FEECALC modifies its first parameter internally (e.g., for intermediate calculations), the caller's WS-CUSTOMER-BALANCE is protected. The second parameter is BY REFERENCE because the caller needs the result back.


Chapter 24: Nested Programs

Exercise 24.2 — Nested Program Design

Question: When would you choose a nested program over an external subprogram?

Answer: Nested programs are preferred when: (1) the subprogram is used only by the containing program and has no independent utility; (2) you want to share data with the contained program via GLOBAL without parameter passing; (3) you want to avoid the overhead of dynamic loading; (4) the combined source is small enough to manage in a single compilation unit. External subprograms are preferred when: (1) the functionality is reusable across multiple callers; (2) the subprogram needs independent compilation and deployment; (3) the team uses a library model for shared components.


Chapter 25: Object-Oriented COBOL

Exercise 25.3 — Class Definition

Question: Define a COBOL class for a BankAccount with balance, deposit, and withdraw methods.

Answer:

       IDENTIFICATION DIVISION.
       CLASS-ID. BankAccount INHERITS Base.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           CLASS Base IS "java.lang.Object".

       IDENTIFICATION DIVISION.
       OBJECT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-BALANCE       PIC S9(13)V99 COMP-3
                             VALUE ZEROS.

       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. "deposit".
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-AMOUNT         PIC S9(13)V99 COMP-3.
       PROCEDURE DIVISION USING LS-AMOUNT.
           ADD LS-AMOUNT TO WS-BALANCE
           .
       END METHOD "deposit".

       IDENTIFICATION DIVISION.
       METHOD-ID. "withdraw".
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-RESULT         PIC 9 VALUE ZERO.
           88  WITHDRAW-OK   VALUE 1.
           88  WITHDRAW-FAIL VALUE 0.
       LINKAGE SECTION.
       01  LS-AMOUNT         PIC S9(13)V99 COMP-3.
       01  LS-STATUS         PIC 9.
       PROCEDURE DIVISION USING LS-AMOUNT LS-STATUS.
           IF LS-AMOUNT <= WS-BALANCE
               SUBTRACT LS-AMOUNT FROM WS-BALANCE
               MOVE 1 TO LS-STATUS
           ELSE
               MOVE 0 TO LS-STATUS
           END-IF
           .
       END METHOD "withdraw".

       END OBJECT.
       END CLASS BankAccount.

OO COBOL uses nested IDENTIFICATION DIVISIONS for the class, object, and each method. Instance data (WS-BALANCE) is defined in the OBJECT paragraph's WORKING-STORAGE. Each method has its own PROCEDURE DIVISION with a USING clause for parameters.


Chapter 27: Embedded SQL Fundamentals

Exercise 27.2 — Cursor Processing

Question: Write embedded SQL to declare and process a cursor that reads all employees in a department, ordered by last name.

Answer:

           EXEC SQL
               DECLARE EMP-CURSOR CURSOR FOR
               SELECT EMPLOYEE_ID, LAST_NAME,
                      FIRST_NAME, SALARY
               FROM EMPLOYEE
               WHERE DEPT_ID = :WS-DEPT-ID
               ORDER BY LAST_NAME
           END-EXEC

           EXEC SQL OPEN EMP-CURSOR END-EXEC
           IF SQLCODE NOT = 0
               DISPLAY 'Cursor open error: ' SQLCODE
               GO TO EMP-CURSOR-EXIT
           END-IF

           PERFORM UNTIL SQLCODE NOT = 0
               EXEC SQL
                   FETCH EMP-CURSOR
                   INTO :WS-EMP-ID, :WS-LAST-NAME,
                        :WS-FIRST-NAME, :WS-SALARY
               END-EXEC
               IF SQLCODE = 0
                   PERFORM PROCESS-EMPLOYEE
               END-IF
           END-PERFORM

           IF SQLCODE NOT = 100
               DISPLAY 'Fetch error: ' SQLCODE
           END-IF

           EXEC SQL CLOSE EMP-CURSOR END-EXEC
           .
       EMP-CURSOR-EXIT.
           EXIT.

SQLCODE 100 means no more rows (normal end of cursor). Any other non-zero SQLCODE indicates an error. The cursor must be explicitly opened, fetched in a loop, and closed. Host variables are prefixed with colons inside EXEC SQL blocks.


Chapter 28: Advanced DB2 Programming

Exercise 28.3 — Handling Deadlocks

Question: Write the error-handling logic for an UPDATE statement that retries on deadlock (SQLCODE -911).

Answer:

       UPDATE-WITH-RETRY.
           MOVE 0 TO WS-RETRY-COUNT

           PERFORM ATTEMPT-UPDATE
               UNTIL WS-UPDATE-DONE = 'Y'
               OR WS-RETRY-COUNT >= 3
           END-PERFORM

           IF WS-UPDATE-DONE NOT = 'Y'
               DISPLAY 'Update failed after 3 retries'
               EXEC SQL ROLLBACK END-EXEC
           END-IF
           .

       ATTEMPT-UPDATE.
           ADD 1 TO WS-RETRY-COUNT

           EXEC SQL
               UPDATE ACCOUNT
               SET BALANCE = BALANCE - :WS-AMOUNT
               WHERE ACCOUNT_ID = :WS-ACCOUNT-ID
           END-EXEC

           EVALUATE SQLCODE
               WHEN 0
                   MOVE 'Y' TO WS-UPDATE-DONE
               WHEN -911
                   DISPLAY 'Deadlock on attempt '
                       WS-RETRY-COUNT ', retrying...'
                   EXEC SQL ROLLBACK END-EXEC
               WHEN OTHER
                   DISPLAY 'SQL error: ' SQLCODE
                   MOVE 'Y' TO WS-UPDATE-DONE
           END-EVALUATE
           .

After SQLCODE -911 (deadlock/timeout), DB2 has already rolled back the unit of work. The program must re-execute the transaction from the beginning (including any prior SQL within the same logical unit of work, not just the failing statement). The retry limit prevents infinite loops.


Chapter 29: CICS Fundamentals

Exercise 29.2 — Pseudo-Conversational Design

Question: Write the main control logic for a pseudo-conversational CICS program that displays a menu, accepts a choice, and processes it.

Answer:

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           EVALUATE TRUE
               WHEN EIBCALEN = 0
                   PERFORM FIRST-TIME-THROUGH
               WHEN OTHER
                   PERFORM PROCESS-USER-INPUT
           END-EVALUATE
           .

       FIRST-TIME-THROUGH.
           PERFORM SEND-MENU-SCREEN
           EXEC CICS RETURN
               TRANSID('MENU')
               COMMAREA(WS-COMMAREA)
               LENGTH(LENGTH OF WS-COMMAREA)
           END-EXEC
           .

       PROCESS-USER-INPUT.
           MOVE DFHCOMMAREA TO WS-COMMAREA
           EXEC CICS RECEIVE MAP('MENMAP')
               MAPSET('MENSET')
               INTO(MENMAPI)
               RESP(WS-RESP)
           END-EXEC

           IF WS-RESP NOT = DFHRESP(NORMAL)
               PERFORM SEND-ERROR-SCREEN
               EXEC CICS RETURN END-EXEC
           END-IF

           EVALUATE MENU-CHOICEI
               WHEN '1'
                   EXEC CICS XCTL
                       PROGRAM('CUSTINQ')
                       COMMAREA(WS-COMMAREA)
                   END-EXEC
               WHEN '2'
                   EXEC CICS XCTL
                       PROGRAM('ACCTMNT')
                       COMMAREA(WS-COMMAREA)
                   END-EXEC
               WHEN 'X'
                   EXEC CICS SEND TEXT
                       FROM(WS-GOODBYE-MSG)
                       ERASE
                   END-EXEC
                   EXEC CICS RETURN END-EXEC
               WHEN OTHER
                   MOVE 'Invalid choice' TO
                       MENU-MSGO
                   PERFORM SEND-MENU-SCREEN
                   EXEC CICS RETURN
                       TRANSID('MENU')
                       COMMAREA(WS-COMMAREA)
                   END-EXEC
           END-EVALUATE
           .

The critical pseudo-conversational pattern: EIBCALEN = 0 means first invocation (no COMMAREA from a previous execution). On subsequent entries, the COMMAREA carries state from the previous interaction. RETURN TRANSID tells CICS which transaction to start when the user presses Enter.


Chapter 30: Advanced CICS Programming

Exercise 30.2 — Container/Channel Pattern

Question: Modify a COMMAREA-based LINK to use channels and containers instead, passing a customer record larger than 32 KB.

Answer:

           EXEC CICS PUT CONTAINER('CUST-DATA')
               CHANNEL('CUST-CHAN')
               FROM(WS-LARGE-CUSTOMER-RECORD)
               FLENGTH(LENGTH OF
                   WS-LARGE-CUSTOMER-RECORD)
               RESP(WS-RESP)
           END-EXEC

           EXEC CICS LINK PROGRAM('CUSTPROC')
               CHANNEL('CUST-CHAN')
               RESP(WS-RESP)
           END-EXEC

           EXEC CICS GET CONTAINER('CUST-RESULT')
               CHANNEL('CUST-CHAN')
               INTO(WS-CUSTOMER-RESULT)
               FLENGTH(WS-RESULT-LENGTH)
               RESP(WS-RESP)
           END-EXEC

The called program (CUSTPROC) retrieves data from container 'CUST-DATA' on the same channel and puts its response into container 'CUST-RESULT'. Channels and containers have no 32 KB limit, support multiple named data areas, and are automatically cleaned up when the channel goes out of scope.


Chapter 31: IMS/DB Basics

Exercise 31.2 — DL/I GU Call

Question: Write a DL/I call to retrieve a specific customer segment by customer ID.

Answer:

       GET-CUSTOMER-BY-ID.
           MOVE 'GU' TO WS-DLI-FUNCTION
           MOVE SPACES TO WS-CUST-SSA
           STRING 'CUSTSEG (CUSTID  = '''
               WS-SEARCH-CUSTID
               ''')'
               DELIMITED BY SIZE
               INTO WS-CUST-SSA
           END-STRING

           CALL 'CBLTDLI' USING
               WS-DLI-FUNCTION
               WS-CUST-PCB
               WS-CUST-IO-AREA
               WS-CUST-SSA

           EVALUATE WS-CUST-STATUS-CODE
               WHEN SPACES
                   MOVE 'Y' TO WS-CUST-FOUND
               WHEN 'GE'
                   MOVE 'N' TO WS-CUST-FOUND
               WHEN OTHER
                   DISPLAY 'DL/I error: '
                       WS-CUST-STATUS-CODE
                   MOVE 'E' TO WS-CUST-FOUND
           END-EVALUATE
           .

The SSA (Segment Search Argument) specifies the segment type (CUSTSEG) and the qualification (CUSTID equals the search value). Status code spaces = success; 'GE' = segment not found. The SSA must be exactly formatted with field name padded to 8 characters.


Chapter 33: Debugging Strategies

Exercise 33.2 — S0C7 Diagnosis

Question: A program ABENDs with S0C7 (data exception). The dump shows the failing instruction was a PACK. What is the most likely cause and how do you diagnose it?

Answer: S0C7 during a PACK instruction means the source field contains data that is not valid packed-decimal or zoned-decimal (non-numeric characters in a numeric field). Common causes: (1) an uninitialized field containing spaces or LOW-VALUES; (2) a record layout mismatch where an alphanumeric field overlays a numeric field; (3) a file with corrupted data; (4) a REDEFINES where the wrong view is being used.

Diagnosis steps: (1) In the dump, locate the PSW (Program Status Word) and extract the instruction address. (2) Use the compiler listing's offset map (LIST option) to identify the COBOL statement at that address. (3) Identify the source data item. (4) In the dump, find that data item's storage (using the Data Division Map from the MAP compiler option) and examine its hex contents. (5) Non-hex characters (e.g., x'40' = space) in a numeric field confirm the diagnosis.

Prevention: use INITIALIZE instead of MOVE SPACES for group items containing numeric fields. Use the NUMCHECK compiler option (Enterprise COBOL 6.3+) for automatic numeric validation.


Chapter 34: Unit Testing COBOL

Exercise 34.2 — Test Case Design

Question: List the test cases needed for a paragraph that calculates shipping cost based on weight (under 1 lb = $5, 1-5 lb = $10, over 5 lb = $10 + $2/lb over 5).

Answer: Applying boundary value analysis:

Test Case Weight Expected Cost Rationale
1 0.01 $5.00 Minimum valid weight
2 0.99 $5.00 Just under 1 lb boundary
3 1.00 $10.00 Exactly at boundary
4 3.00 $10.00 Mid-range of 1-5 tier
5 5.00 $10.00 Exactly at upper boundary
6 5.01 $10.02 Just over 5 lb boundary
7 10.00 $20.00 Well above boundary
8 0.00 Error/reject Zero weight (invalid)
9 -1.00 Error/reject Negative weight (invalid)

The boundary values (0.99, 1.00, 5.00, 5.01) are the most important test cases because boundary errors are the most common defects in range-based logic.


Chapter 36: Performance Tuning

Exercise 36.2 — COMP-3 vs. DISPLAY

Question: A program processes 10 million records, performing arithmetic on 5 numeric fields per record. The fields are currently USAGE DISPLAY. Estimate the performance impact of converting them to COMP-3.

Answer: When arithmetic is performed on DISPLAY numeric fields, the runtime must convert them to packed-decimal (PACK instruction), perform the arithmetic, and convert back (UNPACK instruction). COMP-3 fields are already in packed format, eliminating these conversions.

For each arithmetic operation on a DISPLAY field, approximately 2 extra instructions (PACK + UNPACK) are needed compared to COMP-3. With 5 fields per record and 10 million records, that is 100 million avoided conversions. At roughly 1-2 nanoseconds per conversion on modern z/Architecture, the saving is approximately 100-200 milliseconds of CPU time.

Additionally, COMP-3 fields are smaller (e.g., PIC 9(9) COMP-3 = 5 bytes vs. 9 bytes in DISPLAY), reducing record size and I/O volume. For 10 million records with 5 fields saved at ~4 bytes each, that is approximately 200 MB less I/O.

The conversion is almost always worthwhile for arithmetic-heavy programs. The exception: fields that are only moved (no arithmetic) and must match an external file layout should remain DISPLAY.


Chapter 37: Migration and Modernization

Exercise 37.2 — Modernization Assessment

Question: Your organization has 2,000 COBOL programs. Propose criteria for prioritizing which to modernize first.

Answer: Prioritization criteria (weighted scoring model):

  1. Business criticality (weight: high) — Programs supporting revenue-generating or regulatory functions rank highest.
  2. Change frequency (weight: high) — Programs modified frequently incur higher maintenance costs that modernization can reduce.
  3. Technical complexity (weight: medium) — Programs with extensive GOTOs, no structured design, and poor documentation are costly to maintain but also risky to modernize.
  4. Integration surface (weight: medium) — Programs that need to interact with modern systems (mobile apps, APIs) are high-value modernization candidates.
  5. Developer availability (weight: medium) — Programs whose only knowledgeable developer is near retirement are at risk.
  6. Performance constraints (weight: low-medium) — Programs approaching CPU or I/O limits may benefit from optimization during modernization.
  7. Standalone vs. coupled (weight: medium) — Programs with few dependencies on other COBOL programs are easier to modernize independently.

Start with high-business-value, high-change-frequency, relatively-standalone programs. Avoid starting with the most complex, deeply-coupled systems — build organizational experience on simpler targets first.


Chapter 38: Batch Processing Patterns

Exercise 38.2 — Restart/Recovery

Question: Design a checkpoint/restart mechanism for a long-running batch program that processes millions of records.

Answer: Key components: (1) A checkpoint file that records the last successfully processed record key and running totals at periodic intervals (e.g., every 10,000 records). (2) On startup, the program checks for a checkpoint file; if found, it repositions to the last checkpoint and restores accumulated totals. (3) Commit points for database updates aligned with checkpoint intervals.

       PROCESS-WITH-CHECKPOINT.
           PERFORM CHECK-FOR-RESTART
           PERFORM UNTIL WS-EOF = 'Y'
               PERFORM PROCESS-ONE-RECORD
               ADD 1 TO WS-RECORDS-PROCESSED
               IF FUNCTION MOD(WS-RECORDS-PROCESSED
                   10000) = 0
                   PERFORM WRITE-CHECKPOINT
                   EXEC SQL COMMIT END-EXEC
               END-IF
               PERFORM READ-NEXT-RECORD
           END-PERFORM
           PERFORM DELETE-CHECKPOINT-FILE
           .

The checkpoint write and COMMIT must be atomic (or at least in the correct order: write checkpoint, then COMMIT). On restart, records between the last checkpoint and the failure point will be reprocessed, so the processing logic must be idempotent or the checkpoint must capture enough state to skip already-processed records.


Chapter 40: COBOL and the Modern Stack

Exercise 40.2 — JSON Integration

Question: Write COBOL code to generate a JSON response for a customer inquiry API.

Answer:

       01  WS-CUSTOMER-RESPONSE.
           05  CUSTOMER-ID         PIC 9(10).
           05  CUSTOMER-NAME       PIC X(50).
           05  ACCOUNT-BALANCE     PIC S9(11)V99.
           05  ACCOUNT-STATUS      PIC X(10).

       01  WS-JSON-OUTPUT          PIC X(2000).
       01  WS-JSON-LENGTH          PIC 9(8) COMP.

       GENERATE-JSON-RESPONSE.
           MOVE WS-CUST-ID TO CUSTOMER-ID
           MOVE WS-CUST-NAME TO CUSTOMER-NAME
           MOVE WS-CUST-BALANCE TO ACCOUNT-BALANCE
           MOVE WS-CUST-STATUS TO ACCOUNT-STATUS

           JSON GENERATE WS-JSON-OUTPUT
               FROM WS-CUSTOMER-RESPONSE
               COUNT IN WS-JSON-LENGTH
               ON EXCEPTION
                   DISPLAY 'JSON generation error'
                   MOVE '{"error":"generation failed"}'
                       TO WS-JSON-OUTPUT
               NOT ON EXCEPTION
                   CONTINUE
           END-JSON
           .

JSON GENERATE automatically converts the COBOL data structure to JSON, using the data-names as keys. The resulting JSON would look like: {"CUSTOMER-ID":1234567890,"CUSTOMER-NAME":"SMITH JOHN",...}. The NAME phrase (Enterprise COBOL 6.2+) allows mapping COBOL names to different JSON key names.


Chapter 41: Legacy Code Archaeology

Exercise 41.2 — Code Analysis

Question: You encounter a 5,000-line COBOL program with no comments, single-character variable names, and extensive use of GO TO. Describe your approach to understanding it.

Answer: Systematic approach:

  1. Start with the DATA DIVISION. Read every FD and WORKING-STORAGE item. File descriptions reveal what the program reads and writes. Variable names (even poor ones) and their PICTUREs reveal the data model. Map the 01-level structures.

  2. Identify the entry and exit points. Find the first paragraph in the PROCEDURE DIVISION and trace the main control flow. Identify STOP RUN and GOBACK statements.

  3. Map the GO TO targets. List every paragraph name that is a GO TO target. These form the program's actual control flow graph. Draw it on paper or in a tool.

  4. Trace the I/O. Find every READ, WRITE, REWRITE, DELETE, OPEN, and CLOSE statement. This reveals the program's fundamental purpose (what it reads, transforms, and writes).

  5. Run it with test data. Use the DISPLAY statement or z/OS Debugger to trace actual execution with known inputs. Empirical observation often clarifies logic faster than static reading.

  6. Document as you go. Add comments to each paragraph as you understand it. Rename variables in a working copy (using editor find/replace) to meaningful names.

  7. Write characterization tests. Before changing anything, capture the program's current behavior with known inputs and outputs. These tests become your safety net.


Solutions for additional exercises are available in the instructor's supplement. For coding exercises, always test your solution with multiple inputs including boundary values and error conditions.