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
1 PROCEDURE DIVISION.2 0000-MAIN.3 PERFORM 2000-PROCESS-RECORD THRU 2999-EXIT4 UNTIL WS-EOF = 'Y'5 STOP RUN.67 2000-PROCESS-RECORD.8 READ POSTING-FILE AT END MOVE 'Y' TO WS-EOF9 GO TO 2999-EXIT.10 2050-ROUTE.11 GO TO 2100-FIRST-RECORD.12 2100-FIRST-RECORD.13 PERFORM 3000-OPEN-OUTPUTS14 MOVE PR-BRANCH TO WS-PREV-BRANCH15 ALTER 2050-ROUTE TO PROCEED TO 2200-STEADY-STATE.16 2200-STEADY-STATE.17 IF PR-BRANCH NOT = WS-PREV-BRANCH18 PERFORM 4000-BRANCH-BREAK19 END-IF20 PERFORM 5000-POST-ENTRY.21 2999-EXIT.22 EXIT.
Modern: Explicit state machine
1public final class DailyPostingStep {23 private enum Phase { FIRST_RECORD, STEADY_STATE }45 private Phase phase = Phase.FIRST_RECORD; // was: ALTER 2050-ROUTE6 private String previousBranch;78 public void process(PostingRecord rec) {9 switch (phase) {10 case FIRST_RECORD -> {11 outputs.open(); // 3000-OPEN-OUTPUTS12 previousBranch = rec.branch();13 phase = Phase.STEADY_STATE;14 steadyState(rec); // fall-through into 220015 }16 case STEADY_STATE -> steadyState(rec);17 }18 }1920 private void steadyState(PostingRecord rec) {21 if (!rec.branch().equals(previousBranch)) {22 branchBreak(rec); // 4000-BRANCH-BREAK23 }24 postEntry(rec); // 5000-POST-ENTRY25 }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
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
- Confirm every ALTER target found by the engine; an ALTER inside a rarely called error paragraph is easy to miss by hand.
- Check the PROGRAM-ID clause and the caller for CANCEL statements, because they decide when the state resets.
- 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.
- If the pattern is a pure first-time switch, prefer an explicit initialization step in the batch framework over a state field.
Related patterns
Usually found in the same codebase.
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.