CTS-KB
COBOL 2 / 5

COBOL モジュール設計

#COBOL #モジュール設計 #構造化

対象: GnuCOBOL 4 / Free Format 基本文法は COBOL 文法基礎 を参照


目次

  1. モジュール化の全体像
  2. SECTION によるモジュール化(1ファイル内)
  3. 内部サブプログラム(NESTED)
  4. 外部サブプログラム(CALL)
  5. LINKAGE SECTION とパラメータ受け渡し
  6. COPY 句(コード共有)
  7. コンパイルとリンク
  8. 設計パターン
  9. 実践例:給与計算モジュール
  10. COBOLとDDD的設計思想

1. モジュール化の全体像

COBOLでコードを分割・再利用する方法は3段階ある。

1. モジュール化の全体像

SECTION/PARAGRAPH

(1ファイル内)

変数を共有

NESTED PROGRAM

(1ファイル内)

独立した変数スコープ

外部 CALL

(複数ファイル)

独立コンパイル


2. SECTION によるモジュール化(1ファイル内)

最もシンプルな構造化。変数はすべて共有される。

PROCEDURE DIVISION.
MAIN-SECTION SECTION.
    PERFORM INPUT-SECTION
    PERFORM CALC-SECTION
    PERFORM OUTPUT-SECTION
    STOP RUN.

INPUT-SECTION SECTION.
INPUT-PARA.
    DISPLAY "Enter value:"
    ACCEPT WS-INPUT FROM CONSOLE.

CALC-SECTION SECTION.
CALC-PARA.
    COMPUTE WS-RESULT = WS-INPUT * 2.

OUTPUT-SECTION SECTION.
OUTPUT-PARA.
    DISPLAY "Result: " WS-RESULT.

特徴:

  • PERFORM で呼ぶだけなので手軽
  • 全ての変数が見えてしまう(カプセル化なし)
  • 小規模プログラムに最適

3. 内部サブプログラム(NESTED)

1つのファイル内に複数のプログラムを定義する。変数スコープが独立する。

3.1 基本構造

*> === メインプログラム ===
IDENTIFICATION DIVISION.
    PROGRAM-ID. MAIN-PROG.

DATA DIVISION.
WORKING-STORAGE SECTION.
    77 WS-NUM1    PIC 9(5) VALUE 10.
    77 WS-NUM2    PIC 9(5) VALUE 20.
    77 WS-RESULT  PIC 9(10).

PROCEDURE DIVISION.
MAIN-PARA.
    *> 内部プログラムを CALL で呼ぶ
    CALL "ADD-NUMS"
        USING WS-NUM1 WS-NUM2 WS-RESULT
    END-CALL
    DISPLAY "Result: " WS-RESULT
    STOP RUN.

*> === 内部サブプログラム ===
*> メインプログラムの END の前に定義する
IDENTIFICATION DIVISION.
    PROGRAM-ID. ADD-NUMS.

DATA DIVISION.
LINKAGE SECTION.
    *> 呼び出し元から受け取るパラメータ
    77 LK-A       PIC 9(5).
    77 LK-B       PIC 9(5).
    77 LK-RESULT  PIC 9(10).

PROCEDURE DIVISION USING LK-A LK-B LK-RESULT.
    COMPUTE LK-RESULT = LK-A + LK-B
    GOBACK.                          *> 呼び出し元に戻る

END PROGRAM ADD-NUMS.
END PROGRAM MAIN-PROG.

3.2 ポイント

  • 内部プログラムは END PROGRAM 内部名.END PROGRAM メイン名. の順で閉じる
  • サブプログラムの終了は STOP RUN ではなく GOBACK
  • 変数スコープが完全に分離される(メインの WS-* は見えない)
  • パラメータは USING で受け渡す

3.3 COMMON 属性(兄弟プログラム間の呼び出し)

*> COMMON を付けると、同じ階層の他の内部プログラムからも呼べる
IDENTIFICATION DIVISION.
    PROGRAM-ID. UTIL-PROG IS COMMON.

通常の内部プログラムは親からしか呼べないが、COMMON を付けると兄弟からも呼び出し可能。


4. 外部サブプログラム(CALL)

別ファイルに定義したプログラムを呼び出す。最も本格的なモジュール化。

4.1 呼び出し側(メインプログラム)

*> ファイル: main.cob
IDENTIFICATION DIVISION.
    PROGRAM-ID. MAIN-PROG.

DATA DIVISION.
WORKING-STORAGE SECTION.
    77 WS-PRICE     PIC 9(7)V99   VALUE 1000.00.
    77 WS-TAX-RATE  PIC 9V99      VALUE 0.10.
    77 WS-TAX       PIC 9(7)V99.
    77 WS-TOTAL     PIC 9(7)V99.
    77 WS-RETURN-CD PIC 9         VALUE 0.

PROCEDURE DIVISION.
MAIN-PARA.
    DISPLAY "Price: " WS-PRICE

    *> 外部プログラムを呼び出す
    CALL "CALC-TAX"
        USING
            BY CONTENT   WS-PRICE      *> 読み取り専用で渡す
            BY CONTENT   WS-TAX-RATE   *> 読み取り専用で渡す
            BY REFERENCE WS-TAX        *> 書き込み可能で渡す
            BY REFERENCE WS-TOTAL      *> 書き込み可能で渡す
        RETURNING WS-RETURN-CD
    END-CALL

    *> 呼び出し結果を確認
    IF WS-RETURN-CD = 0
        DISPLAY "Tax  : " WS-TAX
        DISPLAY "Total: " WS-TOTAL
    ELSE
        DISPLAY "Error in CALC-TAX"
    END-IF

    STOP RUN.

4.2 呼び出される側(サブプログラム)

*> ファイル: calc-tax.cob
IDENTIFICATION DIVISION.
    PROGRAM-ID. CALC-TAX.

DATA DIVISION.

*> WORKING-STORAGE: サブプログラム内部だけで使う変数
WORKING-STORAGE SECTION.
    77 WS-WORK   PIC 9(10)V99.

*> LINKAGE SECTION: 呼び出し元とのインターフェース
LINKAGE SECTION.
    77 LK-PRICE     PIC 9(7)V99.
    77 LK-TAX-RATE  PIC 9V99.
    77 LK-TAX       PIC 9(7)V99.
    77 LK-TOTAL     PIC 9(7)V99.

*> USING で受け取る順番は CALL の USING と一致させる
PROCEDURE DIVISION
    USING LK-PRICE LK-TAX-RATE LK-TAX LK-TOTAL
    RETURNING WS-RETURN-CD.

    77 WS-RETURN-CD PIC 9.

COMPUTE-PARA.
    COMPUTE LK-TAX  = LK-PRICE * LK-TAX-RATE
    COMPUTE LK-TOTAL = LK-PRICE + LK-TAX
    MOVE 0 TO WS-RETURN-CD
    GOBACK.                 *> STOP RUN ではなく GOBACK で戻る

4.3 パラメータの渡し方

渡し方意味サブ側で変更呼び出し元に反映
BY REFERENCEアドレス渡し(デフォルト)可能される
BY CONTENT値のコピー渡し可能(コピーを変更)されない
BY VALUEC言語互換の値渡し可能(ローカルコピー)されない
RETURNING戻り値(1つだけ)-される
*> BY REFERENCE(デフォルト): 省略すると BY REFERENCE になる
CALL "SUB" USING WS-A WS-B          *> 両方 BY REFERENCE

*> 混在も可能
CALL "SUB" USING
    BY CONTENT   WS-INPUT            *> 入力(変更されたくない)
    BY REFERENCE WS-OUTPUT           *> 出力(結果を受け取る)
END-CALL

使い分けの指針:

  • 入力パラメータ → BY CONTENT(意図しない変更を防ぐ)
  • 出力パラメータ → BY REFERENCE(結果を受け取る)
  • 戻り値(ステータス等) → RETURNING

5. LINKAGE SECTION とパラメータ受け渡し

5.1 基本ルール

DATA DIVISION.

*> WORKING-STORAGE: サブ内部の変数(呼び出し毎に値を保持)
WORKING-STORAGE SECTION.
    77 WS-CALL-COUNT PIC 9(5) VALUE 0.

*> LOCAL-STORAGE: サブ内部の変数(呼び出し毎に初期化される)
LOCAL-STORAGE SECTION.
    77 LS-TEMP PIC X(100).

*> LINKAGE: 呼び出し元との受け渡し変数
LINKAGE SECTION.
    77 LK-INPUT  PIC X(50).
    77 LK-OUTPUT PIC X(100).
セクション初期化タイミング用途
WORKING-STORAGEプログラムロード時(1回)呼び出し間で値を保持したい変数
LOCAL-STORAGE呼び出し毎毎回初期化したいワーク変数
LINKAGE呼び出し元が確保パラメータの受け渡し

5.2 グループ項目でのパラメータ受け渡し

個別に変数を並べるより、グループ項目でまとめると管理しやすい。

*> --- 呼び出し側 ---
01 WS-TAX-REQUEST.
    05 WS-REQ-PRICE     PIC 9(7)V99.
    05 WS-REQ-TAX-RATE  PIC 9V99.

01 WS-TAX-RESPONSE.
    05 WS-RES-TAX       PIC 9(7)V99.
    05 WS-RES-TOTAL     PIC 9(7)V99.
    05 WS-RES-RETURN-CD PIC 9.

CALL "CALC-TAX"
    USING BY CONTENT   WS-TAX-REQUEST
          BY REFERENCE WS-TAX-RESPONSE
END-CALL

*> --- サブプログラム側 ---
LINKAGE SECTION.
01 LK-REQUEST.
    05 LK-PRICE     PIC 9(7)V99.
    05 LK-TAX-RATE  PIC 9V99.

01 LK-RESPONSE.
    05 LK-TAX       PIC 9(7)V99.
    05 LK-TOTAL     PIC 9(7)V99.
    05 LK-RETURN-CD PIC 9.

PROCEDURE DIVISION USING LK-REQUEST LK-RESPONSE.

5.3 WORKING-STORAGE vs LOCAL-STORAGE の違い

*> WORKING-STORAGE: 呼び出し間で値を保持する
*> 2回目の CALL でも前回の値が残っている
WORKING-STORAGE SECTION.
    77 WS-CALL-COUNT PIC 9(5) VALUE 0.

*> 呼ばれるたびにカウントアップ
PROCEDURE DIVISION USING ...
    ADD 1 TO WS-CALL-COUNT
    DISPLAY "Call #" WS-CALL-COUNT
    GOBACK.
*> 1回目: "Call #00001"
*> 2回目: "Call #00002"  ← 値が残っている

*> LOCAL-STORAGE: 呼び出し毎に VALUE で再初期化される
LOCAL-STORAGE SECTION.
    77 LS-TEMP PIC X(100) VALUE SPACES.
*> 毎回 SPACES にリセットされる

6. COPY 句(コード共有)

6.1 基本的な使い方

共通のレコード定義やデータ定義を .cpy ファイルに切り出し、複数プログラムで共有する。

プロジェクト構成:
├── main.cob          COPY "emp-record.cpy" で取り込む
├── report.cob        COPY "emp-record.cpy" で取り込む
└── copybooks/
    └── emp-record.cpy    共通のレコード定義
*> ファイル: copybooks/emp-record.cpy
*> (IDENTIFICATION DIVISION 等は書かない。断片だけ)
01 EMP-RECORD.
    05 EMP-ID          PIC X(5).
    05 EMP-NAME        PIC X(20).
    05 EMP-DEPT        PIC X(8).
    05 EMP-AGE         PIC 9(3).
*> ファイル: main.cob
DATA DIVISION.
FILE SECTION.
FD EMP-FILE.
    COPY "emp-record.cpy".
*> ↑ コンパイル時に emp-record.cpy の内容がここに展開される

WORKING-STORAGE SECTION.
    COPY "emp-record.cpy"
        REPLACING ==EMP-== BY ==WS-EMP-==.
*> ↑ REPLACING: コピー時に名前を置換
*> EMP-ID → WS-EMP-ID, EMP-NAME → WS-EMP-NAME ...

6.2 REPLACING(コピー時の置換)

*> 元の copybook
01 RECORD-DATA.
    05 REC-CODE   PIC X(5).
    05 REC-NAME   PIC X(20).

*> REPLACING で接頭辞を変えて複数回コピー
COPY "record.cpy" REPLACING ==RECORD-== BY ==INPUT-==.
COPY "record.cpy" REPLACING ==RECORD-== BY ==OUTPUT-==.
*> INPUT-DATA, INPUT-CODE ... と OUTPUT-DATA, OUTPUT-CODE ... が定義される

6.3 コンパイル時の COPY パス指定

# -I で copybook のディレクトリを指定
cobc -x -free -I copybooks -o main main.cob

# 複数ディレクトリ
cobc -x -free -I copybooks -I shared/copy -o main main.cob

6.4 COPY の使いどころ

用途
ファイルレコード定義同じファイルを読み書きする複数プログラムで共有
共通データ定義エラーコード、ステータスコード
共通段落日付変換、入力チェックなどの定型処理

7. コンパイルとリンク

7.1 外部 CALL のコンパイル方法

# 方法1: まとめてコンパイル(簡単)
cobc -x -free -o main main.cob calc-tax.cob
# 両方のソースを指定するだけ

# 方法2: 個別コンパイル + リンク(大規模向け)
# ステップ1: サブプログラムをオブジェクトにコンパイル
cobc -free -c calc-tax.cob          # → calc-tax.o が生成される

# ステップ2: メインとリンクして実行ファイル生成
cobc -x -free -o main main.cob calc-tax.o

# 方法3: 動的ライブラリ(.so)として共有
cobc -free -m calc-tax.cob          # → calc-tax.so が生成される
# メインは単独でコンパイル。実行時に .so を探す
cobc -x -free -o main main.cob
COB_LIBRARY_PATH=. ./main           # 実行時にカレントから .so を検索

7.2 コンパイルオプション(モジュール関連)

オプション説明
-cオブジェクトファイル(.o)を生成。リンクしない
-m動的モジュール(.so / .dll)を生成
-x実行ファイルを生成
-I dirCOPY 句の検索ディレクトリを追加
-l libリンクするライブラリを指定

7.3 静的リンク vs 動的リンク

方式コマンドメリットデメリット
静的cobc -x main.cob sub.cob単一実行ファイル、配布が簡単サブ変更時に全体再コンパイル
静的(.o)cobc -c sub.cobcobc -x main.cob sub.oサブだけ再コンパイル可能.o ファイルの管理が必要
動的(.so)cobc -m sub.cobcobc -x main.cob実行時に差し替え可能.so のパス管理が必要

8. 設計パターン

8.1 推奨するプロジェクト構成

project/
├── src/
│   ├── main.cob              メインプログラム
│   ├── process-order.cob     注文処理サブプログラム
│   └── calc-tax.cob          税計算サブプログラム
├── copybooks/
│   ├── order-record.cpy      注文レコード定義
│   ├── product-record.cpy    商品レコード定義
│   └── error-codes.cpy       共通エラーコード
├── data/
│   ├── orders.dat            入力データ
│   └── results.csv           出力データ
├── tests/
│   └── test-calc-tax.cob     テストプログラム
└── docs/
    └── cobol-grammar.md      文法リファレンス

8.2 サブプログラムの設計指針

1. 入出力を明確にする

*> OK: 入力と出力が明確
PROCEDURE DIVISION
    USING LK-INPUT LK-OUTPUT LK-RETURN-CODE.

*> NG: 何が入力で何が出力か分からない
PROCEDURE DIVISION
    USING LK-A LK-B LK-C LK-D LK-E.

2. グループ項目でインターフェースをまとめる

*> OK: リクエスト/レスポンスのパターン
CALL "PROCESS-ORDER"
    USING BY CONTENT   WS-ORDER-REQUEST
          BY REFERENCE WS-ORDER-RESPONSE
END-CALL

*> NG: パラメータを10個バラバラに渡す
CALL "PROCESS-ORDER"
    USING WS-A WS-B WS-C WS-D WS-E
          WS-F WS-G WS-H WS-I WS-J
END-CALL

3. リターンコードを返す

*> 成功/失敗を呼び出し元が判断できるようにする
01 LK-RESPONSE.
    05 LK-RETURN-CD    PIC 9.
       88 LK-SUCCESS   VALUE 0.
       88 LK-ERROR     VALUE 1.
    05 LK-ERROR-MSG    PIC X(50).

4. GOBACK を使う(STOP RUN を使わない)

*> OK: 呼び出し元に制御を返す
GOBACK.

*> NG: プログラム全体が終了してしまう
STOP RUN.

8.3 モジュール化のステップ

プログラムが大きくなったら、段階的にモジュール化する。

8.3 モジュール化のステップ

ステップ1: SECTION/PARAGRAPH で整理

1ファイル内で処理を段落に分ける

ステップ2: COPY 句でデータ定義を共有

レコード定義を .cpy に切り出す

ステップ3: 外部 CALL でプログラムを分離

業務ロジックをサブプログラム化

判断基準:

8.3 モジュール化のステップ

Yes

No

Yes

No

Yes

No

同じレコード定義を

2箇所以上で使う?

COPY 句

他のプログラムでも

使いたい?

外部 CALL

変数スコープを

分離したい?

NESTED or 外部 CALL

SECTION/PARAGRAPH で十分


9. 実践例:給与計算モジュール

ここまでの知識を組み合わせた実動サンプル。3ファイル構成で COPY + 外部 CALL を実践する。

9.1 ファイル構成

├── salary-main.cob           メインプログラム(呼び出し側)
├── calc-salary.cob           給与計算サブプログラム(呼ばれる側)
└── copybooks/
    └── salary-record.cpy     共通レコード定義(COPY で共有)

9.2 共通レコード定義

copybooks/salary-record.cpy

*> --- 入力: 給与計算リクエスト ---
01 SALARY-REQUEST.
    05 SR-EMP-ID          PIC X(5).
    05 SR-EMP-NAME        PIC X(20).
    05 SR-BASE-SALARY     PIC 9(7).
    05 SR-OVERTIME-HOURS  PIC 9(3).
    05 SR-OVERTIME-RATE   PIC 9(5).

*> --- 出力: 給与計算レスポンス ---
01 SALARY-RESPONSE.
    05 SS-BASE-SALARY     PIC 9(7).
    05 SS-OVERTIME-PAY    PIC 9(7).
    05 SS-GROSS-SALARY    PIC 9(8).
    05 SS-TAX             PIC 9(7).
    05 SS-INSURANCE       PIC 9(7).
    05 SS-NET-SALARY      PIC 9(8).
    05 SS-RETURN-CD       PIC 9.
       88 SS-SUCCESS      VALUE 0.
       88 SS-ERROR        VALUE 1.
    05 SS-ERROR-MSG       PIC X(30).

ポイント:

  • リクエスト(入力)とレスポンス(出力)を明確に分離
  • リターンコード + エラーメッセージで結果を通知
  • 88条件名でエラー判定を読みやすく

9.3 サブプログラム(業務ロジック)

calc-salary.cob

LINKAGE SECTION.
COPY "salary-record.cpy".

PROCEDURE DIVISION
    USING SALARY-REQUEST SALARY-RESPONSE.

CALC-MAIN.
    PERFORM VALIDATE-PARA       *> 入力チェック
    IF SS-ERROR
        GOBACK
    END-IF
    PERFORM CALC-GROSS-PARA     *> 総支給額
    PERFORM CALC-DEDUCTION-PARA *> 控除計算
    SET SS-SUCCESS TO TRUE
    GOBACK.

ポイント:

  • COPY で共通レコードを LINKAGE に取り込む
  • バリデーション → 計算 → 結果セットの流れ
  • エラー時は早期 GOBACK

9.4 メインプログラム(呼び出し側)

salary-main.cob

WORKING-STORAGE SECTION.
COPY "salary-record.cpy".      *> 同じレコード定義を共有

PROCEDURE DIVISION.
MAIN-PARA.
    *> データをセットして CALL するだけ
    INITIALIZE SALARY-REQUEST
    MOVE "E001"        TO SR-EMP-ID
    MOVE "TANAKA TARO" TO SR-EMP-NAME
    MOVE 250000        TO SR-BASE-SALARY
    MOVE 20            TO SR-OVERTIME-HOURS
    MOVE 1500          TO SR-OVERTIME-RATE

    CALL "CALC-SALARY"
        USING BY CONTENT   SALARY-REQUEST
              BY REFERENCE SALARY-RESPONSE
    END-CALL

9.5 コンパイルと実行結果

cobc -x -free -I copybooks -o salary-main salary-main.cob calc-salary.cob
./salary-main
==============================
 Salary Calculation Demo
==============================

--- E001 : TANAKA TARO          ---
  Base Salary  :     250,000 yen
  Overtime Pay :      30,000 yen
  Gross Salary :     280,000 yen
  Tax          :      28,000 yen
  Insurance    :      42,000 yen
  ---------------------
  Net Salary   :     210,000 yen

--- E002 : SUZUKI HANAKO        ---
  Base Salary  :     450,000 yen
  Overtime Pay :           0 yen
  Gross Salary :     450,000 yen
  Tax          :      45,000 yen
  Insurance    :      67,500 yen
  ---------------------
  Net Salary   :     337,500 yen

--- E003 : ERROR CASE           ---
  ERROR: Base salary is zero

10. COBOLとDDD的設計思想

COBOLは1959年に生まれた言語だが、その設計思想には現代のDDD(ドメイン駆動設計)と驚くほど共通する概念がある。

DDD の概念                    COBOLでの対応
───────────────────────────────────────────────────────
ユビキタス言語                COBOL自体が「業務用語で書く言語」
値オブジェクト                COPY 句のレコード定義
エンティティ                  FD + レコード(一意キーを持つ)
集約                          グループ項目(01レベル)
境界づけられたコンテキスト    プログラム単位(PROGRAM-ID)
ドメインサービス              外部サブプログラム(CALL)
リポジトリ                    ファイル操作(OPEN/READ/WRITE/CLOSE)

以下、給与計算を題材に 4つの観点 から段階的にコード化する過程を示す。


10.1 業務ルールの明確化

最初にやること: コードを書く前に、業務ルールを日本語で書き出す。

【給与計算の業務ルール】

1. 総支給額 = 基本給 + 残業代
2. 残業代 = 残業時間 × 残業単価
3. 税率は総支給額で変わる:
   - 500万以上 → 30%
   - 300万以上 → 20%
   - それ以外  → 10%
4. 社会保険料 = 総支給額 × 15%
5. 手取り = 総支給額 - 税金 - 社会保険料
6. 基本給が0の場合はエラー

COBOLでは、この業務ルールが そのまま変数名と段落名 になる。

*> 業務用語 → 変数名(ユビキタス言語)
*>
*>   基本給     → BASE-SALARY
*>   残業代     → OVERTIME-PAY
*>   残業時間   → OVERTIME-HOURS
*>   残業単価   → OVERTIME-RATE
*>   総支給額   → GROSS-SALARY
*>   税金       → TAX
*>   社会保険料 → INSURANCE
*>   手取り     → NET-SALARY

*> 業務ルール → 段落名
*>
*>   ルール1-2  → CALC-GROSS-PARA      (総支給額の計算)
*>   ルール3-4  → CALC-DEDUCTION-PARA  (控除の計算)
*>   ルール6    → VALIDATE-PARA        (入力チェック)

なぜ先に書き出すか:

  • 変数名に迷わなくなる(業務用語 = 変数名)
  • 段落の分け方が自然に決まる(1ルール = 1段落)
  • 仕様変更時に「どの段落を直すか」がすぐ分かる

10.2 データ構造との整合性

業務ルールを決めたら、次にデータ構造を定義する。

ここで重要なのは、データ構造が業務の言葉と一致していること。

*> ============================================================
*> copybooks/salary-record.cpy
*> 業務ルールで使う用語がそのままフィールド名になっている
*> ============================================================

*> --- 入力: 計算に必要な情報(リクエスト) ---
01 SALARY-REQUEST.
    05 SR-EMP-ID          PIC X(5).     *> 社員番号
    05 SR-EMP-NAME        PIC X(20).    *> 社員名
    05 SR-BASE-SALARY     PIC 9(7).     *> 基本給(最大9,999,999)
    05 SR-OVERTIME-HOURS  PIC 9(3).     *> 残業時間(最大999)
    05 SR-OVERTIME-RATE   PIC 9(5).     *> 残業単価(最大99,999)

*> --- 出力: 計算結果(レスポンス) ---
01 SALARY-RESPONSE.
    05 SS-BASE-SALARY     PIC 9(7).     *> 基本給(エコーバック)
    05 SS-OVERTIME-PAY    PIC 9(7).     *> 残業代
    05 SS-GROSS-SALARY    PIC 9(8).     *> 総支給額
    05 SS-TAX             PIC 9(7).     *> 税金
    05 SS-INSURANCE       PIC 9(7).     *> 社会保険料
    05 SS-NET-SALARY      PIC 9(8).     *> 手取り
    05 SS-RETURN-CD       PIC 9.        *> 結果コード
       88 SS-SUCCESS      VALUE 0.      *>   0 = 正常
       88 SS-ERROR        VALUE 1.      *>   1 = エラー
    05 SS-ERROR-MSG       PIC X(30).    *> エラーメッセージ

設計のポイント:

原則実践
入力と出力を分けるSALARY-REQUEST(入力)と SALARY-RESPONSE(出力)を別のグループ項目にする
PICの桁数は業務要件から決める基本給が最大999万 → PIC 9(7)
結果コードを必ず含めるSS-RETURN-CD + 88条件名で成功/失敗を判定
接頭辞で所属を示すSR- = Request、SS- = reSponse
COPYで共有する呼び出し側とサブプログラムで同じ定義を使い、不整合を防ぐ

PICの桁数を間違えるとどうなるか:

*> NG: 基本給を PIC 9(5) にしてしまった場合
05 SR-BASE-SALARY  PIC 9(5).     *> 最大 99,999

*> MOVE 250000 TO SR-BASE-SALARY
*> → 250000 は6桁なので、上位桁が切り捨てられて 50000 になる!
*> COBOLはエラーにならない。静かに壊れる。

*> → 業務要件「基本給は最大999万円」を先に確認してから PIC を決める

10.3 段落・サブルーチンによる整理

「1つの段落 = 1つの業務ルール」で処理を分ける。

*> ============================================================
*> calc-salary.cob(サブプログラム)
*> 業務ルールが段落に対応している
*> ============================================================
PROCEDURE DIVISION
    USING SALARY-REQUEST SALARY-RESPONSE.

*> --- メイン段落: 処理の流れだけ書く ---
CALC-MAIN.
    PERFORM VALIDATE-PARA            *> ルール6: 入力チェック
    IF SS-ERROR
        GOBACK
    END-IF
    PERFORM CALC-GROSS-PARA          *> ルール1-2: 総支給額
    PERFORM CALC-DEDUCTION-PARA      *> ルール3-5: 控除と手取り
    SET SS-SUCCESS TO TRUE
    GOBACK.

メイン段落を見るだけで「何をしているか」が分かる。詳細は各段落に隠蔽されている。

*> --- ルール6: 入力バリデーション ---
*> 「基本給が0ならエラー」という業務ルールがそのままコードに
VALIDATE-PARA.
    IF SR-BASE-SALARY = 0
        SET SS-ERROR TO TRUE
        MOVE "Base salary is zero" TO SS-ERROR-MSG
        EXIT PARAGRAPH               *> 早期リターン
    END-IF
    IF SR-BASE-SALARY > 9999999
        SET SS-ERROR TO TRUE
        MOVE "Base salary overflow" TO SS-ERROR-MSG
    END-IF.
*> --- ルール1-2: 総支給額の計算 ---
*> 「残業代 = 残業時間 × 残業単価」がそのまま COMPUTE に
CALC-GROSS-PARA.
    MOVE SR-BASE-SALARY TO SS-BASE-SALARY

    COMPUTE SS-OVERTIME-PAY =
        SR-OVERTIME-HOURS * SR-OVERTIME-RATE

    COMPUTE SS-GROSS-SALARY =
        SS-BASE-SALARY + SS-OVERTIME-PAY.
*> --- ルール3-5: 控除の計算 ---
*> 「税率は総支給額で変わる」→ EVALUATE TRUE で分岐
CALC-DEDUCTION-PARA.
    EVALUATE TRUE
        WHEN SS-GROSS-SALARY >= 5000000
            MOVE 0.30 TO WS-TAX-RATE
        WHEN SS-GROSS-SALARY >= 3000000
            MOVE 0.20 TO WS-TAX-RATE
        WHEN OTHER
            MOVE 0.10 TO WS-TAX-RATE
    END-EVALUATE

    COMPUTE SS-TAX = SS-GROSS-SALARY * WS-TAX-RATE

    COMPUTE SS-INSURANCE = SS-GROSS-SALARY * 0.15

    COMPUTE SS-NET-SALARY =
        SS-GROSS-SALARY - SS-TAX - SS-INSURANCE.

段落分けの判断基準:

10.3 段落・サブルーチンによる整理

言える

言えない(いろいろになる)

この段落は何をしているか?

一言で言えるか?

OK(適切な粒度)

例: 入力チェック / 総支給額の計算 / 控除の計算

分割すべき

例: チェックして計算して書き出す → 3つの段落に分ける


10.4 条件分岐とループの活用

業務ルールの大半は「条件による場合分け」と「繰り返し処理」で構成される。

IF + 88条件名 = 業務ルールを読みやすく

*> --- 88条件名を使う(推奨) ---
01 WS-EMP-TYPE   PIC X(1).
    88 IS-REGULAR     VALUE "R".    *> 正社員
    88 IS-CONTRACT    VALUE "C".    *> 契約社員
    88 IS-PART-TIME   VALUE "P".    *> パート

*> 業務ルール: 正社員だけボーナスがある
IF IS-REGULAR
    PERFORM CALC-BONUS-PARA
END-IF

*> 業務ルール: パートには残業代を付けない
IF IS-PART-TIME
    MOVE 0 TO SS-OVERTIME-PAY
END-IF

88条件名を使うと WS-EMP-TYPE = "R" より 業務の言葉 で読める。

EVALUATE = 複雑な業務ルールの場合分け

*> 業務ルール: 勤続年数で昇給率を決定
EVALUATE TRUE
    WHEN WS-YEARS-OF-SERVICE >= 20
        MOVE 0.15 TO WS-RAISE-RATE
        MOVE "Senior" TO WS-GRADE
    WHEN WS-YEARS-OF-SERVICE >= 10
        MOVE 0.10 TO WS-RAISE-RATE
        MOVE "Mid-level" TO WS-GRADE
    WHEN WS-YEARS-OF-SERVICE >= 3
        MOVE 0.05 TO WS-RAISE-RATE
        MOVE "Junior" TO WS-GRADE
    WHEN OTHER
        MOVE 0.00 TO WS-RAISE-RATE
        MOVE "Trainee" TO WS-GRADE
END-EVALUATE

PERFORM VARYING = 全社員をループ処理

*> 業務ルール: 社員ファイルを読んで全員の給与を計算する
PERFORM UNTIL WS-EOF
    READ EMP-FILE
        AT END
            SET WS-EOF TO TRUE
        NOT AT END
            *> 読み込んだ社員データから計算リクエストを作る
            MOVE EMP-ID          TO SR-EMP-ID
            MOVE EMP-NAME        TO SR-EMP-NAME
            MOVE EMP-BASE-SALARY TO SR-BASE-SALARY
            MOVE EMP-OT-HOURS    TO SR-OVERTIME-HOURS
            MOVE EMP-OT-RATE     TO SR-OVERTIME-RATE

            *> 給与計算サブプログラムを呼ぶ
            CALL "CALC-SALARY"
                USING BY CONTENT   SALARY-REQUEST
                      BY REFERENCE SALARY-RESPONSE
            END-CALL

            *> 結果に応じて処理を分岐
            IF SS-SUCCESS
                PERFORM WRITE-RESULT-PARA
                ADD 1 TO WS-SUCCESS-COUNT
            ELSE
                PERFORM WRITE-ERROR-PARA
                ADD 1 TO WS-ERROR-COUNT
            END-IF
    END-READ
END-PERFORM

DISPLAY "Processed: " WS-SUCCESS-COUNT " OK, "
    WS-ERROR-COUNT " errors"

ループ + 条件分岐の組み合わせパターン

業務でよくあるパターン:

1. ファイル全件処理
   PERFORM UNTIL EOF → READ → 処理 → END-PERFORM

2. 条件に合うレコードだけ処理
   PERFORM UNTIL EOF → READ → IF 条件 THEN 処理 → END-PERFORM

3. 集計処理(合計、件数、平均)
   PERFORM UNTIL EOF → READ → ADD → END-PERFORM → 最後にDISPLAY

4. マスタ突き合わせ
   PERFORM UNTIL EOF → READ マスタ → READ トランザクション
   → キー比較して処理を分岐

10.5 段階的に業務ロジックをコード化する

DDDと同じく、COBOLでもいきなり全部作らない。業務理解を深めながら段階的に進める。

Phase 1: 業務ルールの明確化(10.1)
  ・業務ルールを日本語で書き出す
  ・用語を統一する(ユビキタス言語)
  ・変数名・段落名を業務用語から決める

Phase 2: データ構造の定義(10.2)
  ・copybook にレコード定義を書く
  ・PIC の桁数は業務要件から決める
  ・入力(Request)と出力(Response)を分ける

Phase 3: 段落による処理の整理(10.3)
  ・1段落 = 1業務ルール
  ・メイン段落は「流れ」だけ書く
  ・詳細は各段落に隠蔽する

Phase 4: 条件分岐とループの実装(10.4)
  ・88条件名で業務ルールを表現する
  ・EVALUATE で場合分けを整理する
  ・PERFORM UNTIL でファイル全件処理する

Phase 5: サブプログラムへの切り出し
  ・再利用可能な業務ロジックを外部 CALL 化
  ・LINKAGE で入出力を明示的に定義する
  ・COPY で共通定義を共有する

給与計算サンプルの全体構造をDDD的に整理すると:

10.5 段階的に業務ロジックをコード化する

値オブジェクト

ドメインサービス層

アプリケーション層

CALL

COPY

COPY

salary-main.cob

ユースケースの組み立て

calc-salary.cob

業務ロジック

VALIDATE-PARA

入力チェック

CALC-GROSS-PARA

総支給額計算

CALC-DEDUCTION-PARA

控除計算

salary-record.cpy

共通データ構造

COBOLの「冗長に見える構文」は、実は業務ロジックを正確にコード化するための仕組み。DDDが2003年に体系化した概念を、COBOLは1959年から実践していた。


関連記事