Skip to content

Engineering2026-05-19

Reading a COBOL codebase you did not write

The programs are the last thing to read. Start with the job control and the copybooks, because that is where forty years of business rules were actually written down.

Author
Halvard Bruun
Published
19 MAY 2026
Read
5 MIN
Ref
DBAB91

An engineer handed 210,000 lines of COBOL will usually open the largest program first. It is the wrong move. The largest program is largest because it accreted, and its structure will teach you nothing except that a lot of people have been here before you.

COBOL systems keep their meaning in two places, and neither is the procedure division. Start with the job control, then the copybooks. The programs come third, and by then most of them will be readable.

Job control is the architecture diagram

The JCL tells you what runs, in what order, reading which datasets and writing which others. That is the dataflow, recorded in an executable form that could not have drifted from reality, because the system would have stopped.

Read the nightly batch scheduler definitions and draw the graph: job, step, program, input datasets, output datasets, condition codes. A week of this gives you something the organisation almost certainly does not have — an accurate picture of which programs matter, which datasets are hand-offs between business domains, and which steps are conditional on a return code from a step that runs three hours earlier.

Condition-code logic deserves particular attention. COND=(4,LT) on a step is a business rule expressed in job control, and it will not appear in any program listing you read.

The copybooks are the domain model

A copybook is a shared record layout, included by every program that touches the data. It is the closest thing the system has to a schema, and it is where the domain vocabulary lives.

       01  WS-POLICY-RECORD.
           05  WS-POL-ID              PIC X(10).
           05  WS-POL-STATUS          PIC X.
               88  POL-ACTIVE         VALUE 'A'.
               88  POL-LAPSED         VALUE 'L'.
               88  POL-CANCELLED      VALUE 'C' 'X' 'Z'.
               88  POL-CHARGEABLE     VALUE 'A' 'L'.
           05  WS-POL-PREMIUM         PIC S9(9)V99 COMP-3.
           05  WS-POL-EXTRA           PIC X(40).
           05  WS-POL-MOTOR REDEFINES WS-POL-EXTRA.
               10  WS-MOT-REG         PIC X(8).
               10  WS-MOT-NCD-YEARS   PIC 9(2).
               10  FILLER             PIC X(30).

Twelve lines, four findings.

Level-88 items are business rules with names. POL-CANCELLED matches three status codes. C is the documented one; X and Z arrived with acquisitions in 1998 and 2004 and appear in no data dictionary. Any rewrite that treats status as a three-value enum will silently mis-handle every policy inherited from those books.

Overlapping 88s encode policy. POL-CHARGEABLE covers active and lapsed. Somebody decided lapsed policies still attract a charge. That decision exists nowhere else.

COMP-3 is packed decimal, not binary. S9(9)V99 COMP-3 occupies six bytes, holds a signed value to two implied decimal places, and is exact. Migrating it to a float is the single most common data-migration defect we see, and it surfaces months later as pennies of drift in a reconciliation.

REDEFINES is an untyped union. The same 40 bytes mean different things for different products, and the discriminator is not in the record. It is in whichever program decided which layout to use — so you must find that program before you can safely read the field.

Three traps worth knowing before you start

  • PERFORM ... THRU ... executes a range of paragraphs. PERFORM A200-VALIDATE THRU A200-EXIT runs everything physically located between those two labels. Someone inserting a paragraph in the middle in 2009 extended the behaviour of every caller, invisibly.
  • GO TO ... DEPENDING ON is a computed jump on an integer. The mapping from number to destination is positional, so adding a branch renumbers the rest.
  • Fall-through is normal. Paragraphs run into one another unless something prevents it. There is no block scope to protect you, and indentation means nothing.

Prove what actually runs

Static reachability analysis will over-report: it counts a program as live if anything references it, including a job that was disabled in 2013. Pair it with execution evidence — SMF records, or the scheduler's own run history — over a full business cycle, so quarter-end and year-end work is included.

On Nordvik Bank's core, 640 programs were in the source library. Over 24 months of execution history, 128 had never run. That is 18% of the estate retired without a line of analysis, and it was the single cheapest scope reduction available on the programme.

After two weeks

You should be able to name the ten programs that carry the business, describe the six datasets that pass between domains, and list the fields whose meaning is not derivable from the code. That last list is the one that matters, and it is what you take to the two people who still remember. Ask them about X and Z.

They will know. They will also be pleased that someone finally asked, because nobody has in a decade — and you will get an hour of context that no amount of reading was ever going to produce.

Written by

Halvard Bruun

Contributor

Share

Related notes

All notes
  • 01Engineering

    The integration nobody documented

    Every legacy platform has interfaces that appear in no diagram and no register. Here is how we find them before cutover finds them for us — and why the interface diagram is the last place to look.

    Legacy4 MIN
  • 02Engineering

    What a mainframe migration actually costs

    The build is about a third of it. Here is the real shape of a nine-month core banking migration budget, line by line, including the three lines nobody quotes for.

    Mainframe5 MIN
  • 03Reliability

    Cutover rehearsals, and why we do six

    A 412-step runbook took 19 hours on its first rehearsal against a 9-hour window. By the sixth it took 6 hours 40. The rehearsals were not practice — each one was designed to fail differently.

    Cutover · Runbooks5 MIN