Skip to content
PYD ENTERPRISES

COBOL anti-pattern

ALTER statement

ALTER rewrites the target of a GO TO at runtime, so the control flow you read in the listing is not the control flow that executes.

FAMILY
COBOL
CUTOVER RISK
Critical
Changes runtime behavior in ways a line-by-line reading misses. Resolve before any cutover plan.
MODERN MAPPING
Explicit state machine
GENERATED ARTIFACT
POSTDLY1.alter-model.json

Before and after

legacy source → modern mapping

Legacy: exhibits the pattern

POSTDLY1.cblCOBOL
1 PROCEDURE DIVISION.
2 0000-MAIN.
3 PERFORM 2000-PROCESS-RECORD THRU 2999-EXIT
4 UNTIL WS-EOF = 'Y'
5 STOP RUN.
6
7 2000-PROCESS-RECORD.
8 READ POSTING-FILE AT END MOVE 'Y' TO WS-EOF
9 GO TO 2999-EXIT.
10 2050-ROUTE.
11 GO TO 2100-FIRST-RECORD.
12 2100-FIRST-RECORD.
13 PERFORM 3000-OPEN-OUTPUTS
14 MOVE PR-BRANCH TO WS-PREV-BRANCH
15 ALTER 2050-ROUTE TO PROCEED TO 2200-STEADY-STATE.
16 2200-STEADY-STATE.
17 IF PR-BRANCH NOT = WS-PREV-BRANCH
18 PERFORM 4000-BRANCH-BREAK
19 END-IF
20 PERFORM 5000-POST-ENTRY.
21 2999-EXIT.
22 EXIT.

Modern: Explicit state machine

DailyPostingStep.javaJava
1public final class DailyPostingStep {
2
3 private enum Phase { FIRST_RECORD, STEADY_STATE }
4
5 private Phase phase = Phase.FIRST_RECORD; // was: ALTER 2050-ROUTE
6 private String previousBranch;
7
8 public void process(PostingRecord rec) {
9 switch (phase) {
10 case FIRST_RECORD -> {
11 outputs.open(); // 3000-OPEN-OUTPUTS
12 previousBranch = rec.branch();
13 phase = Phase.STEADY_STATE;
14 steadyState(rec); // fall-through into 2200
15 }
16 case STEADY_STATE -> steadyState(rec);
17 }
18 }
19
20 private void steadyState(PostingRecord rec) {
21 if (!rec.branch().equals(previousBranch)) {
22 branchBreak(rec); // 4000-BRANCH-BREAK
23 }
24 postEntry(rec); // 5000-POST-ENTRY
25 }
26}

Why it is dangerous in modernization

An altered paragraph looks like an unconditional jump to one place, but after the ALTER executes it jumps somewhere else for the rest of the run unit. Nothing at the GO TO site tells a reader that the target moves. Teams that read the source top to bottom, or tools that build a naive call graph from the literal GO TO operand, model the wrong program: the first-time path looks like the only path, and the steady-state path looks unreachable. Rewrites built from that reading either run initialization on every record or skip it entirely. ALTER was flagged obsolete in COBOL-85 and removed from the 2002 standard, yet IBM Enterprise COBOL still compiles it, so it survives in batch programs written in the 1970s and 1980s that nobody has touched since.

How PYD ENTERPRISES detects it

The COBOL front end records every ALTER statement and every paragraph that is the subject of one. Each alterable paragraph must consist of a single GO TO sentence, so the engine replaces it in the control-flow graph with a synthetic dispatch node whose possible targets are the original operand plus every ALTER ... TO PROCEED TO target found anywhere in the program. A reaching-definitions pass over a hidden state variable then computes, for each path, which target is live. The output names the initial target, the transitions, and the paragraphs that run on the first and on subsequent iterations, each linked to the line range of the ALTER and the GO TO it modifies.

Maps to: Explicit state machine

The modern equivalent is a named state field with an enum of phases and a switch over it. The hidden state that ALTER kept inside the object code becomes a visible member of the service or batch step, which you can log, test, and reset deliberately. In most cases the pattern is a first-time switch, and the target becomes a two-value enum or an idempotent initializer guarded by a flag.

Generated specification fragment

POSTDLY1.alter-model.jsonJSON
1{
2 "construct": "ALTER",
3 "alterableParagraph": "2050-ROUTE",
4 "source": { "file": "POSTDLY1.cbl", "lines": [10, 11] },
5 "initialTarget": "2100-FIRST-RECORD",
6 "transitions": [
7 {
8 "from": "2100-FIRST-RECORD",
9 "to": "2200-STEADY-STATE",
10 "alteredAt": { "file": "POSTDLY1.cbl", "lines": [15, 15] }
11 }
12 ],
13 "resetsOn": ["first CALL", "CANCEL", "PROGRAM-ID IS INITIAL"],
14 "classification": "first-time switch"
15}

Every generated element links back to the source file and line range shown in the legacy pane. Review and sign off before building against it.

What ALTER actually does at runtime

ALTER 2050-ROUTE TO PROCEED TO 2200-STEADY-STATE does not execute a jump. It patches the GO TO inside paragraph 2050-ROUTE so that every later arrival there goes to 2200 instead of 2100. The change persists for as long as the program stays in its last-used state: across loop iterations and across repeated CALLs from a driver program. The altered GO TO returns to its initial target only when the program is entered in initial state, which happens on the first CALL, after a CANCEL, or on every CALL when the PROGRAM-ID carries the INITIAL attribute.

That last detail matters during modernization. A subprogram that is called once per record by a driver and never cancelled behaves as a first-time switch over the whole job. The same subprogram compiled with IS INITIAL behaves as if the ALTER never happened. The behavior is in the compile attributes and the caller, not in the paragraph you are reading.

Common shapes in production code

  • First-time switch: open files, print headers or load a table once, then alter the router to skip the setup path.
  • Toggle: two ALTERs flip a router between two targets, used for alternating page layouts or odd and even records.
  • Error latch: after a fatal condition the router is altered to a paragraph that skips posting and only counts rejected records until end of file.

The error latch is the dangerous one. It means a single bad record silently changes what the program does for every later record, and a rewrite that treats each record independently will post entries the legacy job would have rejected.

Review checklist before you sign off the extracted state machine

  1. Confirm every ALTER target found by the engine; an ALTER inside a rarely called error paragraph is easy to miss by hand.
  1. Check the PROGRAM-ID clause and the caller for CANCEL statements, because they decide when the state resets.
  1. Map each state to a named enum value and write one equivalence test per transition, using the fixtures PYD ENTERPRISES generates from the first and second record of a run.
  1. If the pattern is a pure first-time switch, prefer an explicit initialization step in the batch framework over a state field.

Find every ALTER statement in your codebase.

Run PYD ENTERPRISES in your private VPC and get each occurrence with file, line range, modern mapping and equivalence fixtures, ready for your architects to review.