【COBOL】CALL文完全ガイド|静的/動的CALL・GOBACK/STOP RUNの違い・CANCEL・共通モジュール設計

COBOLのCALL文はプログラムを複数のモジュールに分割し、互いを呼び出す仕組みです。単純な呼び出しだけでなく、静的/動的リンク・GOBACKとSTOP RUNの使い分け・CANCEL文によるモジュール再初期化・ネストプログラムまで理解することで、保守性の高い設計ができます。

この記事ではCALL文の完全な構文オプションから、呼び出し連鎖でのGOBAK設計、バッチ処理の共通モジュール実装パターンまで解説します。引数の渡し方(BY REFERENCE/BY CONTENT/BY VALUE)については引数の使い方完全ガイドをご参照ください。

この記事でわかること

  • CALL文の完全な構文(静的/動的・ON EXCEPTION・END-CALL)
  • GOBACKとSTOP RUNの違い(呼び出し連鎖での誤用が多い)
  • CANCEL文でモジュールをアンロードし、WORKING-STORAGEを再初期化する方法
  • ネストプログラム(IDENTIFICATION DIVISION内の内部プログラム定義)
  • プログラム間のデータ共有3方式の使い分け
  • 共通エラーハンドラ・共通ログ出力の実践モジュール設計
スポンサーリンク

CALL文の完全な構文

CALL文には静的呼び出しと動的呼び出しがあり、オプション句を組み合わせることでエラーハンドリングや引数の渡し方を細かく制御できます。

CALL文の完全な構文
CALL {プログラム名リテラル | プログラム名変数}
    [USING {[BY REFERENCE] | BY CONTENT | BY VALUE}
           引数1 [引数2 ...]]
    [RETURNING 戻り値変数]
    [ON EXCEPTION 例外時処理]
    [NOT ON EXCEPTION 正常時処理]
END-CALL.
オプション 説明
プログラム名リテラル ‘SUBPGM’のように文字列定数で指定。リンク時に結合(静的CALL)
プログラム名変数 WS-PGM-NAMEのようにPIC X変数で指定。実行時にロード(動的CALL)
USING句 引数を渡す。BY REFERENCE(デフォルト)/ BY CONTENT / BY VALUE の3方式
RETURNING句 戻り値を受け取る(COBOL 2002以降。IBM環境では使用制限あり)
ON EXCEPTION CALLが失敗した場合(モジュール未存在・ロードエラー)に実行する処理
NOT ON EXCEPTION CALLが成功した場合に実行する処理(任意)
END-CALL CALLブロックの明示的な終端。ON/NOT ON EXCEPTIONを使う場合は必須
静的CALLと動的CALLの書き方
WORKING-STORAGE SECTION.
01 WS-MODULE-NAME   PIC X(8).
01 WS-AMOUNT        PIC 9(9)V99.
01 WS-TAX           PIC 9(7)V99.
01 WS-RATE          PIC V99.

*=== 静的CALL(リテラル指定・リンク時に結合)===
PROCEDURE DIVISION.
    CALL 'TAXCALC' USING WS-AMOUNT WS-RATE WS-TAX
    END-CALL.

*=== 動的CALL(変数指定・実行時にロード)===
    IF WS-REGION = 'JP'
        MOVE 'TAXCALCJ' TO WS-MODULE-NAME
    ELSE
        MOVE 'TAXCALCE' TO WS-MODULE-NAME
    END-IF

    CALL WS-MODULE-NAME USING WS-AMOUNT WS-RATE WS-TAX
        ON EXCEPTION
            DISPLAY 'モジュールのロードに失敗: ' WS-MODULE-NAME
            MOVE 12 TO RETURN-CODE
            GOBACK
        NOT ON EXCEPTION
            DISPLAY 'CALLに成功しました'
    END-CALL.

GOBACKとSTOP RUNの違い

COBOLにはプログラムを終了させる命令が複数ありますが、使い分けを誤るとサブプログラムがジョブ全体を終了させてしまうバグが起きます。

命令 動作 使いどころ
GOBACK サブプログラム: 呼び元に制御を返す
メインプログラム: ジョブを終了する
サブプログラムの終了には必ずこれを使う
STOP RUN どこで実行してもジョブ全体を終了する メインプログラムの最後のみで使う
EXIT PROGRAM サブプログラムからの終了(GOBACKと同じ効果) 旧スタイル。GOBACKの方が推奨
STOP literal オペレータにメッセージを表示して一時停止
(バッチ処理では使用禁止)
旧スタイル。現代COBOLでは非推奨
GOBACKとSTOP RUNの使い分け
*=== メインプログラム ===
IDENTIFICATION DIVISION.
PROGRAM-ID. MAIN-PGM.

PROCEDURE DIVISION.
    CALL 'SUBPGM-A' USING WS-DATA
    END-CALL

    IF RETURN-CODE NOT = 0
        DISPLAY 'サブプログラムエラー'
        *> メインではSTOP RUNでジョブ終了
        STOP RUN
    END-IF

    DISPLAY '正常終了'
    STOP RUN.    *> メインの終了はSTOP RUN

*=== サブプログラム(STOP RUNを使ってはいけない例)===
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPGM-A.

PROCEDURE DIVISION USING LK-DATA.
    IF LK-DATA = SPACES
        DISPLAY 'エラー: データが空です'
        MOVE 8 TO RETURN-CODE
        *> NG: STOP RUNを使うとジョブ全体が終了してしまう
        *STOP RUN.

        *> OK: GOBACKで呼び元に戻る
        GOBACK
    END-IF

    PERFORM PROCESS-DATA
    MOVE 0 TO RETURN-CODE
    GOBACK.     *> サブプログラムの終了は必ずGOBACK

PROCESS-DATA.
    *> 処理内容
    EXIT.
サブプログラムでSTOP RUNを書くとどうなるか
サブプログラムA内でSTOP RUNを実行すると、CALLした呼び元のメインプログラムに戻らず、ジョブ全体が終了します。CLOSE漏れやロールバック漏れが発生し、ファイルが壊れたりデータが中途半端な状態になる危険があります。サブプログラムの終了は必ずGOBACKを使うことを徹底しましょう。

RETURN-CODEを使った戻りコードの連鎖

複数階層のCALL連鎖(A→B→C)でRETURN-CODEを適切に引き継がないと、エラーが呼び元に伝播しません。

3階層の呼び出し連鎖でのRETURN-CODE設計
*=== 最下層: SUBPGM-C ===
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPGM-C.
LINKAGE SECTION.
01 LK-DATA    PIC X(100).
PROCEDURE DIVISION USING LK-DATA.
    *> 処理実行
    IF エラー条件
        MOVE 8 TO RETURN-CODE    *> エラーをRETURN-CODEにセット
        GOBACK
    END-IF
    MOVE 0 TO RETURN-CODE
    GOBACK.

*=== 中間層: SUBPGM-B ===
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPGM-B.
WORKING-STORAGE SECTION.
01 WS-RC-SAVE  PIC S9(9) COMP.    *> RETURN-CODEを退避
LINKAGE SECTION.
01 LK-INPUT   PIC X(100).
01 LK-OUTPUT  PIC X(100).
PROCEDURE DIVISION USING LK-INPUT LK-OUTPUT.
    CALL 'SUBPGM-C' USING LK-INPUT
        ON EXCEPTION
            DISPLAY 'SUBPGM-Cのロード失敗'
            MOVE 12 TO RETURN-CODE
            GOBACK
    END-CALL
    MOVE RETURN-CODE TO WS-RC-SAVE    *> すぐに退避(次のCALLで上書きされる前に)

    IF WS-RC-SAVE NOT = 0
        *> エラーを呼び元に伝播させる
        MOVE WS-RC-SAVE TO RETURN-CODE
        GOBACK
    END-IF

    *> Cが成功した場合の後続処理
    PERFORM BUILD-OUTPUT
    MOVE 0 TO RETURN-CODE
    GOBACK.

*=== 呼び元: MAIN-PGM ===
PROCEDURE DIVISION.
    CALL 'SUBPGM-B' USING WS-INPUT WS-OUTPUT
    END-CALL
    EVALUATE RETURN-CODE
        WHEN 0  DISPLAY '正常終了'
        WHEN 8  DISPLAY '業務エラー'
        WHEN 12 DISPLAY 'システムエラー'
    END-EVALUATE.
RETURN-CODEの退避を忘れない
CALL直後にRETURN-CODEを確認しないまま別のCALLを実行すると、RETURN-CODEが上書きされて最初のエラーが消えます。CALL直後は必ず MOVE RETURN-CODE TO WS-RC-SAVE で退避し、そのWS-RC-SAVEで判定するパターンを習慣にしましょう。

CANCEL文でモジュールを解放・再初期化する

動的CALLでロードされたモジュールは、CANCEL文を実行するまでメモリに残ります。CANCELするとモジュールが解放され、次のCALL時に再ロード・WORKING-STORAGEが再初期化されます。

CANCEL文の動作と使い方
WORKING-STORAGE SECTION.
01 WS-COUNTER     PIC 9(5).

PROCEDURE DIVISION.
    *--- 1回目のCALL ---
    CALL 'COUNTER-MOD'
    MOVE RETURN-CODE TO WS-COUNTER
    DISPLAY '1回目: ' WS-COUNTER     *> カウンタ=1

    *--- 2回目のCALL(モジュールはまだメモリに残っている)---
    CALL 'COUNTER-MOD'
    MOVE RETURN-CODE TO WS-COUNTER
    DISPLAY '2回目: ' WS-COUNTER     *> カウンタ=2(前回の状態が保持される)

    *--- CANCEL: モジュールをメモリから解放 ---
    CANCEL 'COUNTER-MOD'

    *--- 3回目のCALL(再ロード→WORKING-STORAGEが再初期化される)---
    CALL 'COUNTER-MOD'
    MOVE RETURN-CODE TO WS-COUNTER
    DISPLAY '3回目: ' WS-COUNTER     *> カウンタ=1(初期値に戻る)

    STOP RUN.

*=== COUNTER-MOD サブプログラム ===
IDENTIFICATION DIVISION.
PROGRAM-ID. COUNTER-MOD.
WORKING-STORAGE SECTION.
01 WS-CNT    PIC 9(5) VALUE 0.    *> 初回ロード時のみ0に初期化
PROCEDURE DIVISION.
    ADD 1 TO WS-CNT
    MOVE WS-CNT TO RETURN-CODE
    GOBACK.
動作 説明
CANCEL後の再CALLでWSが初期化される バッチループで同一モジュールを毎回初期値から処理したい場合に使う
CANCELしない場合 モジュールのWS値は次のCALLでも保持される。意図的に状態を持たせたい場合はCANCELしない
CANCEL後のON EXCEPTION CANCELしたモジュールへの即時CALLはON EXCEPTIONにならない(再ロードされる)
静的CALLへのCANCEL リンク時に組み込まれた静的モジュールにはCANCELが効かない(コンパイラ依存)

ネストプログラム(内部プログラム)

COBOL 85以降では、プログラムの中に別のプログラムを定義する「ネストプログラム」が使えます。別ファイルにしなくても、1つのソースファイルに複数のプログラムを収められます。

ネストプログラムの構造
IDENTIFICATION DIVISION.
PROGRAM-ID. OUTER-PROG.     *> 外部プログラム(親)

DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-OUTER-DATA  PIC X(20) VALUE 'OUTER DATA'.
01 WS-RESULT      PIC X(20).

PROCEDURE DIVISION.
    CALL 'INNER-PROG' USING WS-OUTER-DATA WS-RESULT
    DISPLAY '結果: ' WS-RESULT
    STOP RUN.

*>>> ネストプログラムの開始 <<<
IDENTIFICATION DIVISION.
PROGRAM-ID. INNER-PROG.     *> 内部プログラム(子)

DATA DIVISION.
LINKAGE SECTION.
01 LK-INPUT   PIC X(20).
01 LK-OUTPUT  PIC X(20).

PROCEDURE DIVISION USING LK-INPUT LK-OUTPUT.
    MOVE LK-INPUT TO LK-OUTPUT
    INSPECT LK-OUTPUT CONVERTING SPACES TO '-'
    GOBACK.

END PROGRAM INNER-PROG.     *> 内部プログラムの終端
*>>> ネストプログラムの終了 <<<

END PROGRAM OUTER-PROG.     *> 外部プログラムの終端

グローバルデータを共有する(GLOBAL句)

親プログラムのWORKING-STORAGEにGLOBALを付けると、その変数をネストした子プログラムから参照できます。

GLOBAL句によるデータ共有
IDENTIFICATION DIVISION.
PROGRAM-ID. PARENT-PROG.

DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LOG-FILE    PIC X(40) VALUE '/log/batch.log' GLOBAL.  *> 子も参照可能
01 WS-ERROR-CODE  PIC 9(4)              VALUE 0    GLOBAL.
01 WS-PRIVATE-DATA PIC X(20).                               *> GLOBAL なし:子から見えない

PROCEDURE DIVISION.
    CALL 'CHILD-PROC-A'
    CALL 'CHILD-PROC-B'
    STOP RUN.

IDENTIFICATION DIVISION.
PROGRAM-ID. CHILD-PROC-A.
PROCEDURE DIVISION.
    *> WS-LOG-FILE は GLOBAL なので直接参照できる
    DISPLAY 'ログファイル: ' WS-LOG-FILE
    *> WS-PRIVATE-DATA は見えない(コンパイルエラー)
    GOBACK.
END PROGRAM CHILD-PROC-A.

IDENTIFICATION DIVISION.
PROGRAM-ID. CHILD-PROC-B.
PROCEDURE DIVISION.
    IF WS-ERROR-CODE NOT = 0    *> GLOBAL変数を参照
        DISPLAY 'エラー検出: ' WS-ERROR-CODE
    END-IF
    GOBACK.
END PROGRAM CHILD-PROC-B.

END PROGRAM PARENT-PROG.
ネストプログラムと外部ファイル分割の使い分け

  • ネストプログラム: 1つのコンパイル単位内で完結する処理・親専用のヘルパー処理。小さなユーティリティに向く
  • 外部ファイル分割: 複数のメインプログラムから共有して呼ぶ共通モジュール・別チームが開発するモジュール

ネストプログラムは親からしか呼べないため、共通モジュールは外部ファイルで管理するのが原則です。

プログラム間のデータ共有3方式

CALL文で複数プログラムが協調動作するとき、データの共有方法には3つのアプローチがあります。

方式 仕組み 特徴 向いている場面
引数(USING句) CALLのたびに渡す。渡したいデータを明示できる 柔軟・安全。引数が多くなるとCALL文が煩雑になる 通常のデータ受け渡し全般
EXTERNAL句 同一実行環境内で変数名を共有。どのプログラムからも直接参照可能 引数不要で便利だが、どこから変更されるか追いにくい 全モジュール共通の設定値・エラー情報
ファイル/DBを介した共有 一時ファイルやDBに書き出して別プログラムが読む 完全に独立して動作可能。I/Oオーバーヘッドあり 非同期処理・別ジョブのプログラム間
EXTERNAL句によるデータ共有
*=== プログラムAで定義 ===
IDENTIFICATION DIVISION.
PROGRAM-ID. PROG-A.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SHARED-CONFIG     EXTERNAL.    *> EXTERNAL宣言
   05 CFG-DB-HOST       PIC X(40).
   05 CFG-DB-PORT       PIC 9(5).
   05 CFG-ERROR-CODE    PIC 9(4).
   05 CFG-LOG-LEVEL     PIC X(1).

PROCEDURE DIVISION.
    *> 設定値を初期化
    MOVE 'db.example.com' TO CFG-DB-HOST
    MOVE 5432             TO CFG-DB-PORT
    MOVE 'I'              TO CFG-LOG-LEVEL   *> I=INFO
    MOVE 0                TO CFG-ERROR-CODE

    CALL 'PROG-B'    *> USINGなしで呼んでも共有データを参照できる
    STOP RUN.

*=== プログラムBで同名のEXTERNAL変数を定義 ===
IDENTIFICATION DIVISION.
PROGRAM-ID. PROG-B.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SHARED-CONFIG     EXTERNAL.    *> 同じ名前・構造でEXTERNAL宣言
   05 CFG-DB-HOST       PIC X(40).
   05 CFG-DB-PORT       PIC 9(5).
   05 CFG-ERROR-CODE    PIC 9(4).
   05 CFG-LOG-LEVEL     PIC X(1).

PROCEDURE DIVISION.
    *> PROG-Aで設定した値を参照できる
    DISPLAY 'DB接続先: ' CFG-DB-HOST ':' CFG-DB-PORT
    GOBACK.

実践: バッチ処理の共通モジュール設計

COBOLの大規模バッチシステムでは、エラー処理・ログ出力・DB接続などを共通モジュール化することで保守性が大幅に向上します。

共通エラーハンドラモジュール

呼び元: エラーハンドラの呼び出し
WORKING-STORAGE SECTION.
01 WS-ERR-INFO.
   05 WS-ERR-CODE      PIC X(4).
   05 WS-ERR-MSG       PIC X(80).
   05 WS-ERR-PGM       PIC X(8).
   05 WS-ERR-SEVERITY  PIC 9(2).    *> 04=警告 08=エラー 12=重大

PROCEDURE DIVISION.
    *> エラー発生時
    MOVE 'E001'        TO WS-ERR-CODE
    MOVE 'ファイルOPENエラー' TO WS-ERR-MSG
    MOVE 'MAIN-PGM'   TO WS-ERR-PGM
    MOVE 8             TO WS-ERR-SEVERITY
    CALL 'ERRHANDL' USING BY REFERENCE WS-ERR-INFO
    END-CALL

    IF RETURN-CODE = 12
        GOBACK    *> 重大エラーは処理中断
    END-IF.
共通エラーハンドラ: ERRHANDL
IDENTIFICATION DIVISION.
PROGRAM-ID. ERRHANDL.

DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TIMESTAMP   PIC X(26).
01 WS-LOG-LINE    PIC X(200).
01 WS-SYSDATE     PIC X(8).

LINKAGE SECTION.
01 LK-ERR-INFO.
   05 LK-ERR-CODE     PIC X(4).
   05 LK-ERR-MSG      PIC X(80).
   05 LK-ERR-PGM      PIC X(8).
   05 LK-ERR-SEVERITY PIC 9(2).

PROCEDURE DIVISION USING LK-ERR-INFO.
    *> タイムスタンプ取得
    MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP

    *> ログ行を組み立て
    STRING WS-TIMESTAMP(1:4) DELIMITED BY SIZE '-'
           DELIMITED BY SIZE
           WS-TIMESTAMP(5:2) DELIMITED BY SIZE '-'
           DELIMITED BY SIZE
           WS-TIMESTAMP(7:2) DELIMITED BY SIZE ' '
           DELIMITED BY SIZE
           WS-TIMESTAMP(9:6)  DELIMITED BY SIZE ' '
           DELIMITED BY SIZE
           '[' DELIMITED BY SIZE
           LK-ERR-PGM         DELIMITED BY SPACE
           '] ['               DELIMITED BY SIZE
           LK-ERR-CODE        DELIMITED BY SIZE
           '] '               DELIMITED BY SIZE
           LK-ERR-MSG         DELIMITED BY SPACE
           INTO WS-LOG-LINE

    *> 重要度に応じて出力先を分ける
    EVALUATE LK-ERR-SEVERITY
        WHEN 4
            DISPLAY '[WARN] ' WS-LOG-LINE
        WHEN 8
            DISPLAY '[ERROR] ' WS-LOG-LINE
        WHEN 12
            DISPLAY '[FATAL] ' WS-LOG-LINE
    END-EVALUATE

    *> 重大エラーはRETURN-CODEに12をセット
    MOVE LK-ERR-SEVERITY TO RETURN-CODE
    GOBACK.

共通ログ出力モジュール(条件付きログレベル)

ログレベル制御付きの共通ログモジュール呼び出し
*=== 呼び元での使い方 ===
WORKING-STORAGE SECTION.
01 WS-LOG-MSG     PIC X(120).
01 WS-LOG-LEVEL   PIC X(1) VALUE 'D'.  *> D=DEBUG I=INFO W=WARN E=ERROR
   88 LOG-DEBUG   VALUE 'D'.
   88 LOG-INFO    VALUE 'I'.

PROCEDURE DIVISION.
    *> DEBUGログ(LOG-LEVELがDの場合のみ出力)
    IF LOG-DEBUG
        MOVE '処理開始: 件数チェック' TO WS-LOG-MSG
        CALL 'LOGWRITE' USING BY CONTENT WS-LOG-LEVEL
                               BY CONTENT WS-LOG-MSG
        END-CALL
    END-IF

    *> INFOログ(常に出力)
    MOVE '処理件数: ' TO WS-LOG-MSG
    STRING WS-LOG-MSG DELIMITED BY SPACE
           WS-REC-COUNT DELIMITED BY SIZE
           '件' DELIMITED BY SIZE
           INTO WS-LOG-MSG
    CALL 'LOGWRITE' USING BY CONTENT 'I'
                           BY CONTENT WS-LOG-MSG
    END-CALL.

呼び出し連鎖の全体像(設計パターン)

バッチジョブの呼び出し連鎖設計例
*=== ジョブ制御(JCL/シェルから起動)===
*
*  MAIN-PGM(ジョブ制御・全体管理)
*    ├─ INITSUB  (初期処理: 設定ロード・接続確立)
*    ├─ OPENSUB  (ファイルオープン)
*    ├─ PROCSUB  (主処理: データ変換・集計)←ループ呼び出し
*    │     └─ VALIDSUB  (入力バリデーション)
*    │     └─ CALCSUB   (金額計算)
*    │     └─ OUTSUB    (出力レコード生成)
*    ├─ CLOSESUB (ファイルクローズ・統計出力)
*    └─ ERRHANDL (共通エラーハンドラ)← 各サブから呼ばれる
*
*--- MAIN-PGM の PROCEDURE DIVISION ---

PROCEDURE DIVISION.
MAIN-LOGIC.
    PERFORM INIT-PHASE
    IF RETURN-CODE NOT = 0
        PERFORM ABNORMAL-END
        STOP RUN
    END-IF

    PERFORM OPEN-PHASE
    IF RETURN-CODE NOT = 0
        PERFORM ABNORMAL-END
        STOP RUN
    END-IF

    PERFORM PROCESS-PHASE UNTIL WS-EOF

    PERFORM CLOSE-PHASE

    IF WS-ERROR-COUNT > 0
        DISPLAY '警告: エラー件数=' WS-ERROR-COUNT
        MOVE 4 TO RETURN-CODE
    ELSE
        MOVE 0 TO RETURN-CODE
    END-IF
    STOP RUN.

INIT-PHASE.
    CALL 'INITSUB' USING WS-CONFIG
    MOVE RETURN-CODE TO WS-RC-SAVE
    IF WS-RC-SAVE NOT = 0
        MOVE WS-RC-SAVE TO RETURN-CODE
    END-IF.

PROCESS-PHASE.
    CALL 'PROCSUB' USING WS-INPUT-REC WS-OUTPUT-REC WS-CTRL-INFO
    IF RETURN-CODE = 8
        ADD 1 TO WS-ERROR-COUNT
    END-IF
    PERFORM READ-NEXT-RECORD.

ABNORMAL-END.
    DISPLAY '異常終了: RC=' RETURN-CODE
    CALL 'CLOSESUB' USING WS-FILE-STATUS.   *> クローズだけは確実に実行

よくある落とし穴と対策

落とし穴 症状 対策
サブプログラムでSTOP RUNを使う ジョブ全体が終了し、呼び元のCLOSEやロールバックが実行されない サブプログラムの終了は必ずGOBACKを使う
CALL直後にRETURN-CODEを確認しない 次のCALL文でRETURN-CODEが上書きされ、最初のエラーが消える CALL直後にMOVE RETURN-CODE TO WS-RC-SAVEで退避してから判定
動的CALLのモジュール名の末尾スペース PIC X(8)変数に’TAXCALC’を入れると’TAXCALC ‘(末尾スペース付き)になり、モジュールが見つからない MOVE ‘TAXCALC’ TO WS-MOD-NAME後にINSPECT … REPLACING TRAILING SPACESするか、FUNCTION TRIM(WS-MOD-NAME)を使う
CANCELせずにループ内で動的CALL ロードされたモジュールのWS値が引き継がれ、前回の残存データが混入する ループ内で毎回初期化が必要なモジュールはCALL前にCANCELを実行する
ネストプログラムを外部から直接CALL ネストプログラムは親プログラム内からしかCALLできない。別プログラムから呼ぶとエラー 複数プログラムから共有したいモジュールは別ファイルの外部プログラムとして定義する

よくある質問

QCALLとPERFORMの違いは何ですか?
APERFORMは同一プログラム内の段落(パラグラフ)を呼び出します。CALLは別プログラム(別のPROGRAM-ID)を呼び出します。別ファイルにコンパイルされたプログラムを呼ぶにはCALLが必要です。同一ソースファイル内のネストプログラムもCALLで呼びます。
QCALLのプログラム名に変数を使うと性能が落ちますか?
Aはい、若干のオーバーヘッドがあります。動的CALLは実行時にモジュールをロードするため、静的CALLよりも初回呼び出しが遅くなります。ただし2回目以降はメモリにキャッシュされるため差は小さくなります。頻繁に呼ぶ処理は静的CALLを使い、動的に切り替えが必要な処理だけ動的CALLにするのが効率的です。
Qサブプログラムから別のサブプログラムを呼び出せますか?
Aはい、何階層でもCALL連鎖ができます。ただし呼び出し深度が深すぎるとスタックオーバーフローのリスクがあります。実務では3〜5階層程度が限界の目安です。また各階層でRETURN-CODEを正しく退避・伝播する設計が重要です。
QIDENTIFICATION DIVISION の END PROGRAM は必須ですか?
Aネストプログラムを使う場合は必須です。ネストプログラムはEND PROGRAM 内部プログラム名で終端を示し、その後に親プログラムのEND PROGRAM 親プログラム名が来ます。ネストプログラムを使わない通常のプログラムでは省略できますが、可読性のために書いても問題ありません。
QCALL ‘SUBPGM’とCALL ‘SUBPGM’ END-CALLの違いは何ですか?
AON EXCEPTION / NOT ON EXCEPTIONを使う場合はEND-CALLが必要です。使わない場合はピリオドで終わらせることもできますが、END-CALLを書く方が「CALLの範囲がここまで」と明示できて保守しやすいです。特にIF文の中にCALLを書く場合は、END-CALLで閉じると意図しないピリオドの範囲問題を防げます。

まとめ

CALL文を使いこなすためのポイントを整理します。

命令/方式 特徴
静的CALL リテラルでプログラム名指定。リンク時結合。高速・モジュール固定
動的CALL 変数でプログラム名指定。実行時ロード。切り替え可能・ON EXCEPTIONで失敗検知
GOBACK サブプログラム終了命令。呼び元に戻る。サブでSTOP RUNは絶対禁止
RETURN-CODE 戻りコード。CALL直後にWS変数へ退避する習慣を
CANCEL ロード済みモジュールを解放。次のCALLでWS再初期化
ネストプログラム 親専用の内部プログラム。GLOBAL句で変数共有可能
EXTERNAL句 プログラム間で変数を共有。設定値や共通エラー情報に使う

CALL設計の核心は「サブプログラムの終了はGOBACK・RETURN-CODEは即座に退避・エラーは必ず呼び元に伝播」の3原則です。この原則を守るだけで、連鎖するCALLでのデバッグが格段に楽になります。

引数の渡し方(BY REFERENCE/BY CONTENT/BY VALUE)とLINKAGE SECTIONの詳細については引数の使い方完全ガイドを参照してください。サブプログラムの戻り方についてはRETURN文とGOBACKの解説も合わせてご覧ください。