The world runs on COBOL. Despite decades of predictions about its demise, COBOL remains the backbone of global commerce, government services, and financial infrastructure. An estimated 220 billion lines of COBOL code are in active production today...
In This Chapter
- Part VIII - Modern COBOL and System Evolution
- 39.1 Understanding Legacy COBOL Systems
- 39.2 Reading and Understanding Unfamiliar COBOL Code
- 39.3 Documentation Archaeology
- 39.4 Common Legacy Patterns and Anti-Patterns
- 39.5 Refactoring Techniques
- 39.6 Modernization Strategies
- 39.7 The Strangler Fig Pattern for COBOL Systems
- 39.8 Data Modernization
- 39.9 Code Analysis Tools
- 39.10 Testing During Modernization
- 39.11 Migration from COBOL-74/85 to COBOL 2002/2014
- 39.12 Case Study: Modernizing a 1980s Insurance Claims System
- Summary
Chapter 39: Legacy System Maintenance and Modernization
Part VIII - Modern COBOL and System Evolution
The world runs on COBOL. Despite decades of predictions about its demise, COBOL remains the backbone of global commerce, government services, and financial infrastructure. An estimated 220 billion lines of COBOL code are in active production today, processing over 95% of ATM transactions, 80% of in-person transactions, and the majority of batch processing in banking, insurance, and government agencies. This chapter addresses the practical realities of maintaining, understanding, and modernizing these massive legacy systems while keeping mission-critical operations running without interruption.
Legacy system modernization is not a theoretical exercise. It is a discipline practiced daily by thousands of programmers who must balance the need for technological advancement against the risk of disrupting systems that process trillions of dollars in transactions every year. The techniques presented here are drawn from real-world modernization projects spanning decades of accumulated experience.
39.1 Understanding Legacy COBOL Systems
What Makes a System "Legacy"
The term "legacy" carries no inherent negative connotation. A legacy system is simply one that has been in production long enough to have accumulated significant business value, institutional knowledge, and technical debt. Most legacy COBOL systems share several defining characteristics.
Scale and complexity. A typical mainframe COBOL application at a large bank or insurance company comprises between 5 and 50 million lines of code spread across thousands of programs. The largest installations exceed 100 million lines. These systems were built over decades by hundreds or thousands of programmers, many of whom have long since retired.
Deep business logic embedding. Legacy COBOL programs encode business rules that have been refined through years of regulatory changes, market shifts, and customer requirements. These rules are often undocumented outside the code itself, making the source code the authoritative specification of how the business operates.
Tight coupling to infrastructure. Legacy programs are deeply intertwined with their execution environment: CICS for transaction processing, IMS or DB2 for data management, JCL for batch scheduling, VSAM for file access. Modernization cannot address the COBOL code in isolation.
Data format dependencies. Legacy systems use EBCDIC character encoding, packed decimal arithmetic (COMP-3), binary representations (COMP), and fixed-length record formats that differ fundamentally from modern computing conventions.
Batch-oriented architecture. Many legacy systems were designed around nightly batch processing cycles. Daily transactions are accumulated and processed in sequential batch runs that update master files. This architecture influences every aspect of the system design.
The Scale of Legacy COBOL
To appreciate the challenge, consider the following statistics that frame the scope of legacy COBOL in production:
- The U.S. federal government maintains approximately 10 billion lines of COBOL across agencies including the IRS, Social Security Administration, and Department of Veterans Affairs.
- Major banks typically maintain between 20 and 60 million lines of COBOL for core banking operations.
- Insurance companies routinely have 10 to 30 million lines of COBOL for policy administration, claims processing, and actuarial calculations.
- Global COBOL transaction volume exceeds 30 billion transactions per day.
These numbers explain why wholesale replacement is rarely feasible. The risk of rewriting systems that handle this volume of critical transactions is enormous, and the business knowledge embedded in the code cannot be extracted through any automated process with complete fidelity.
Common Legacy System Architectures
Legacy COBOL systems generally follow one of several architectural patterns:
Batch sequential processing. Input files are sorted and processed sequentially against master files, producing updated master files and reports. This is the oldest and most common pattern.
CICS online transaction processing. Users interact through 3270 terminal screens (BMS maps) that invoke COBOL programs through CICS. Each transaction is a short-lived program execution that reads and updates shared resources.
IMS-based hierarchical systems. Programs access data through IMS DL/I calls, navigating hierarchical database structures. The programming model is fundamentally different from relational database access.
Mixed batch and online. Most production systems combine online transaction processing during business hours with batch processing during overnight windows.
39.2 Reading and Understanding Unfamiliar COBOL Code
The first skill required for legacy maintenance is the ability to read and comprehend COBOL programs written by other programmers, often decades ago, frequently without documentation. This is a systematic discipline, not an innate talent.
Initial Assessment
When presented with an unfamiliar program, begin with a structural survey before reading any detailed logic.
*================================================================*
* PROGRAM: CLMPROC1 *
* AUTHOR: J.HENDERSON *
* DATE: 03/15/1987 *
* PURPOSE: PROCESS INSURANCE CLAIMS - INITIAL VALIDATION *
* MODIFICATIONS: *
* 04/22/1988 - R.SMITH - ADDED FLOOD COVERAGE CHECK *
* 11/03/1990 - T.JONES - Y2K DATE FIX *
* 06/15/1995 - M.WONG - NEW STATE REGULATIONS *
* 01/10/2000 - K.PATEL - EURO CURRENCY SUPPORT *
* 09/30/2008 - ??? - EMERGENCY FIX PROD ABEND *
* 03/22/2015 - D.LEE - ACA COMPLIANCE CHANGES *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. CLMPROC1.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT CLAIM-INPUT ASSIGN TO CLMSIN
FILE STATUS IS WS-CLAIM-STATUS.
SELECT POLICY-MASTER ASSIGN TO POLMSTR
FILE STATUS IS WS-POLICY-STATUS.
SELECT ERROR-REPORT ASSIGN TO ERRRPT
FILE STATUS IS WS-ERROR-STATUS.
SELECT VALID-CLAIMS ASSIGN TO VLDCLMS
FILE STATUS IS WS-VALID-STATUS.
Note what the header tells us: the program has been modified by at least six different people over 28 years. One modification (2008) has an unknown author and was an emergency fix. This modification history is itself valuable intelligence about the program's evolution and potential weak points.
Dead Code Detection
Legacy programs frequently contain dead code, which consists of paragraphs, sections, or entire data definitions that are never referenced. Dead code obscures the actual logic and must be identified before meaningful analysis can proceed.
WORKING-STORAGE SECTION.
*--- These fields are actively used ---
01 WS-CLAIM-RECORD.
05 WS-CLAIM-NUMBER PIC X(10).
05 WS-POLICY-NUMBER PIC X(12).
05 WS-CLAIM-DATE PIC 9(8).
05 WS-CLAIM-AMOUNT PIC S9(7)V99 COMP-3.
05 WS-CLAIM-TYPE PIC X(2).
*--- These fields appear to be dead code from removed feature ---
01 WS-OLD-RATING-AREA.
05 WS-OLD-RATE-TABLE PIC X(4).
05 WS-OLD-RATE-FACTOR PIC S9(3)V9(4) COMP-3.
05 WS-OLD-RATE-DATE PIC 9(6).
05 WS-OLD-RATE-CODE PIC X(2).
*--- Also potentially dead - no PERFORM references found ---
01 WS-CONVERSION-WORK.
05 WS-CONV-INPUT PIC X(80).
05 WS-CONV-OUTPUT PIC X(80).
05 WS-CONV-LENGTH PIC S9(4) COMP.
To detect dead code systematically, search for every paragraph name in the PROCEDURE DIVISION and verify that each one is referenced by at least one PERFORM, GO TO, or SECTION invocation. Similarly, search for every data name in WORKING-STORAGE and verify that each one appears in at least one procedural statement.
Control Flow Analysis
Legacy COBOL control flow can be extraordinarily difficult to follow, particularly in programs that use GO TO statements extensively. The key technique is to build a control flow graph, either on paper or using tools.
PROCEDURE DIVISION.
MAIN-PARA.
OPEN INPUT CLAIM-INPUT POLICY-MASTER
OPEN OUTPUT ERROR-REPORT VALID-CLAIMS.
READ CLAIM-INPUT
AT END GO TO EOJ-PARA.
GO TO PROCESS-CLAIM.
PROCESS-CLAIM.
IF WS-CLAIM-TYPE = 'AU'
GO TO AUTO-CLAIM
ELSE IF WS-CLAIM-TYPE = 'HO'
GO TO HOME-CLAIM
ELSE IF WS-CLAIM-TYPE = 'LF'
GO TO LIFE-CLAIM
ELSE
GO TO INVALID-TYPE.
AUTO-CLAIM.
PERFORM VALIDATE-AUTO-POLICY.
IF WS-VALID-FLAG = 'Y'
GO TO WRITE-VALID
ELSE
GO TO WRITE-ERROR.
HOME-CLAIM.
PERFORM VALIDATE-HOME-POLICY.
IF WS-VALID-FLAG = 'Y'
GO TO WRITE-VALID
ELSE
GO TO WRITE-ERROR.
LIFE-CLAIM.
PERFORM VALIDATE-LIFE-POLICY.
IF WS-VALID-FLAG = 'Y'
GO TO WRITE-VALID
ELSE
GO TO WRITE-ERROR.
INVALID-TYPE.
MOVE 'INVALID CLAIM TYPE' TO WS-ERROR-MSG.
GO TO WRITE-ERROR.
WRITE-VALID.
WRITE VALID-CLAIM-RECORD FROM WS-CLAIM-RECORD.
GO TO READ-NEXT.
WRITE-ERROR.
WRITE ERROR-RECORD FROM WS-ERROR-LINE.
ADD 1 TO WS-ERROR-COUNT.
GO TO READ-NEXT.
READ-NEXT.
READ CLAIM-INPUT
AT END GO TO EOJ-PARA.
GO TO PROCESS-CLAIM.
EOJ-PARA.
CLOSE CLAIM-INPUT POLICY-MASTER
ERROR-REPORT VALID-CLAIMS.
STOP RUN.
When tracing this code, draw arrows from each GO TO to its target. You will see that the flow forms a loop: MAIN-PARA leads to PROCESS-CLAIM, which branches to one of several claim type paragraphs, all of which converge at either WRITE-VALID or WRITE-ERROR, both of which go to READ-NEXT, which loops back to PROCESS-CLAIM or exits to EOJ-PARA. Understanding this loop structure is essential before any modification.
39.3 Documentation Archaeology
When working with undocumented or poorly documented legacy systems, programmers must reconstruct the intent of the code through a process best described as documentation archaeology. This involves examining multiple sources of evidence to understand not just what the code does but why it does it.
Sources of Evidence
Inline comments. Even sparse comments provide clues. A comment like *FIX FOR STATE REGULATION 42-B reveals that a particular code block implements a specific legal requirement, which can be researched independently.
Data names. COBOL's verbose naming convention is an advantage during archaeology. A field named WS-POLICYHOLDER-DEDUCTIBLE-AMT is self-documenting in a way that a variable named x or tmp never could be.
Copybook contents. Shared copybooks define data structures used across multiple programs. Examining which copybooks a program uses reveals its data dependencies and suggests its role in the larger system.
JCL job streams. The JCL that executes a COBOL program reveals its inputs, outputs, and position in the batch processing sequence. A program that runs after the daily transaction sort but before the master file update is clearly part of the transaction processing pipeline.
Modification history. Change management records, even informal ones in program headers, reveal the evolution of requirements. Each modification tells a story about a business need that drove a code change.
Recovering Business Rules
The most valuable archaeological work is recovering embedded business rules. Consider this code fragment from an insurance claims processing system:
CALCULATE-DEDUCTIBLE.
IF WS-POLICY-TYPE = 'HO3' OR 'HO5'
IF WS-CLAIM-DATE >= 20050829
AND WS-STATE-CODE = 'LA' OR 'MS' OR 'AL' OR 'FL'
IF WS-PERIL-CODE = 'WND' OR 'HUR'
COMPUTE WS-DEDUCTIBLE =
WS-DWELLING-COVERAGE * WS-HURR-DEDUCT-PCT
ELSE
MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
END-IF
ELSE
MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
END-IF
ELSE
MOVE WS-STD-DEDUCTIBLE TO WS-DEDUCTIBLE
END-IF.
This code tells a specific story. The date 20050829 is August 29, 2005, the date Hurricane Katrina made landfall. After Katrina, insurance companies in Gulf Coast states (Louisiana, Mississippi, Alabama, Florida) implemented percentage-based hurricane deductibles for homeowner policies (HO3 and HO5 forms), replacing the flat dollar deductible used for other perils. This single paragraph encodes both a business rule and its historical context. A replacement system must preserve this logic, and understanding the "why" behind it is essential for correct implementation.
Building a Program Inventory
For large modernization efforts, create a systematic inventory of all programs, documenting for each one:
- Program name and size (lines of code)
- Input and output files or databases
- Copybooks used
- Programs it calls (via CALL statements)
- Programs that call it
- CICS transactions associated with it (if online)
- Batch jobs that execute it (from JCL)
- Last modification date and author
- Business function performed
This inventory becomes the foundation for all modernization planning.
39.4 Common Legacy Patterns and Anti-Patterns
Legacy COBOL systems exhibit recurring patterns that make maintenance difficult. Recognizing these patterns is the first step toward addressing them.
Pervasive GO TO Usage
The most common legacy pattern is the use of GO TO as the primary control flow mechanism. Before the structured programming movement of the late 1970s and early 1980s, GO TO was the standard way to direct program flow in COBOL.
*================================================================*
* LEGACY PATTERN: GO TO-based control flow *
* This is typical of code written before 1985 *
*================================================================*
PROCEDURE DIVISION.
000-START.
OPEN INPUT TRANS-FILE MASTER-FILE.
OPEN OUTPUT NEW-MASTER REPORT-FILE.
READ MASTER-FILE INTO WS-MASTER-REC
AT END MOVE HIGH-VALUES TO WS-MASTER-KEY
END-READ.
READ TRANS-FILE INTO WS-TRANS-REC
AT END MOVE HIGH-VALUES TO WS-TRANS-KEY
END-READ.
GO TO 100-COMPARE.
100-COMPARE.
IF WS-MASTER-KEY = HIGH-VALUES
AND WS-TRANS-KEY = HIGH-VALUES
GO TO 900-EOJ.
IF WS-TRANS-KEY < WS-MASTER-KEY
GO TO 200-NEW-ACCOUNT.
IF WS-TRANS-KEY = WS-MASTER-KEY
GO TO 300-UPDATE.
IF WS-TRANS-KEY > WS-MASTER-KEY
GO TO 400-WRITE-MASTER.
200-NEW-ACCOUNT.
MOVE WS-TRANS-REC TO WS-MASTER-REC.
MOVE 'N' TO WS-NEW-ACCT-FLAG.
PERFORM 350-APPLY-TRANS.
GO TO 500-READ-TRANS.
300-UPDATE.
PERFORM 350-APPLY-TRANS.
GO TO 500-READ-TRANS.
350-APPLY-TRANS.
IF WS-TRANS-TYPE = 'D'
ADD WS-TRANS-AMOUNT TO WS-MASTER-BALANCE
ELSE IF WS-TRANS-TYPE = 'W'
SUBTRACT WS-TRANS-AMOUNT FROM WS-MASTER-BALANCE
IF WS-MASTER-BALANCE < ZERO
MOVE 'O' TO WS-OVERDRAFT-FLAG
GO TO 360-OVERDRAFT
END-IF
ELSE IF WS-TRANS-TYPE = 'I'
COMPUTE WS-MASTER-BALANCE = WS-MASTER-BALANCE *
(1 + WS-INTEREST-RATE)
ELSE
MOVE 'INVALID TRANS TYPE' TO WS-ERROR-MSG
GO TO 370-ERROR.
GO TO 380-TRANS-DONE.
360-OVERDRAFT.
ADD 1 TO WS-OVERDRAFT-COUNT.
COMPUTE WS-OVERDRAFT-FEE = 35.00.
SUBTRACT WS-OVERDRAFT-FEE FROM WS-MASTER-BALANCE.
WRITE REPORT-REC FROM WS-OVERDRAFT-LINE.
GO TO 380-TRANS-DONE.
370-ERROR.
ADD 1 TO WS-ERROR-COUNT.
WRITE REPORT-REC FROM WS-ERROR-LINE.
GO TO 380-TRANS-DONE.
380-TRANS-DONE.
EXIT.
400-WRITE-MASTER.
WRITE NEW-MASTER-REC FROM WS-MASTER-REC.
READ MASTER-FILE INTO WS-MASTER-REC
AT END MOVE HIGH-VALUES TO WS-MASTER-KEY.
GO TO 100-COMPARE.
500-READ-TRANS.
READ TRANS-FILE INTO WS-TRANS-REC
AT END MOVE HIGH-VALUES TO WS-TRANS-KEY.
GO TO 100-COMPARE.
900-EOJ.
CLOSE TRANS-FILE MASTER-FILE
NEW-MASTER REPORT-FILE.
STOP RUN.
This code implements a classic sequential file matching algorithm, but the GO TO statements make it difficult to reason about the flow. The program jumps between paragraphs in a non-linear fashion, and understanding the complete processing cycle for a single transaction requires tracing through multiple jumps.
ALTER Statement
The ALTER statement is one of the most dangerous features ever included in a programming language. It dynamically changes the target of a GO TO statement at runtime:
*================================================================*
* LEGACY ANTI-PATTERN: ALTER statement *
* This makes control flow unknowable from static analysis *
*================================================================*
PROCEDURE DIVISION.
START-PARA.
ALTER DISPATCH-PARA TO PROCEED TO FIRST-TIME-INIT.
GO TO DISPATCH-PARA.
DISPATCH-PARA.
GO TO NORMAL-PROCESS.
FIRST-TIME-INIT.
PERFORM LOAD-TABLES.
PERFORM OPEN-FILES.
ALTER DISPATCH-PARA TO PROCEED TO NORMAL-PROCESS.
GO TO DISPATCH-PARA.
NORMAL-PROCESS.
PERFORM READ-INPUT.
IF WS-END-OF-FILE = 'Y'
ALTER DISPATCH-PARA TO PROCEED TO CLEANUP
GO TO DISPATCH-PARA
END-IF.
PERFORM PROCESS-RECORD.
GO TO DISPATCH-PARA.
CLEANUP.
PERFORM CLOSE-FILES.
STOP RUN.
The ALTER statement makes the GO TO at DISPATCH-PARA point to different locations depending on the runtime state. This is self-modifying code in effect, and it is nearly impossible to analyze statically. Any program containing ALTER statements should be refactored as a high priority.
Deeply Nested IF Statements
Before the EVALUATE statement was introduced in COBOL-85, complex conditional logic was expressed through deeply nested IF statements:
*================================================================*
* LEGACY ANTI-PATTERN: Deeply nested IFs *
* Common before EVALUATE was available *
*================================================================*
DETERMINE-PREMIUM.
IF WS-POLICY-TYPE = 'AUTO'
IF WS-DRIVER-AGE < 25
IF WS-GENDER = 'M'
IF WS-ACCIDENTS > 0
MOVE 2850.00 TO WS-BASE-PREMIUM
ELSE
IF WS-TICKETS > 2
MOVE 2200.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1800.00 TO WS-BASE-PREMIUM
END-IF
END-IF
ELSE
IF WS-ACCIDENTS > 0
MOVE 2400.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1500.00 TO WS-BASE-PREMIUM
END-IF
END-IF
ELSE IF WS-DRIVER-AGE < 65
IF WS-GENDER = 'M'
IF WS-ACCIDENTS > 0
MOVE 1800.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1200.00 TO WS-BASE-PREMIUM
END-IF
ELSE
IF WS-ACCIDENTS > 0
MOVE 1600.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1000.00 TO WS-BASE-PREMIUM
END-IF
END-IF
ELSE
IF WS-ACCIDENTS > 0
MOVE 2100.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1400.00 TO WS-BASE-PREMIUM
END-IF
END-IF
ELSE IF WS-POLICY-TYPE = 'HOME'
IF WS-CONSTRUCTION = 'FRAME'
IF WS-ROOF-AGE > 15
MOVE 2200.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1600.00 TO WS-BASE-PREMIUM
END-IF
ELSE
IF WS-ROOF-AGE > 20
MOVE 1800.00 TO WS-BASE-PREMIUM
ELSE
MOVE 1200.00 TO WS-BASE-PREMIUM
END-IF
END-IF
END-IF.
This nested structure is difficult to read, difficult to modify (adding a new category requires careful attention to bracket matching), and prone to logic errors. The END-IF scope terminators were not available in COBOL-74, making such code even harder to follow in older programs that used period-terminated IF statements.
Monolithic Programs
Some legacy programs contain thousands or even tens of thousands of lines in a single program, with little or no modular structure:
*================================================================*
* LEGACY ANTI-PATTERN: Monolithic program *
* This program handles ALL claims processing in one unit *
* Actual programs of 15,000+ lines are not uncommon *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. CLMSMSTR.
* THIS PROGRAM:
* - VALIDATES CLAIMS
* - CHECKS POLICY COVERAGE
* - CALCULATES DEDUCTIBLES
* - APPLIES STATE REGULATIONS
* - DETERMINES PAYMENT AMOUNTS
* - GENERATES CHECKS
* - PRODUCES REPORTS
* - UPDATES CLAIM HISTORY
* - SENDS NOTIFICATIONS
* ALL IN ONE 18,000 LINE PROGRAM
Such programs violate every principle of modular design. They are difficult to understand, impossible to test in isolation, and dangerous to modify because a change in one section may have unintended effects on distant sections that share data through WORKING-STORAGE.
39.5 Refactoring Techniques
Refactoring is the process of restructuring code without changing its external behavior. For legacy COBOL, refactoring typically proceeds through several well-defined transformations.
Structured Programming Transformation
The most fundamental refactoring replaces GO TO-based control flow with structured PERFORM statements. Here is the GO TO-based bank transaction program from Section 39.4, refactored into structured form:
*================================================================*
* REFACTORED: Structured version of sequential update program *
* GO TO statements eliminated, logic preserved exactly *
*================================================================*
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-FILES
UNTIL WS-MASTER-KEY = HIGH-VALUES
AND WS-TRANS-KEY = HIGH-VALUES
PERFORM 9000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT TRANS-FILE MASTER-FILE
OPEN OUTPUT NEW-MASTER REPORT-FILE
PERFORM 1100-READ-MASTER
PERFORM 1200-READ-TRANS
.
1100-READ-MASTER.
READ MASTER-FILE INTO WS-MASTER-REC
AT END MOVE HIGH-VALUES TO WS-MASTER-KEY
END-READ
.
1200-READ-TRANS.
READ TRANS-FILE INTO WS-TRANS-REC
AT END MOVE HIGH-VALUES TO WS-TRANS-KEY
END-READ
.
2000-PROCESS-FILES.
EVALUATE TRUE
WHEN WS-TRANS-KEY < WS-MASTER-KEY
PERFORM 3000-ADD-NEW-ACCOUNT
WHEN WS-TRANS-KEY = WS-MASTER-KEY
PERFORM 4000-UPDATE-ACCOUNT
WHEN WS-TRANS-KEY > WS-MASTER-KEY
PERFORM 5000-WRITE-UNCHANGED-MASTER
END-EVALUATE
.
3000-ADD-NEW-ACCOUNT.
MOVE WS-TRANS-REC TO WS-MASTER-REC
MOVE 'N' TO WS-NEW-ACCT-FLAG
PERFORM 4100-APPLY-TRANSACTION
PERFORM 1200-READ-TRANS
.
4000-UPDATE-ACCOUNT.
PERFORM 4100-APPLY-TRANSACTION
PERFORM 1200-READ-TRANS
.
4100-APPLY-TRANSACTION.
EVALUATE WS-TRANS-TYPE
WHEN 'D'
ADD WS-TRANS-AMOUNT TO WS-MASTER-BALANCE
WHEN 'W'
PERFORM 4200-PROCESS-WITHDRAWAL
WHEN 'I'
COMPUTE WS-MASTER-BALANCE = WS-MASTER-BALANCE *
(1 + WS-INTEREST-RATE)
WHEN OTHER
PERFORM 4300-HANDLE-INVALID-TRANS
END-EVALUATE
.
4200-PROCESS-WITHDRAWAL.
SUBTRACT WS-TRANS-AMOUNT FROM WS-MASTER-BALANCE
IF WS-MASTER-BALANCE < ZERO
ADD 1 TO WS-OVERDRAFT-COUNT
COMPUTE WS-OVERDRAFT-FEE = 35.00
SUBTRACT WS-OVERDRAFT-FEE FROM WS-MASTER-BALANCE
MOVE 'O' TO WS-OVERDRAFT-FLAG
WRITE REPORT-REC FROM WS-OVERDRAFT-LINE
END-IF
.
4300-HANDLE-INVALID-TRANS.
MOVE 'INVALID TRANS TYPE' TO WS-ERROR-MSG
ADD 1 TO WS-ERROR-COUNT
WRITE REPORT-REC FROM WS-ERROR-LINE
.
5000-WRITE-UNCHANGED-MASTER.
WRITE NEW-MASTER-REC FROM WS-MASTER-REC
PERFORM 1100-READ-MASTER
.
9000-TERMINATE.
CLOSE TRANS-FILE MASTER-FILE
NEW-MASTER REPORT-FILE
.
Notice the key changes in this refactoring:
- A clear main loop replaces the implicit loop formed by GO TO chains. The PERFORM...UNTIL in 0000-MAIN-PROCESS makes the iteration structure explicit.
- EVALUATE replaces cascading IF/GO TO. The transaction type dispatch in 4100-APPLY-TRANSACTION and the file comparison in 2000-PROCESS-FILES use EVALUATE for clear, readable branching.
- Each paragraph has one entry and one exit. No paragraph transfers control to another via GO TO. Every paragraph returns to its caller naturally.
- Hierarchical numbering makes the call structure visible. 4100 and 4200 are called from within the 4000-level processing, indicating their subordinate role.
- The program reads top-down. You can understand the overall structure from 0000-MAIN-PROCESS without reading any other paragraph.
Paragraph Extraction
Large inline code blocks should be extracted into named paragraphs. This transformation improves readability and enables reuse. As discussed in Chapter 21 on coding standards, well-named paragraphs serve as executable documentation.
Before extraction:
PROCESS-CLAIM.
* --- 45 lines of validation logic ---
IF WS-CLAIM-AMOUNT > ZERO
IF WS-CLAIM-AMOUNT <= WS-POLICY-LIMIT
IF WS-CLAIM-DATE >= WS-POLICY-EFF-DATE
IF WS-CLAIM-DATE <= WS-POLICY-EXP-DATE
MOVE 'Y' TO WS-CLAIM-VALID
ELSE
MOVE 'CLAIM AFTER EXPIRY'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
ELSE
MOVE 'CLAIM BEFORE EFFECTIVE'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
ELSE
MOVE 'EXCEEDS POLICY LIMIT'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
ELSE
MOVE 'ZERO OR NEGATIVE AMOUNT'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
* --- 30 more lines of processing ---
.
After extraction:
PROCESS-CLAIM.
PERFORM 3100-VALIDATE-CLAIM-AMOUNT
PERFORM 3200-VALIDATE-CLAIM-DATES
IF WS-CLAIM-VALID = 'Y'
PERFORM 3300-CALCULATE-PAYMENT
PERFORM 3400-UPDATE-CLAIM-HISTORY
ELSE
PERFORM 3500-WRITE-REJECTION
END-IF
.
3100-VALIDATE-CLAIM-AMOUNT.
MOVE 'Y' TO WS-CLAIM-VALID
IF WS-CLAIM-AMOUNT NOT > ZERO
MOVE 'ZERO OR NEGATIVE AMOUNT'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
ELSE IF WS-CLAIM-AMOUNT > WS-POLICY-LIMIT
MOVE 'EXCEEDS POLICY LIMIT'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
.
3200-VALIDATE-CLAIM-DATES.
IF WS-CLAIM-VALID = 'Y'
IF WS-CLAIM-DATE < WS-POLICY-EFF-DATE
MOVE 'CLAIM BEFORE EFFECTIVE'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
ELSE IF WS-CLAIM-DATE > WS-POLICY-EXP-DATE
MOVE 'CLAIM AFTER EXPIRY'
TO WS-REJECT-REASON
MOVE 'N' TO WS-CLAIM-VALID
END-IF
END-IF
.
The extracted version is longer in total lines but dramatically easier to understand. Each validation step is isolated, named, and independently testable. The PROCESS-CLAIM paragraph now reads as a high-level description of the claims processing workflow.
EVALUATE Replacement for Nested IFs
The deeply nested premium calculation from Section 39.4 can be refactored using EVALUATE with multiple WHEN conditions and a table-driven approach:
*================================================================*
* REFACTORED: Table-driven premium calculation *
* Replaces deeply nested IF structure *
*================================================================*
WORKING-STORAGE SECTION.
01 WS-RATE-TABLE.
05 WS-RATE-ENTRY OCCURS 12 TIMES.
10 WS-RT-POLICY-TYPE PIC X(4).
10 WS-RT-AGE-MIN PIC 9(3).
10 WS-RT-AGE-MAX PIC 9(3).
10 WS-RT-RISK-FACTOR PIC X(1).
10 WS-RT-BASE-PREMIUM PIC S9(7)V99 COMP-3.
PROCEDURE DIVISION.
DETERMINE-PREMIUM.
MOVE ZERO TO WS-BASE-PREMIUM
PERFORM VARYING WS-RATE-IDX FROM 1 BY 1
UNTIL WS-RATE-IDX > 12
OR WS-BASE-PREMIUM > ZERO
IF WS-POLICY-TYPE = WS-RT-POLICY-TYPE(WS-RATE-IDX)
AND WS-DRIVER-AGE >= WS-RT-AGE-MIN(WS-RATE-IDX)
AND WS-DRIVER-AGE <= WS-RT-AGE-MAX(WS-RATE-IDX)
AND WS-RISK-FACTOR = WS-RT-RISK-FACTOR(WS-RATE-IDX)
MOVE WS-RT-BASE-PREMIUM(WS-RATE-IDX)
TO WS-BASE-PREMIUM
END-IF
END-PERFORM
IF WS-BASE-PREMIUM = ZERO
MOVE 'RATE NOT FOUND' TO WS-ERROR-MSG
PERFORM 9100-HANDLE-RATING-ERROR
END-IF
.
LOAD-RATE-TABLE.
* Load rates from file or database instead of hardcoding
READ RATE-FILE INTO WS-RATE-TABLE
AT END
MOVE 'RATE TABLE EMPTY' TO WS-ERROR-MSG
PERFORM 9100-HANDLE-RATING-ERROR
END-READ
.
This refactoring not only eliminates the nested IFs but externalizes the premium rates from code into data. Adding a new rate category now requires only a new row in the rate table, not a code change. This is a fundamental improvement in maintainability.
39.6 Modernization Strategies
Modernization is a spectrum of approaches, each with different levels of risk, cost, and transformation depth. The choice of strategy depends on the organization's goals, budget, risk tolerance, and timeline.
The Five Rs of Modernization
Rehost (Lift and Shift). Move the COBOL application from the mainframe to a different platform (Linux, cloud) without changing the code. Products like Micro Focus Enterprise Server and NTT DATA UniKix enable COBOL programs to run on distributed platforms. This approach is the lowest risk and lowest cost but provides the fewest benefits. The application still behaves exactly as it did on the mainframe, with all its architectural limitations intact.
Replatform. Move the application to a new platform and replace some infrastructure components while keeping the business logic intact. For example, replace VSAM files with a relational database, replace CICS screens with web interfaces, or replace JCL batch scheduling with a modern scheduler. The COBOL code is modified to work with the new infrastructure but the core logic is preserved.
Refactor. Restructure the COBOL code to improve its internal design without changing its external behavior. This includes the techniques discussed in Section 39.5: eliminating GO TO statements, extracting paragraphs, replacing nested IFs, and decomposing monolithic programs into modules. Refactoring can be performed on the existing platform or combined with rehosting.
Replace. Discard the legacy application entirely and implement its functionality in a new system, typically using modern languages and architectures. This is the highest-risk option because it requires complete understanding and reimplementation of all business logic. Many replacement projects have failed spectacularly because the legacy system's behavior was more complex than anticipated.
Wrap. Encapsulate the legacy application behind modern interfaces (REST APIs, message queues, web services) without modifying the underlying code. The legacy system becomes a black-box service that can be consumed by modern applications. This approach is often the most pragmatic first step because it enables gradual modernization while preserving the proven business logic.
Risk Assessment
Each strategy carries different risks:
| Strategy | Risk Level | Cost | Timeline | Business Logic Preservation |
|---|---|---|---|---|
| Rehost | Low | Low | Months | Complete |
| Replatform | Medium | Medium | 6-18 months | High |
| Refactor | Medium | Medium | 6-24 months | Complete |
| Replace | Very High | Very High | 2-5+ years | Must be verified |
| Wrap | Low | Low-Medium | Weeks-Months | Complete |
The wrapping approach deserves special attention because it is the most commonly successful first step and provides the foundation for incremental modernization.
Wrapping Legacy COBOL with Modern Interfaces
Here is an example of wrapping a legacy COBOL claims processing program with a CICS web service interface:
*================================================================*
* PROGRAM: CLMWSVC *
* PURPOSE: Web service wrapper for legacy claims validation *
* This program receives JSON input via CICS WEB services, *
* calls the existing CLMPROC1 program, and returns JSON output *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. CLMWSVC.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-REQUEST-BODY PIC X(4096).
01 WS-RESPONSE-BODY PIC X(4096).
01 WS-REQUEST-LENGTH PIC S9(8) COMP.
01 WS-RESPONSE-LENGTH PIC S9(8) COMP.
01 WS-HTTP-STATUS PIC S9(4) COMP.
01 WS-JSON-CLAIM.
05 WS-JC-CLAIM-NUMBER PIC X(10).
05 WS-JC-POLICY-NUMBER PIC X(12).
05 WS-JC-CLAIM-DATE PIC X(10).
05 WS-JC-CLAIM-AMOUNT PIC X(12).
05 WS-JC-CLAIM-TYPE PIC X(2).
01 WS-LEGACY-CLAIM-REC.
COPY CLMREC01.
01 WS-LEGACY-RESULT.
COPY CLMRSLT1.
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
PERFORM 1000-RECEIVE-REQUEST
PERFORM 2000-PARSE-JSON-INPUT
PERFORM 3000-MAP-TO-LEGACY-FORMAT
PERFORM 4000-CALL-LEGACY-PROGRAM
PERFORM 5000-MAP-FROM-LEGACY-FORMAT
PERFORM 6000-BUILD-JSON-RESPONSE
PERFORM 7000-SEND-RESPONSE
EXEC CICS RETURN END-EXEC
.
1000-RECEIVE-REQUEST.
EXEC CICS WEB RECEIVE
INTO(WS-REQUEST-BODY)
LENGTH(WS-REQUEST-LENGTH)
MAXLENGTH(4096)
END-EXEC
.
2000-PARSE-JSON-INPUT.
EXEC CICS PUT CONTAINER('DFHJSON-DATA')
FROM(WS-REQUEST-BODY)
FLENGTH(WS-REQUEST-LENGTH)
CHANNEL('CLMCHANL')
END-EXEC
EXEC CICS LINK PROGRAM('DFHJSON')
CHANNEL('CLMCHANL')
END-EXEC
EXEC CICS GET CONTAINER('DFHJSON-FIELD')
INTO(WS-JSON-CLAIM)
CHANNEL('CLMCHANL')
END-EXEC
.
3000-MAP-TO-LEGACY-FORMAT.
* Convert modern formats to legacy expectations
MOVE WS-JC-CLAIM-NUMBER TO CLM-CLAIM-NUMBER
MOVE WS-JC-POLICY-NUMBER TO CLM-POLICY-NUMBER
* Convert ISO date to legacy YYYYMMDD
PERFORM 3100-CONVERT-DATE
* Convert decimal string to COMP-3
PERFORM 3200-CONVERT-AMOUNT
MOVE WS-JC-CLAIM-TYPE TO CLM-CLAIM-TYPE
.
3100-CONVERT-DATE.
UNSTRING WS-JC-CLAIM-DATE DELIMITED BY '-'
INTO WS-DATE-YYYY WS-DATE-MM WS-DATE-DD
END-UNSTRING
STRING WS-DATE-YYYY WS-DATE-MM WS-DATE-DD
DELIMITED SIZE INTO CLM-CLAIM-DATE
END-STRING
.
3200-CONVERT-AMOUNT.
COMPUTE CLM-CLAIM-AMOUNT =
FUNCTION NUMVAL(WS-JC-CLAIM-AMOUNT)
.
4000-CALL-LEGACY-PROGRAM.
CALL 'CLMPROC1' USING WS-LEGACY-CLAIM-REC
WS-LEGACY-RESULT
.
5000-MAP-FROM-LEGACY-FORMAT.
* Convert legacy result fields to modern formats
MOVE RSL-STATUS-CODE TO WS-JR-STATUS
MOVE RSL-STATUS-MSG TO WS-JR-MESSAGE
MOVE RSL-PAYMENT-AMT TO WS-JR-AMOUNT
.
6000-BUILD-JSON-RESPONSE.
STRING '{"status":"' WS-JR-STATUS '",'
'"message":"' WS-JR-MESSAGE '",'
'"paymentAmount":' WS-JR-AMOUNT '}'
DELIMITED SIZE INTO WS-RESPONSE-BODY
END-STRING
MOVE FUNCTION LENGTH(
FUNCTION TRIM(WS-RESPONSE-BODY))
TO WS-RESPONSE-LENGTH
.
7000-SEND-RESPONSE.
MOVE 200 TO WS-HTTP-STATUS
EXEC CICS WEB SEND
FROM(WS-RESPONSE-BODY)
LENGTH(WS-RESPONSE-LENGTH)
MEDIATYPE('application/json')
STATUSCODE(WS-HTTP-STATUS)
STATUSTEXT('OK')
END-EXEC
.
This wrapper program makes the legacy CLMPROC1 program accessible as a RESTful web service without modifying the legacy code at all. Modern front-end applications, microservices, and mobile apps can now interact with the legacy business logic through standard HTTP/JSON protocols.
39.7 The Strangler Fig Pattern for COBOL Systems
The Strangler Fig pattern, named by Martin Fowler after tropical strangler fig trees that gradually envelop and replace their host trees, is the most successful strategy for incrementally replacing legacy systems. Rather than attempting a risky big-bang replacement, the legacy system is gradually surrounded by new functionality until the old system can be decommissioned.
Implementing the Pattern
The strategy proceeds in phases:
Phase 1: Intercept. Place an intermediary layer (API gateway, message broker, or routing program) between consumers and the legacy system. All requests flow through this intermediary, which initially routes everything to the legacy system unchanged.
Phase 2: Implement. Build new implementations of specific functions using modern technology. These new implementations handle the same inputs and produce the same outputs as the corresponding legacy functions.
Phase 3: Redirect. Configure the intermediary to route specific functions to the new implementation instead of the legacy system. Run both in parallel to verify identical results.
Phase 4: Retire. Once the new implementation is verified, remove the corresponding legacy code. Repeat phases 2-4 for the next function.
COBOL-Specific Considerations
For COBOL systems, the strangler pattern has several specific implementation considerations:
Transaction boundaries. CICS transactions are natural units for strangling. Each transaction (identified by a four-character transaction ID) can be redirected independently from the legacy COBOL program to a new implementation.
Batch job decomposition. Monolithic batch jobs must be decomposed into smaller units before individual functions can be strangled. This often requires refactoring the batch program first.
Data coupling. Legacy programs typically share data through files (VSAM, sequential) and databases (DB2, IMS). The new implementations must maintain compatibility with the shared data formats throughout the transition period. Dual-write strategies, where both old and new systems update the same data stores, are common during the transition.
*================================================================*
* PROGRAM: CLMROUTE *
* PURPOSE: Strangler fig router for claims processing *
* Routes claim types to legacy or new implementation *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. CLMROUTE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ROUTING-TABLE.
05 FILLER PIC X(6) VALUE 'AUNEW '.
05 FILLER PIC X(6) VALUE 'HOLEG '.
05 FILLER PIC X(6) VALUE 'LFLEG '.
05 FILLER PIC X(6) VALUE 'FLNEW '.
05 FILLER PIC X(6) VALUE 'MDLEG '.
01 WS-ROUTE-ENTRIES REDEFINES WS-ROUTING-TABLE.
05 WS-ROUTE-ENTRY OCCURS 5 TIMES.
10 WS-ROUTE-TYPE PIC X(2).
10 WS-ROUTE-TARGET PIC X(3).
10 FILLER PIC X(1).
01 WS-ROUTE-FOUND PIC X VALUE 'N'.
01 WS-ROUTE-IDX PIC 9(2) COMP.
PROCEDURE DIVISION.
0000-ROUTE-CLAIM.
MOVE 'N' TO WS-ROUTE-FOUND
PERFORM VARYING WS-ROUTE-IDX FROM 1 BY 1
UNTIL WS-ROUTE-IDX > 5
OR WS-ROUTE-FOUND = 'Y'
IF WS-CLAIM-TYPE = WS-ROUTE-TYPE(WS-ROUTE-IDX)
MOVE 'Y' TO WS-ROUTE-FOUND
EVALUATE WS-ROUTE-TARGET(WS-ROUTE-IDX)
WHEN 'NEW'
PERFORM 2000-ROUTE-TO-NEW-SYSTEM
WHEN 'LEG'
PERFORM 3000-ROUTE-TO-LEGACY
END-EVALUATE
END-IF
END-PERFORM
IF WS-ROUTE-FOUND = 'N'
PERFORM 3000-ROUTE-TO-LEGACY
END-IF
.
2000-ROUTE-TO-NEW-SYSTEM.
* Route to new microservice via MQ or HTTP
EXEC CICS LINK PROGRAM('CLMNEWGW')
COMMAREA(WS-CLAIM-DATA)
LENGTH(LENGTH OF WS-CLAIM-DATA)
END-EXEC
.
3000-ROUTE-TO-LEGACY.
* Route to existing legacy program
EXEC CICS LINK PROGRAM('CLMPROC1')
COMMAREA(WS-CLAIM-DATA)
LENGTH(LENGTH OF WS-CLAIM-DATA)
END-EXEC
.
In this example, auto (AU) and flood (FL) claims have been migrated to the new system, while homeowner (HO), life (LF), and medical (MD) claims still run through the legacy program. The routing table can be updated to redirect additional claim types as their new implementations are completed and verified.
39.8 Data Modernization
Data modernization is often the most challenging aspect of legacy system transformation because data formats are deeply embedded in every program and copybook throughout the system.
EBCDIC to Unicode Conversion
Mainframe systems use EBCDIC (Extended Binary Coded Decimal Interchange Code) character encoding, while modern systems use ASCII or Unicode. The conversion is not a simple character-by-character mapping because EBCDIC and ASCII have different collating sequences and different representations for special characters.
*================================================================*
* Data conversion considerations *
* EBCDIC to ASCII/Unicode mapping issues *
*================================================================*
WORKING-STORAGE SECTION.
* EBCDIC lowercase 'a' = X'81', ASCII 'a' = X'61'
* EBCDIC uppercase 'A' = X'C1', ASCII 'A' = X'41'
* EBCDIC digits are X'F0'-X'F9'
* EBCDIC space is X'40', ASCII space is X'20'
* Signed zoned decimal has sign in last byte:
* +123 in EBCDIC = X'F1F2C3' (C = positive)
* -123 in EBCDIC = X'F1F2D3' (D = negative)
* These have NO ASCII equivalent
01 WS-EBCDIC-AMOUNT PIC S9(5)V99.
* Contains zone bits that encode the sign
* Direct byte transfer to ASCII system will corrupt the value
01 WS-COMP3-AMOUNT PIC S9(7)V99 COMP-3.
* Packed decimal: +1234567.89 = X'123456789C'
* 5 bytes store 9 digits plus sign
* No equivalent in ASCII systems
01 WS-COMP-FIELD PIC S9(8) COMP.
* Binary: mainframe uses big-endian byte order
* Intel/AMD systems use little-endian
* Same bit pattern means different numbers on each platform
Packed Decimal to Standard Format Conversion
COMP-3 (packed decimal) is the most common numeric format in COBOL financial applications because it provides exact decimal arithmetic without floating-point rounding errors. During modernization, packed decimal values must be converted to formats supported by the target platform while preserving exact precision.
*================================================================*
* PROGRAM: DATACONV *
* PURPOSE: Convert legacy data formats for modernization *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. DATACONV.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LEGACY-POLICY-REC.
* Legacy fixed-format record from VSAM file
05 LP-POLICY-NUM PIC X(12).
05 LP-HOLDER-NAME PIC X(30).
05 LP-EFF-DATE PIC 9(8).
05 LP-EXP-DATE PIC 9(8).
05 LP-PREMIUM-AMT PIC S9(7)V99 COMP-3.
05 LP-DEDUCTIBLE PIC S9(5)V99 COMP-3.
05 LP-COVERAGE-LIMIT PIC S9(9)V99 COMP-3.
05 LP-STATE-CODE PIC X(2).
05 LP-AGENT-CODE PIC X(6).
05 LP-STATUS-FLAG PIC X(1).
05 FILLER PIC X(20).
* Total record length: 106 bytes
01 WS-MODERN-POLICY-REC.
* Modern delimited format for database load
05 MP-POLICY-NUM PIC X(12).
05 MP-DELIMITER-1 PIC X VALUE '|'.
05 MP-HOLDER-NAME PIC X(30).
05 MP-DELIMITER-2 PIC X VALUE '|'.
05 MP-EFF-DATE PIC X(10).
05 MP-DELIMITER-3 PIC X VALUE '|'.
05 MP-EXP-DATE PIC X(10).
05 MP-DELIMITER-4 PIC X VALUE '|'.
05 MP-PREMIUM-AMT PIC X(12).
05 MP-DELIMITER-5 PIC X VALUE '|'.
05 MP-DEDUCTIBLE PIC X(10).
05 MP-DELIMITER-6 PIC X VALUE '|'.
05 MP-COVERAGE-LIMIT PIC X(14).
05 MP-DELIMITER-7 PIC X VALUE '|'.
05 MP-STATE-CODE PIC X(2).
05 MP-DELIMITER-8 PIC X VALUE '|'.
05 MP-AGENT-CODE PIC X(6).
05 MP-DELIMITER-9 PIC X VALUE '|'.
05 MP-STATUS-FLAG PIC X(1).
01 WS-DATE-WORK.
05 WS-DW-YYYY PIC X(4).
05 WS-DW-MM PIC X(2).
05 WS-DW-DD PIC X(2).
01 WS-DATE-ISO.
05 WS-DI-YYYY PIC X(4).
05 WS-DI-DASH1 PIC X VALUE '-'.
05 WS-DI-MM PIC X(2).
05 WS-DI-DASH2 PIC X VALUE '-'.
05 WS-DI-DD PIC X(2).
PROCEDURE DIVISION.
0000-CONVERT-RECORDS.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-RECORDS
UNTIL WS-EOF-FLAG = 'Y'
PERFORM 9000-TERMINATE
STOP RUN
.
2000-PROCESS-RECORDS.
READ LEGACY-FILE INTO WS-LEGACY-POLICY-REC
AT END
MOVE 'Y' TO WS-EOF-FLAG
EXIT PARAGRAPH
END-READ
PERFORM 2100-CONVERT-FIELDS
WRITE MODERN-RECORD FROM WS-MODERN-POLICY-REC
ADD 1 TO WS-RECORDS-CONVERTED
.
2100-CONVERT-FIELDS.
* Direct moves for character fields
MOVE LP-POLICY-NUM TO MP-POLICY-NUM
MOVE LP-HOLDER-NAME TO MP-HOLDER-NAME
MOVE LP-STATE-CODE TO MP-STATE-CODE
MOVE LP-AGENT-CODE TO MP-AGENT-CODE
MOVE LP-STATUS-FLAG TO MP-STATUS-FLAG
* Convert dates from YYYYMMDD to YYYY-MM-DD
MOVE LP-EFF-DATE TO WS-DATE-WORK
PERFORM 2200-FORMAT-ISO-DATE
MOVE WS-DATE-ISO TO MP-EFF-DATE
MOVE LP-EXP-DATE TO WS-DATE-WORK
PERFORM 2200-FORMAT-ISO-DATE
MOVE WS-DATE-ISO TO MP-EXP-DATE
* Convert COMP-3 to display format
* COBOL handles this automatically via MOVE
MOVE LP-PREMIUM-AMT TO MP-PREMIUM-AMT
MOVE LP-DEDUCTIBLE TO MP-DEDUCTIBLE
MOVE LP-COVERAGE-LIMIT TO MP-COVERAGE-LIMIT
.
2200-FORMAT-ISO-DATE.
MOVE WS-DW-YYYY TO WS-DI-YYYY
MOVE WS-DW-MM TO WS-DI-MM
MOVE WS-DW-DD TO WS-DI-DD
.
Flat Files to Database Migration
Moving from VSAM or sequential flat files to relational databases is a common modernization step. The key challenge is that COBOL programs using flat files often rely on the physical ordering of records, which relational databases do not guarantee.
*================================================================*
* Before: VSAM KSDS access *
*================================================================*
READ-POLICY-VSAM.
MOVE WS-POLICY-KEY TO POLICY-RECORD-KEY.
READ POLICY-FILE INTO WS-POLICY-RECORD
INVALID KEY
MOVE 'POLICY NOT FOUND' TO WS-ERROR-MSG
PERFORM 9100-HANDLE-NOT-FOUND
END-READ.
*================================================================*
* After: DB2 SQL access (same logic, different data source) *
*================================================================*
READ-POLICY-DB2.
EXEC SQL
SELECT POLICY_NUM, HOLDER_NAME,
EFF_DATE, EXP_DATE,
PREMIUM_AMT, DEDUCTIBLE,
COVERAGE_LIMIT, STATE_CODE,
AGENT_CODE, STATUS_FLAG
INTO :WS-POL-NUM, :WS-HOLDER-NAME,
:WS-EFF-DATE, :WS-EXP-DATE,
:WS-PREMIUM, :WS-DEDUCTIBLE,
:WS-COV-LIMIT, :WS-STATE,
:WS-AGENT, :WS-STATUS
FROM POLICY_MASTER
WHERE POLICY_NUM = :WS-POLICY-KEY
END-EXEC
EVALUATE SQLCODE
WHEN 0
CONTINUE
WHEN +100
MOVE 'POLICY NOT FOUND' TO WS-ERROR-MSG
PERFORM 9100-HANDLE-NOT-FOUND
WHEN OTHER
MOVE SQLCODE TO WS-SQL-CODE-DISPLAY
STRING 'DB2 ERROR: ' WS-SQL-CODE-DISPLAY
DELIMITED SIZE INTO WS-ERROR-MSG
PERFORM 9200-HANDLE-DB-ERROR
END-EVALUATE
.
39.9 Code Analysis Tools
Manual analysis of millions of lines of code is impractical. Several commercial and open-source tools exist to automate the analysis of legacy COBOL systems.
IBM Application Discovery and Delivery Intelligence (ADDI)
IBM ADDI (formerly IBM Application Discovery) scans COBOL source code, copybooks, JCL, DB2 DDL, CICS CSD definitions, and other mainframe artifacts to build a comprehensive model of the application. Its key capabilities include:
- Cross-reference analysis. Identifies which programs use which copybooks, which programs call which other programs, which programs access which files and databases, and which JCL jobs execute which programs.
- Impact analysis. Given a proposed change to a data field, copybook, or program, identifies all affected components throughout the system.
- Dead code detection. Identifies unreachable paragraphs, unused data definitions, and programs that are not called by any other program or JCL job.
- Business rule extraction. Identifies conditional logic patterns that likely represent business rules, extracting them into a catalog that can be reviewed by business analysts.
- Complexity metrics. Calculates cyclomatic complexity, nesting depth, program size, and other metrics that help prioritize modernization efforts.
Micro Focus Enterprise Analyzer
Micro Focus Enterprise Analyzer provides similar capabilities with particular strength in visualization. Its interactive diagrams show program call graphs, data flow diagrams, and control flow graphs that make complex system architectures comprehensible.
Open-Source Alternatives
For smaller installations or budget-constrained projects, several open-source tools provide partial coverage:
- GnuCOBOL includes a cross-reference listing compiler option that identifies data name usage.
- COBOL-lint tools can check for common anti-patterns and coding standard violations.
- Custom scripts using grep, awk, and Python can extract paragraph names, PERFORM targets, CALL targets, and COPY statements to build basic cross-reference databases.
Using Analysis Output for Modernization Planning
Analysis tool output drives modernization planning by answering critical questions:
- Which programs are the most complex and risky to modify?
- Which programs share the most copybooks (indicating tight coupling)?
- Which programs are called by the most other programs (indicating high impact if changed)?
- Which programs are never called (candidates for retirement)?
- Which data fields are used by the most programs (indicating high impact if the format changes)?
39.10 Testing During Modernization
Testing is the single most critical activity during modernization. Every change must be verified to produce exactly the same results as the legacy system. Two primary testing strategies are employed.
Regression Testing
Regression testing captures the legacy system's outputs for a comprehensive set of inputs, then verifies that the modernized system produces identical outputs for the same inputs.
*================================================================*
* PROGRAM: REGTEST1 *
* PURPOSE: Regression test harness for claims processing *
* Captures before/after results for comparison *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. REGTEST1.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEST-CASE.
05 WS-TC-NUMBER PIC 9(6).
05 WS-TC-DESCRIPTION PIC X(50).
05 WS-TC-INPUT.
10 WS-TC-CLAIM-REC PIC X(200).
05 WS-TC-EXPECTED.
10 WS-TC-EXP-STATUS PIC X(2).
10 WS-TC-EXP-AMOUNT PIC S9(7)V99.
10 WS-TC-EXP-REASON PIC X(30).
05 WS-TC-ACTUAL.
10 WS-TC-ACT-STATUS PIC X(2).
10 WS-TC-ACT-AMOUNT PIC S9(7)V99.
10 WS-TC-ACT-REASON PIC X(30).
01 WS-COUNTERS.
05 WS-TESTS-RUN PIC 9(6) VALUE ZERO.
05 WS-TESTS-PASSED PIC 9(6) VALUE ZERO.
05 WS-TESTS-FAILED PIC 9(6) VALUE ZERO.
PROCEDURE DIVISION.
0000-RUN-REGRESSION-TESTS.
PERFORM 1000-INITIALIZE
PERFORM 2000-EXECUTE-TEST-CASES
UNTIL WS-EOF-FLAG = 'Y'
PERFORM 3000-REPORT-RESULTS
PERFORM 9000-TERMINATE
STOP RUN
.
2000-EXECUTE-TEST-CASES.
READ TEST-CASE-FILE INTO WS-TEST-CASE
AT END
MOVE 'Y' TO WS-EOF-FLAG
EXIT PARAGRAPH
END-READ
ADD 1 TO WS-TESTS-RUN
* Call the program under test
MOVE WS-TC-CLAIM-REC TO WS-CLAIM-INPUT-AREA
CALL 'CLMPROC1' USING WS-CLAIM-INPUT-AREA
WS-CLAIM-RESULT-AREA
* Capture actual results
MOVE RSL-STATUS TO WS-TC-ACT-STATUS
MOVE RSL-AMOUNT TO WS-TC-ACT-AMOUNT
MOVE RSL-REASON TO WS-TC-ACT-REASON
* Compare expected vs actual
IF WS-TC-EXPECTED = WS-TC-ACTUAL
ADD 1 TO WS-TESTS-PASSED
ELSE
ADD 1 TO WS-TESTS-FAILED
PERFORM 2100-LOG-FAILURE
END-IF
.
2100-LOG-FAILURE.
MOVE WS-TC-NUMBER TO RPT-TEST-NUMBER
MOVE WS-TC-DESCRIPTION TO RPT-TEST-DESC
MOVE WS-TC-EXP-STATUS TO RPT-EXPECTED-STATUS
MOVE WS-TC-ACT-STATUS TO RPT-ACTUAL-STATUS
MOVE WS-TC-EXP-AMOUNT TO RPT-EXPECTED-AMOUNT
MOVE WS-TC-ACT-AMOUNT TO RPT-ACTUAL-AMOUNT
WRITE REPORT-LINE FROM WS-FAILURE-DETAIL
.
3000-REPORT-RESULTS.
MOVE WS-TESTS-RUN TO RPT-TOTAL-RUN
MOVE WS-TESTS-PASSED TO RPT-TOTAL-PASSED
MOVE WS-TESTS-FAILED TO RPT-TOTAL-FAILED
WRITE REPORT-LINE FROM WS-SUMMARY-LINE
IF WS-TESTS-FAILED > ZERO
MOVE 8 TO RETURN-CODE
ELSE
MOVE 0 TO RETURN-CODE
END-IF
.
Parallel Running
Parallel running executes both the legacy and modernized systems simultaneously in production, comparing their outputs in real time. This provides the highest level of confidence but requires significant infrastructure.
The parallel run typically proceeds through stages:
- Shadow mode. The modernized system processes the same inputs as the legacy system, but only the legacy system's outputs are used. Differences are logged for investigation.
- Verification mode. Both systems process inputs, and an automated comparator verifies that outputs match. If they differ, the legacy system's output is used and the discrepancy is flagged.
- Cutover mode. The modernized system's output is used in production, with the legacy system running in parallel as a safety net. If the modernized system produces an error, the legacy system's output can be substituted.
- Retirement. After a sufficient period with no discrepancies, the legacy system is decommissioned.
39.11 Migration from COBOL-74/85 to COBOL 2002/2014
Many legacy systems were written to the COBOL-74 or COBOL-85 standards and have never been updated to use features available in COBOL 2002 or COBOL 2014. Migration to modern COBOL standards is a less risky form of modernization that preserves the language while improving the code's structure and capability.
Key Features Available in COBOL 2002/2014
Object-oriented COBOL. COBOL 2002 introduced full object-oriented programming with classes, methods, inheritance, and interfaces. While OO COBOL has not been widely adopted, it enables better modular design for new development.
Free-format source. COBOL 2002 allows free-format source code, eliminating the 72-column line limit and the fixed column positions for area A and area B. This is a significant quality-of-life improvement for programmers.
*================================================================*
* COBOL-85 fixed format (columns 7-72 significant) *
*================================================================*
IDENTIFICATION DIVISION.
PROGRAM-ID. OLDSTYLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CUSTOMER-NAME PIC X(30).
PROCEDURE DIVISION.
DISPLAY 'HELLO FROM FIXED FORMAT'.
STOP RUN.
*> COBOL 2002+ free format (no column restrictions)
IDENTIFICATION DIVISION.
PROGRAM-ID. NEWSTYLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CUSTOMER-NAME PIC X(30).
PROCEDURE DIVISION.
DISPLAY 'HELLO FROM FREE FORMAT'.
STOP RUN.
Intrinsic functions expanded. COBOL 2002 and 2014 added numerous intrinsic functions for string handling, date manipulation, mathematical operations, and more. These replace verbose inline code with concise function calls.
*================================================================*
* COBOL-85: Manual string reversal *
*================================================================*
REVERSE-STRING-MANUAL.
MOVE FUNCTION LENGTH(WS-INPUT-STRING)
TO WS-STRING-LEN
MOVE SPACES TO WS-OUTPUT-STRING
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-STRING-LEN
COMPUTE WS-REV-IDX = WS-STRING-LEN - WS-IDX + 1
MOVE WS-INPUT-STRING(WS-IDX:1)
TO WS-OUTPUT-STRING(WS-REV-IDX:1)
END-PERFORM.
*================================================================*
* COBOL 2002+: Intrinsic function *
*================================================================*
REVERSE-STRING-MODERN.
MOVE FUNCTION REVERSE(WS-INPUT-STRING)
TO WS-OUTPUT-STRING.
VALIDATE statement. COBOL 2002 provides the VALIDATE statement for data validation against the rules defined in the data description, reducing the need for manual validation code.
XML and JSON support. COBOL 2014 and vendor extensions provide XML GENERATE, XML PARSE, JSON GENERATE, and JSON PARSE statements for native handling of modern data formats.
*================================================================*
* COBOL 2014: Native JSON generation *
*================================================================*
WORKING-STORAGE SECTION.
01 WS-POLICY-DATA.
05 POLICY-NUMBER PIC X(12) VALUE 'POL-00012345'.
05 HOLDER-NAME PIC X(30) VALUE 'JANE SMITH'.
05 PREMIUM-AMOUNT PIC 9(7)V99 VALUE 1250.00.
05 STATE-CODE PIC X(2) VALUE 'CA'.
01 WS-JSON-OUTPUT PIC X(500).
01 WS-JSON-LENGTH PIC 9(5).
PROCEDURE DIVISION.
GENERATE-JSON.
JSON GENERATE WS-JSON-OUTPUT
FROM WS-POLICY-DATA
COUNT WS-JSON-LENGTH
ON EXCEPTION
DISPLAY 'JSON GENERATION FAILED'
NOT ON EXCEPTION
DISPLAY WS-JSON-OUTPUT(1:WS-JSON-LENGTH)
END-JSON
.
Migration Steps
- Compile with strictest warnings. Modern COBOL compilers can flag deprecated features and suggest replacements.
- Replace period-terminated sentences with explicit scope terminators (END-IF, END-PERFORM, END-READ, and so on). This is the single most impactful change for code clarity.
- Replace GO TO with PERFORM. Use the structured programming transformation described in Section 39.5.
- Remove ALTER statements. Replace with EVALUATE or conditional PERFORM.
- Replace nested IFs with EVALUATE where appropriate.
- Add inline PERFORM where appropriate to reduce the number of tiny single-use paragraphs.
- Adopt modern data types where compatible with existing data files.
39.12 Case Study: Modernizing a 1980s Insurance Claims System
This case study illustrates the application of techniques discussed throughout this chapter to a realistic insurance claims processing system.
System Profile
Company: Mid-Atlantic Casualty Insurance (fictional) System: Claims Processing System (CPS) Original deployment: 1984 Language: COBOL-74 (partially upgraded to COBOL-85) Platform: IBM z/OS, CICS, DB2, VSAM Size: 2.4 million lines of COBOL across 847 programs Copybooks: 312 shared copybooks CICS transactions: 94 online transactions Batch jobs: 267 nightly batch jobs Developers: Original team of 12 has been replaced multiple times; current team of 6
Initial Assessment
The modernization team began with an analysis using IBM ADDI, which revealed:
- 147 programs (17%) contained ALTER statements
- 423 programs (50%) used GO TO as the primary control flow mechanism
- 89 programs (11%) exceeded 5,000 lines
- 12 programs exceeded 15,000 lines
- 34 copybooks were not referenced by any program (dead artifacts)
- 58 programs were not called by any JCL job or other program (dead programs)
- Average cyclomatic complexity was 47 (extremely high; generally, values above 20 indicate code that is very difficult to test)
Phase 1: Cleanup (Months 1-3)
The first phase focused on removing dead code and artifacts:
*================================================================*
* BEFORE: Claims validation with dead code and ALTER *
* Program CLMVAL03 - 2,847 lines *
*================================================================*
PROCEDURE DIVISION.
INIT-PARA.
ALTER PROCESS-DISPATCH TO PROCEED TO FIRST-RUN.
GO TO PROCESS-DISPATCH.
PROCESS-DISPATCH.
GO TO NORMAL-RUN.
FIRST-RUN.
OPEN INPUT CLAIM-FILE POLICY-FILE.
OPEN OUTPUT VALID-FILE ERROR-FILE REPORT-FILE.
ALTER PROCESS-DISPATCH TO PROCEED TO NORMAL-RUN.
GO TO PROCESS-DISPATCH.
NORMAL-RUN.
READ CLAIM-FILE INTO WS-CLAIM-REC
AT END GO TO END-OF-JOB.
* --- Dead code from removed feature (Y2K remediation) ---
* IF WS-CLAIM-DATE-YY < 50
* ADD 2000 TO WS-CLAIM-DATE-YY
* ELSE
* ADD 1900 TO WS-CLAIM-DATE-YY
* END-IF
IF WS-CLAIM-AMT > 0
IF WS-CLAIM-AMT <= 999999.99
GO TO VALIDATE-POLICY
ELSE
MOVE 'AMOUNT EXCEEDS MAXIMUM' TO WS-ERR-MSG
GO TO WRITE-ERROR
END-IF
ELSE
MOVE 'INVALID CLAIM AMOUNT' TO WS-ERR-MSG
GO TO WRITE-ERROR
END-IF.
VALIDATE-POLICY.
READ POLICY-FILE INTO WS-POLICY-REC
INVALID KEY
MOVE 'POLICY NOT ON FILE' TO WS-ERR-MSG
GO TO WRITE-ERROR.
IF WS-POLICY-STATUS NOT = 'A'
MOVE 'POLICY NOT ACTIVE' TO WS-ERR-MSG
GO TO WRITE-ERROR.
GO TO CHECK-COVERAGE.
CHECK-COVERAGE.
* (Additional validation logic follows for 200+ lines)
GO TO WRITE-VALID.
* --- Dead paragraph - old rating code never called ---
OLD-RATE-CALC.
MULTIPLY WS-BASE-RATE BY WS-TERR-FACTOR
GIVING WS-ADJUSTED-RATE.
ADD WS-SURCHARGE TO WS-ADJUSTED-RATE.
GO TO WRITE-VALID.
WRITE-VALID.
WRITE VALID-REC FROM WS-CLAIM-REC.
ADD 1 TO WS-VALID-COUNT.
GO TO PROCESS-DISPATCH.
WRITE-ERROR.
WRITE ERROR-REC FROM WS-ERROR-LINE.
ADD 1 TO WS-ERROR-COUNT.
GO TO PROCESS-DISPATCH.
END-OF-JOB.
CLOSE CLAIM-FILE POLICY-FILE
VALID-FILE ERROR-FILE REPORT-FILE.
STOP RUN.
*================================================================*
* AFTER: Claims validation - cleaned and restructured *
* Program CLMVAL03 - 1,924 lines (32% reduction) *
* Dead code removed, ALTER eliminated, GO TO removed *
*================================================================*
PROCEDURE DIVISION.
0000-MAIN-PROCESS.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-CLAIMS
UNTIL WS-EOF-FLAG = 'Y'
PERFORM 9000-TERMINATE
STOP RUN
.
1000-INITIALIZE.
OPEN INPUT CLAIM-FILE POLICY-FILE
OPEN OUTPUT VALID-FILE ERROR-FILE REPORT-FILE
INITIALIZE WS-COUNTERS
.
2000-PROCESS-CLAIMS.
PERFORM 2100-READ-CLAIM
IF WS-EOF-FLAG = 'Y'
EXIT PARAGRAPH
END-IF
PERFORM 3000-VALIDATE-CLAIM
IF WS-CLAIM-VALID = 'Y'
PERFORM 5000-WRITE-VALID-CLAIM
ELSE
PERFORM 6000-WRITE-ERROR-RECORD
END-IF
.
2100-READ-CLAIM.
READ CLAIM-FILE INTO WS-CLAIM-REC
AT END MOVE 'Y' TO WS-EOF-FLAG
END-READ
.
3000-VALIDATE-CLAIM.
MOVE 'Y' TO WS-CLAIM-VALID
PERFORM 3100-VALIDATE-AMOUNT
IF WS-CLAIM-VALID = 'Y'
PERFORM 3200-VALIDATE-POLICY
END-IF
IF WS-CLAIM-VALID = 'Y'
PERFORM 3300-VALIDATE-COVERAGE
END-IF
.
3100-VALIDATE-AMOUNT.
IF WS-CLAIM-AMT NOT > ZERO
MOVE 'INVALID CLAIM AMOUNT' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
ELSE IF WS-CLAIM-AMT > 999999.99
MOVE 'AMOUNT EXCEEDS MAXIMUM' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
END-IF
.
3200-VALIDATE-POLICY.
READ POLICY-FILE INTO WS-POLICY-REC
INVALID KEY
MOVE 'POLICY NOT ON FILE' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
EXIT PARAGRAPH
END-READ
IF WS-POLICY-STATUS NOT = 'A'
MOVE 'POLICY NOT ACTIVE' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
END-IF
.
3300-VALIDATE-COVERAGE.
* Coverage validation logic (extracted from inline)
IF WS-CLAIM-DATE < WS-POLICY-EFF-DATE
OR WS-CLAIM-DATE > WS-POLICY-EXP-DATE
MOVE 'CLAIM OUTSIDE POLICY PERIOD' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
END-IF
IF WS-CLAIM-VALID = 'Y'
IF WS-CLAIM-AMT > WS-POLICY-COV-LIMIT
MOVE 'EXCEEDS COVERAGE LIMIT' TO WS-ERR-MSG
MOVE 'N' TO WS-CLAIM-VALID
END-IF
END-IF
.
5000-WRITE-VALID-CLAIM.
WRITE VALID-REC FROM WS-CLAIM-REC
ADD 1 TO WS-VALID-COUNT
.
6000-WRITE-ERROR-RECORD.
WRITE ERROR-REC FROM WS-ERROR-LINE
ADD 1 TO WS-ERROR-COUNT
.
9000-TERMINATE.
PERFORM 9100-PRINT-SUMMARY
CLOSE CLAIM-FILE POLICY-FILE
VALID-FILE ERROR-FILE REPORT-FILE
.
9100-PRINT-SUMMARY.
MOVE WS-VALID-COUNT TO RPT-VALID-COUNT
MOVE WS-ERROR-COUNT TO RPT-ERROR-COUNT
COMPUTE RPT-TOTAL-COUNT =
WS-VALID-COUNT + WS-ERROR-COUNT
WRITE REPORT-LINE FROM WS-SUMMARY-LINE
.
Phase 2: API Wrapping (Months 4-8)
The highest-value CICS transactions were wrapped with REST API endpoints, enabling new web and mobile applications to access claims data without going through 3270 terminal screens. The wrapping approach from Section 39.6 was applied to 23 key transactions.
Phase 3: Data Modernization (Months 6-14)
The VSAM policy master file was migrated to DB2 tables. This required modifying 134 programs that accessed the policy file. Each modification replaced VSAM READ/WRITE/REWRITE/DELETE statements with equivalent SQL operations. The programs were modified in batches of 15-20, with each batch undergoing full regression testing before the next batch began.
Phase 4: Strangler Fig Migration (Months 12-30)
New claim types (cyber insurance, rideshare coverage) were implemented entirely in Java microservices. The strangler fig router directed these claim types to the new services while existing claim types continued to be processed by the refactored COBOL programs. Over 18 months, 40% of claim types were migrated to the new platform.
Results
After 30 months of incremental modernization:
- Code volume reduced from 2.4 million to 1.7 million lines (29% reduction through dead code removal and refactoring)
- ALTER statements reduced from 147 programs to 0
- GO TO usage reduced from 423 programs to 31 (programs scheduled for replacement)
- Average cyclomatic complexity reduced from 47 to 18
- All 94 CICS transactions available as REST APIs
- 40% of claims processing running on modern Java microservices
- Zero production outages during the entire modernization effort
- The legacy COBOL system continued to process 100% of production claims throughout the project, with no business disruption
Lessons Learned
-
Start with analysis, not coding. The initial ADDI analysis was the most valuable investment, identifying 58 dead programs that could be immediately retired and 34 dead copybooks that could be removed.
-
Regression testing is non-negotiable. Every change, no matter how small, was regression tested. The team maintained a test suite of 24,000 test cases that ran in 45 minutes on the mainframe.
-
Preserve the team's knowledge. The six COBOL programmers were not replaced; they were retrained to work alongside the Java developers. Their knowledge of the business rules and system behavior was irreplaceable.
-
Modernization is a continuous process, not a project. The modernization effort was designed to produce incremental value at every stage, not to deliver a big-bang transformation at the end.
-
Do not underestimate data conversion. Converting from VSAM to DB2 consumed more effort than any other single activity because data format dependencies were pervasive and subtle. A field that appeared to be a simple PIC X(8) date could contain packed zeros, spaces, or special sentinel values that the programs checked for in non-obvious ways.
Summary
Legacy COBOL system maintenance and modernization is a discipline that combines technical skill with pragmatic engineering judgment. The 220 billion lines of COBOL in production worldwide will not be replaced overnight, and they do not need to be. Through systematic analysis, careful refactoring, strategic wrapping, and incremental replacement using patterns like the strangler fig, organizations can modernize their COBOL systems while preserving the business logic that drives their operations.
The key principles of successful modernization are: understand before you change, test everything, preserve business logic, proceed incrementally, and remember that working software has value that must not be destroyed in the pursuit of technological currency. As discussed throughout this textbook and reinforced by the coding standards in Chapter 21, well-structured COBOL code is maintainable code, whether it was written in 1984 or 2024.
The next chapter addresses the complementary discipline of testing, quality assurance, and deployment, which provides the safety net that makes all modernization work possible.