Changing Code Without Breaking It

COBOL COBOL 2014 (ISO/IEC 1989:2014), built with GnuCOBOL 3.x · ✓ verified by execution on 2026-08-16

When you encounter legacy code that has been running untouched for decades, the prospect of changing it can be terrifying. Many legacy programs rely on complex, unstructured GO TO loops and overlapping memory spaces (REDEFINES). Modifying such code without a safety net often introduces subtle bugs that are difficult to trace.

Refactoring is the process of changing the internal structure of code without altering its external behavior. In COBOL, this means transforming “spaghetti code” into structured, readable logic. However, you should never attempt this without regression testing.

The Regression Test Harness

A test harness isolates the behavior of a program segment so it can be verified independently. This baseline is your safety net. You run the original code, capture its outputs and side-effects, and assert that the new code produces identically the same results.

Below is an example of an unmodified legacy program wrapped in a simple test harness:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. TestHarness.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TEST-VAL PIC 9 VALUE 1.
       PROCEDURE DIVISION.
           PERFORM RUN-TEST.
           STOP RUN.
       RUN-TEST.
           DISPLAY "BEFORE: " TEST-VAL.
           PERFORM LEGACY-CODE.
           DISPLAY "AFTER: " TEST-VAL.
       LEGACY-CODE.
           ADD 5 TO TEST-VAL.
Output
BEFORE: 1
AFTER: 6

Small Incremental Steps

Refactoring should be performed in small, verifiable steps. After every minor change, you rerun your test harness. If the output diverges, you immediately know which step broke the logic.

Here is the same test harness, but the LEGACY-CODE has been refactored into NEW-CODE:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. RefactoredHarness.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TEST-VAL PIC 9 VALUE 1.
       01  ADD-AMOUNT PIC 9 VALUE 5.
       PROCEDURE DIVISION.
           PERFORM RUN-TEST.
           STOP RUN.
       RUN-TEST.
           DISPLAY "BEFORE: " TEST-VAL.
           PERFORM NEW-CODE.
           DISPLAY "AFTER: " TEST-VAL.
       NEW-CODE.
           ADD ADD-AMOUNT TO TEST-VAL.
Output
BEFORE: 1
AFTER: 6

Notice that the output remains precisely the same. Behavior preservation means the outputs of the old and new logic are identical for all inputs. Refactoring changes how the program is written without changing what it does.

Untangling GO TO Logic

One of the most common refactoring tasks in COBOL is removing GO TO statements in favor of structured PERFORM or IF/ELSE blocks.

Consider this unstructured snippet:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. Before.
       PROCEDURE DIVISION.
       P1.
           GO TO P2.
       P2.
           DISPLAY "X".
           STOP RUN.
Output
X

It can be safely refactored to:

cobol ✓ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. After.
       PROCEDURE DIVISION.
       P1.
           PERFORM P2.
           STOP RUN.
       P2.
           DISPLAY "X".
Output
X

The exact behavior is maintained, but the flow of control is now predictable and structured.

Visualizing the Flow

Legacy GO TO Structured

Predict the Output

Before proceeding, let’s verify you understand the behavior of the refactored complex logic.

Predict the output cobol

Read the code. What exactly will it print? Commit to an answer before you look.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ComplexR.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  VAL PIC 9 VALUE 1.
       PROCEDURE DIVISION.
       P1.
           ADD 1 TO VAL.
           GO TO P3.
       P2.
           ADD 2 TO VAL.
       P3.
           ADD 3 TO VAL.
           DISPLAY VAL.
           STOP RUN.
Output
5

The program skips P2 because of the GO TO P3 statement. So VAL becomes 1 + 1 + 3 = 5.

Check yourself

What is the primary purpose of a test harness when refactoring?

Reveal answer

To establish a behavioral baseline and prevent regressions. — A test harness is designed to lock in the current behavior of a system, catching any unintended side effects when the internal code is altered.

What does 'behavior preservation' mean in the context of refactoring?

Reveal answer

The outputs of the old and new logic are identical for all inputs. — Refactoring changes how the code is written, but the outputs must remain identical for the same inputs.

Why is refactoring COBOL GO TO statements critical?

Reveal answer

Because GO TO logic is often unstructured, making the control flow hard to follow and modify safely. — GO TO statements often create spaghetti code that is difficult to understand and inherently unsafe to modify.

Challenges

Challenge 1 +50 XP

We need a safety net for LEGACY-TAX. Write a RUN-TEST paragraph that displays 'BEFORE: ' followed by TAX-VAL, PERFORMs LEGACY-TAX, and then displays 'AFTER: ' followed by TAX-VAL. Call RUN-TEST from the main procedure and STOP RUN.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 — expects "BEFORE: 15\nAFTER: 20\n"
Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. HarnessPractice.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  TAX-VAL PIC 99 VALUE 15.
       PROCEDURE DIVISION.
           PERFORM RUN-TEST.
           STOP RUN.
       RUN-TEST.
           DISPLAY 'BEFORE: ' TAX-VAL.
           PERFORM LEGACY-TAX.
           DISPLAY 'AFTER: ' TAX-VAL.
       LEGACY-TAX.
           ADD 5 TO TAX-VAL.

🐞 Bug Hunt +50 XP

A developer rewrote the LEGACY-DISCOUNT paragraph from scratch, but it fails for edge cases. Find and fix the bug in NEW-DISCOUNT so it exactly matches the output of LEGACY-DISCOUNT for all inputs.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

cobol ✓ solution verified at build time

COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the fixed code should be, then open the solution and compare. The expected output below is real — it came from compiling the solution with GnuCOBOL when this page was built.

  • Test 1 (input: "60") — expects "DISCOUNT: 20\n"
  • Test 2 (input: "40") — expects "DISCOUNT: 10\n"
  • Test 3 (input: "20") — expects "DISCOUNT: 00\n"
Show solution (0 XP)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. BugHuntRefactor.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  PRICE PIC 99.
       01  DISCOUNT PIC 99 VALUE 0.
       PROCEDURE DIVISION.
           ACCEPT PRICE.
           PERFORM NEW-DISCOUNT.
           DISPLAY 'DISCOUNT: ' DISCOUNT.
           STOP RUN.
       LEGACY-DISCOUNT.
           IF PRICE > 50
               GO TO HIGH-DISCOUNT.
           IF PRICE > 30
               GO TO MED-DISCOUNT.
           GO TO END-DISCOUNT.
       HIGH-DISCOUNT.
           ADD 20 TO DISCOUNT.
           GO TO END-DISCOUNT.
       MED-DISCOUNT.
           ADD 10 TO DISCOUNT.
       END-DISCOUNT.
           EXIT.
       NEW-DISCOUNT.
           IF PRICE > 50
               ADD 20 TO DISCOUNT
           ELSE
               IF PRICE > 30
                   ADD 10 TO DISCOUNT
               END-IF
           END-IF.