Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Extracting Subprograms: COBOL Refactoring

Recommended resources

Welcome to the next step in our journey from micro-optimizations to architectural improvements. In our last lesson, we honed in on enhancing performance at the code level with efficient table handling. Now, we're zooming out to look at the program's structure itself. This is a critical skill for any senior developer, as it moves from simply writing correct code to designing maintainable, scalable, and modern systems.

Today's lesson focuses on a fundamental practice in software modernization: refactoring. Specifically, we will tackle how to deconstruct a monolithic COBOL program by identifying a piece of business logic and extracting it into its own callable subprogram. We'll pay close attention to the mechanics of this process and, crucially, the techniques for preserving data scope and integrity between the calling program and the new subprogram. This skill is a direct prerequisite for many of the modernization goals you've outlined, such as creating REST APIs or applying AI-driven code translation.

The Rationale for Refactoring: From Monolith to Modular

After 12 years in the field, you've undoubtedly encountered massive, multi-functional COBOL programs—often called "monoliths." These programs, while functional, can become a significant source of technical debt. They are often difficult to understand, risky to change, and nearly impossible to test in isolation.

Refactoring is the disciplined process of restructuring existing code, without changing its external behavior, to improve these non-functional attributes. By breaking a monolith into smaller, single-purpose subprograms, we gain several key advantages:

  • Maintainability: Smaller modules are easier to comprehend and safer to modify.
  • Reusability: A well-defined subprogram (e.g., a currency conversion routine) can be called from many other programs, eliminating redundant code.
  • Testability: Each subprogram can be unit-tested independently, dramatically improving quality assurance.
  • Modernization Readiness: Decomposed logic is the first step toward exposing business functions as services or APIs, a core concept in mainframe modernization.

The image below illustrates this journey. A single, monolithic component is progressively broken down, first into separate internal classes and finally into independent, reusable library components that can be leveraged by different applications.

This diagram shows the evolution of a Time Service Client application. Phase 1 is a monolith. Phase 2 separates logic into different classes within a single component. Phase 3a extracts the time service logic into a separate, reusable library, which is then used by a different application in Phase 3b, demonstrating reusability.

The Anatomy of Extraction: Creating a Subprogram

Let's walk through the manual process of extracting a PERFORMed section into an external, independently compiled subprogram. Understanding these manual steps is vital, even when using modern IDE tools that automate the process, because it teaches you what the tool is doing and how to troubleshoot it.

Imagine a monolithic program that validates customer data and then processes an order. The validation logic is a perfect candidate for extraction.

The Process:

  1. Identify a Cohesive Block of Logic: Find a section or a series of paragraphs that perform one well-defined business function. In our example, this is the PERFORM VALIDATE-CUSTOMER-DATA section.

  2. Analyze Data Dependencies: This is the most critical step. You must determine exactly what data the section needs to do its job and what data it produces.

    • Inputs: Data the logic reads but does not change (e.g., customer ID, account number).
    • Outputs: Data the logic creates or modifies (e.g., a validation status flag, an error message).
    • In/Outs: Data the logic reads and modifies (e.g., a record area that is enriched with new information).
    • Internal Data: Variables used only within the section can be defined locally in the new subprogram's WORKING-STORAGE.
  3. Create the Subprogram:

    • Create a new source file (e.g., CUSTVAL.cbl).
    • In the PROCEDURE DIVISION, define the parameters identified in the previous step using the USING phrase.
    • In the DATA DIVISION, create a LINKAGE SECTION. This section describes the data being passed from the caller. The 01-level items here must match the order and structure of the items in the USING phrase.
    • Copy the business logic into the new subprogram's PROCEDURE DIVISION.
    • End the program with an EXIT PROGRAM statement, which returns control to the caller.
  4. Modify the Original Program:

    • Remove the extracted section of code.
    • Replace the PERFORM statement with a CALL to the new subprogram. The CALL statement will use its own USING phrase to pass the required data items.

Let's solidify these concepts with a tutorial.

Contained and external sub-programs

This resource provides an excellent, concise explanation of the core COBOL constructs for modular programming.

First, read the introduction to subprograms and the overview of the CALL verb. Then, focus on the section Parameter passing mechanisms. This part is essential as it explains the two fundamental ways COBOL handles data scope between programs.

Passing Data: BY REFERENCE vs. BY CONTENT

As you just read, the USING clause on a CALL statement gives you precise control over data scope. This is a topic that frequently appears in senior developer interviews.

  • CALL ... USING BY REFERENCE data-item: This is the default mechanism. The subprogram receives the memory address of the original data item. Any change the subprogram makes to the corresponding LINKAGE SECTION item directly alters the data in the calling program. Use this for output parameters or in/out parameters. It is highly efficient as no data is copied.

  • CALL ... USING BY CONTENT data-item: The system makes a temporary copy of the data item, and the subprogram receives the address of that copy. The subprogram can modify its local copy, but the original data in the calling program remains untouched. This is the safest way to pass input parameters that should not be altered. It incurs a small overhead for the data copy.

As a senior developer, your choice between these two communicates your intent and understanding of data integrity. A standard pattern is:

  • Use BY CONTENT for read-only inputs.
  • Use BY REFERENCE for outputs or data that needs to be updated in place.

State Management in Subprograms

By default, a COBOL subprogram retains its state between calls. WORKING-STORAGE values and file statuses persist. While sometimes useful, this "state memory" can lead to unpredictable behavior. A subprogram should ideally produce the same output given the same input, regardless of its history.

You can enforce a stateless, predictable behavior using two methods.

Contained and external sub-programs

This section covers how to control the state of a subprogram.

Please read the section titled State memory and the IS INITIAL clause. Pay close attention to the example contrasting the "Fickle" and "Steadfast" programs.

As the text explains:

  1. PROGRAM-ID. MySub IS INITIAL.: This clause ensures the subprogram is restored to its initial state every time it is called. All WORKING-STORAGE items are reset to their VALUE clauses. This is the simplest and most common way to guarantee statelessness.
  2. CANCEL 'MySub': This statement, executed in the calling program, dynamically unloads the subprogram from memory. The next time it's called, it will be loaded fresh in its initial state. CANCEL is more flexible but also more explicit.

Modern Refactoring with Tooling

Manually extracting a subprogram is excellent for understanding the principles, but in a modern development environment, you'd leverage tools. IDEs like Micro Focus Enterprise Developer or IBM Developer for z/OS (IDz) have powerful refactoring capabilities that automate this entire process.

The following guide, while specific to a Micro Focus product, perfectly illustrates the automated workflow for what we've just discussed manually.

[PDF] API Enablement and COBOL Code Refactoring ebook - Micro Focus

This guide demonstrates how modern tools simplify the refactoring process.

First, read the section Breaking down the monolith to understand the strategic context. Then, carefully review the next section, Making independently callable programs. Notice how the tool automates the analysis of data dependencies and the generation of the LINKAGE SECTION and CALL statement—the very steps we outlined manually.

The key takeaway is that the tool is a powerful assistant, but it's applying the same fundamental rules of COBOL program linkage that you've just reviewed. Your knowledge allows you to guide the tool, validate its output, and debug any issues that arise.


Alternative Strategy: Contained Subprograms

An alternative to creating separate external programs is to use contained subprograms. The subprogram code resides within the same source file as the main program, typically after the main program's logic and before the final END PROGRAM marker for the compilation unit.

This approach offers a different way to manage data scope: the IS GLOBAL clause.

Contained and external sub-programs

This resource also explains the concept of contained programs and an alternative data sharing method.

Please read the section The IS GLOBAL clause and then look at the practical example that follows.

By declaring a data item with IS GLOBAL in the containing program's WORKING-STORAGE, that data becomes directly accessible to all subprograms contained within it, without needing to be passed via the LINKAGE SECTION. This can be convenient for data that is universally needed by all related sub-modules, but it also creates tighter coupling, which can be a disadvantage. For true, reusable components intended for use across many applications, external subprograms with a well-defined LINKAGE SECTION are the industry standard.


Conclusion

In this lesson, we elevated our focus from code-level optimization to architectural improvement through refactoring. We dissected the process of extracting logic from a monolithic program into a callable subprogram, a cornerstone of mainframe application modernization.

Key Takeaways:

  • Why Refactor: To improve maintainability, enable reuse, facilitate unit testing, and prepare legacy code for API enablement.
  • The Extraction Process: Manually or with tools, the process involves identifying a cohesive logic block, analyzing its data dependencies, creating a new program with a LINKAGE SECTION, and replacing the original logic with a CALL statement.
  • Preserving Data Scope: This is managed via the CALL ... USING phrase. Use BY REFERENCE for data you need to modify and BY CONTENT for read-only inputs to ensure safety and clarity.
  • State Management: Use IS INITIAL in the subprogram's PROGRAM-ID to ensure it behaves predictably and is not affected by previous executions.
  • Architectural Choices: External subprograms promote loose coupling and reusability, while contained subprograms offer a simpler way to share data (IS GLOBAL) for closely related modules.

In our next lesson, we will apply our file handling knowledge to a complex, senior-level challenge: solving a scenario to reconcile two large sequential files with mismatched record formats and keys.