11 min read

In your introductory course, you probably wrote self-contained programs. Each program was a complete unit: it defined all its own data, contained all its own logic, and ran on its own. That approach works for learning. It does not work for the...

Part V: Subprograms and Modular Design

No Program Is an Island

In your introductory course, you probably wrote self-contained programs. Each program was a complete unit: it defined all its own data, contained all its own logic, and ran on its own. That approach works for learning. It does not work for the enterprise.

A real enterprise COBOL system is not one program. It is hundreds of programs — sometimes thousands — that work together. GlobalBank's core banking system comprises over 2,400 individual COBOL programs. MedClaim's claims processing system has roughly 800. These programs call each other, pass data to each other, share common routines, and are compiled and deployed independently so that a change to one program does not require recompiling the entire system.

This is modular design, and it is the subject of Part V.

Modularity is not a COBOL-specific concept. Every serious programming language supports some form of code organization into reusable, independently maintainable units — functions, classes, modules, packages, libraries. What differs is the mechanism. In COBOL, the primary mechanism is the CALL statement, which invokes a separately compiled subprogram, passes it data through the LINKAGE SECTION, and receives control back when the subprogram finishes.

Understanding CALL — its syntax, its semantics, its performance characteristics, and its role in system architecture — is essential for any COBOL programmer who will work on production systems. You cannot work at GlobalBank or MedClaim or any comparable enterprise and write only standalone programs. You will write programs that call other programs. You will write programs that are called by other programs. You will modify programs whose calling relationships span dozens of modules. And you will need to understand how data flows through those relationships.

The Case for Modularity

Why not just write one big program? It is a fair question, and the answer illuminates fundamental principles of software engineering that apply far beyond COBOL.

Reuse. If twenty programs need to validate a date, you can write the validation logic twenty times — or you can write it once, in a subprogram, and have all twenty programs call it. The subprogram approach means that when you find a bug in the date validation logic (or when a business rule changes), you fix it in one place. The twenty calling programs pick up the fix automatically when the subprogram is recompiled. The copy-paste approach means you have twenty potential places to fix, and the near certainty that you will miss at least one.

Maintainability. A 500-line program that does one thing well is easier to understand, modify, and test than a 10,000-line program that does twenty things in a tangle of interleaved logic. Breaking a system into focused subprograms reduces the cognitive load on the programmer who must modify the system later — a programmer who may be you, six months from now, having forgotten the details.

Team development. On a large project, different programmers work on different programs simultaneously. If the entire system is one monolithic program, only one person can work on it at a time (or the team faces nightmarish merge conflicts). If the system is decomposed into subprograms with well-defined interfaces, programmers can work in parallel, each responsible for their own module.

Independent testing. A subprogram with a clear interface — defined inputs and expected outputs — can be tested in isolation, without running the entire system. This makes testing faster, more thorough, and more focused.

Controlled compilation. In a large COBOL system, recompiling everything takes significant time and requires retesting everything. If only one subprogram changes, only that subprogram needs to be recompiled and retested (along with integration testing of its callers). This is not just a convenience; it is an economic necessity in systems with thousands of programs.

Maria Chen at GlobalBank puts it simply: "Modularity is how you build systems that teams of people can maintain over decades. Without it, you have a house of cards."

CALL Mechanics: The Details Matter

The COBOL CALL statement looks straightforward:

CALL 'DATEUTIL' USING WS-INPUT-DATE WS-OUTPUT-DATE WS-RETURN-CODE.

But behind that simple syntax lies a set of mechanical details that you must understand to use CALL correctly and diagnose problems when they arise.

Static vs. Dynamic CALL. A static CALL (CALL 'literal-name') links the subprogram into the calling program's load module at link-edit time. A dynamic CALL (CALL WS-PROGRAM-NAME, where WS-PROGRAM-NAME is a data item) loads the subprogram into memory at runtime. Static calls are faster — the subprogram is already in memory — but dynamic calls are more flexible, allowing you to change which subprogram is called without recompiling the caller. Enterprise systems use both, depending on the situation.

Parameter passing. COBOL passes parameters BY REFERENCE (the default) or BY CONTENT. BY REFERENCE passes the address of the data item, meaning the called program can modify the caller's data — powerful but dangerous if the called program is buggy. BY CONTENT passes a copy, protecting the caller's data but preventing the called program from returning results through that parameter. Understanding the difference, and choosing correctly, is critical.

The LINKAGE SECTION. The called program's LINKAGE SECTION defines the parameters it expects. The calling program's USING clause must match — same number of parameters, compatible sizes and types. A mismatch does not cause a compile error (because the programs are compiled separately), but it causes runtime errors — often SOC4 abends — that can be difficult to diagnose.

CANCEL and program state. After a CALL, the called program remains in memory and retains its state (WORKING-STORAGE values). A subsequent CALL to the same program finds it in the state the previous call left it in. The CANCEL statement releases the program and resets its state. Understanding this behavior is important for programs that maintain counters, flags, or accumulated values across multiple calls.

Return codes. COBOL provides the RETURN-CODE special register for passing a numeric status between programs. By convention (though not by language requirement), a return code of zero means success, and non-zero values indicate errors. Checking the return code after every CALL is a defensive programming practice as essential as checking FILE STATUS after every file operation.

Nested Programs: Modularity Without Separate Compilation

COBOL-85 introduced nested programs — programs defined within other programs, sharing the same source file but maintaining their own data and logic. Nested programs offer a middle ground between inline code (no modularity) and separately compiled subprograms (maximum modularity but maximum overhead in terms of compilation and deployment).

Nested programs are particularly useful for utility routines that are specific to one program and not reusable across the system. Rather than creating a separately compiled subprogram for a routine that only one program will ever call, you can define it as a nested program — modular enough to have its own data and logic, but contained within the parent program for ease of compilation and deployment.

At MedClaim, James Okafor uses nested programs in the claims adjudication engine for validation routines that are specific to particular claim types. The provider validation logic for dental claims, for example, is different from the validation logic for medical claims, and both are different from pharmacy claims. Each is a nested program within the main adjudication program, keeping the logic organized without creating separately compiled subprograms that would add complexity to the build and deployment process.

Object-Oriented COBOL: The Modern Extension

The COBOL 2002 and COBOL 2014 standards introduced object-oriented features: classes, methods, interfaces, inheritance, and polymorphism. OO COBOL extends the language's modularity capabilities beyond subprograms into the realm of object-oriented design, allowing COBOL programmers to use the same design patterns and architectural approaches that are standard in Java, C#, and other OO languages.

OO COBOL is not widely adopted in production systems — the vast majority of enterprise COBOL code uses procedural style with CALL-based modularity. But it is increasingly relevant for new development, particularly in shops that are modernizing their COBOL systems by adding object-oriented layers on top of existing procedural code. IBM's Enterprise COBOL compiler supports OO features, and understanding them positions you for the future of the language.

Part V covers OO COBOL pragmatically: not as a theoretical exercise in object-oriented design (there are other textbooks for that), but as a practical extension of COBOL's modularity toolkit. You will learn to define classes and methods, to create and use objects, and to understand when OO COBOL adds value versus when procedural CALL-based design is the better choice.

Derek Washington at GlobalBank, with his Java background, was initially excited about OO COBOL and initially disappointed by how little of it was used in practice. Over time, he came to understand why: the existing codebase was procedural, the team's expertise was procedural, and the tooling — compilers, debuggers, source management systems — was optimized for procedural COBOL. Introducing OO COBOL would have required not just new code but new skills, new tools, and new practices. "OO COBOL is the right answer for some problems," Derek concluded. "But it is not the right answer for all problems, and knowing the difference matters more than knowing the syntax."

Inter-Language Communication: COBOL Meets the World

Enterprise systems are not monolingual. A single application might include COBOL programs calling C routines for high-performance data manipulation, Assembler routines for low-level system services, PL/I programs that were written by a different team in a different era, and Java components that provide modern interfaces. COBOL's ability to call programs written in other languages — and to be called by them — is a critical enterprise skill.

Inter-language communication involves challenges beyond simple parameter passing. Data representations differ between languages: COBOL's packed decimal has no equivalent in C, C's null-terminated strings have no equivalent in COBOL, and Java's Unicode strings must be converted to COBOL's EBCDIC character set. Calling conventions differ: some languages pass parameters on the stack, others in registers, and the details vary by platform and compiler.

Part V covers inter-language CALL mechanics for the most common combinations: COBOL calling C, COBOL calling Assembler, and COBOL interacting with Java (both through JNI on the mainframe and through more modern integration approaches). These are practical skills that you will need in any enterprise environment where COBOL programs are part of a larger, multilingual application.

At GlobalBank, the modernization initiative that brought Derek Washington to the team has introduced several Java-based components that interact with COBOL through a combination of MQ messaging, web service calls, and direct JNI invocations. Understanding how data flows between COBOL and Java — the encoding conversions, the data type mappings, the error handling across language boundaries — is now a required skill for the development team.

What Part V Covers

The five chapters in Part V build from basic CALL mechanics to advanced modularity:

Chapter 22: Subprogram Basics and the CALL Statement covers the mechanics of calling and being called: static and dynamic CALL, the USING clause, the LINKAGE SECTION, RETURN-CODE, and the compile-link-execute sequence for multi-program systems.

Chapter 23: Parameter Passing and Data Sharing dives deep into BY REFERENCE and BY CONTENT, parameter matching rules, common pitfalls (length mismatches, type mismatches, missing parameters), and design patterns for safe parameter interfaces.

Chapter 24: Nested Programs and Program Organization covers COBOL's nested program facility, the COMMON and INITIAL clauses, data scoping rules, and the design decisions that determine when nested programs are preferable to separately compiled subprograms.

Chapter 25: Object-Oriented COBOL introduces OO COBOL pragmatically: class definitions, methods, object creation, inheritance, interfaces, and the practical considerations for using OO features in an enterprise COBOL environment.

Chapter 26: Inter-Language Communication covers calling between COBOL and other languages: C, Assembler, Java, and the data conversion, encoding, and calling-convention challenges that inter-language communication involves.

The Architecture of Real Systems

Part V is ultimately about architecture — about how large systems are organized into manageable, maintainable, testable pieces. The CALL statement is the mechanism, but the skill is design: deciding what should be a subprogram, what parameters it should accept, what contract it establishes with its callers, and how the collection of subprograms fits together into a coherent system.

Maria Chen describes system architecture in organic terms: "A well-designed system is like a healthy body. Each organ does its job, the circulatory system carries nutrients where they are needed, and the brain coordinates everything. A poorly designed system is like a body where the liver is trying to do the heart's job and the lungs are connected to the stomach."

The programs you will design after completing Part V will be well-organized bodies: each component doing its job, clean interfaces carrying data where it needs to go, and a clear architecture that anyone on the team can understand. That is the goal. Let us build toward it.