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:
-
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.
-
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.
-
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):
- Business criticality (weight: high) — Programs supporting revenue-generating or regulatory functions rank highest.
- Change frequency (weight: high) — Programs modified frequently incur higher maintenance costs that modernization can reduce.
- Technical complexity (weight: medium) — Programs with extensive GOTOs, no structured design, and poor documentation are costly to maintain but also risky to modernize.
- Integration surface (weight: medium) — Programs that need to interact with modern systems (mobile apps, APIs) are high-value modernization candidates.
- Developer availability (weight: medium) — Programs whose only knowledgeable developer is near retirement are at risk.
- Performance constraints (weight: low-medium) — Programs approaching CPU or I/O limits may benefit from optimization during modernization.
- 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:
-
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.
-
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.
-
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.
-
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).
-
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.
-
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.
-
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.