対象: GnuCOBOL 4 / Free Format 基本文法は COBOL 文法基礎 を参照
目次
- モジュール化の全体像
- SECTION によるモジュール化(1ファイル内)
- 内部サブプログラム(NESTED)
- 外部サブプログラム(CALL)
- LINKAGE SECTION とパラメータ受け渡し
- COPY 句(コード共有)
- コンパイルとリンク
- 設計パターン
- 実践例:給与計算モジュール
- COBOLとDDD的設計思想
1. モジュール化の全体像
COBOLでコードを分割・再利用する方法は3段階ある。
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 VALUE | C言語互換の値渡し | 可能(ローカルコピー) | されない |
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 dir | COPY 句の検索ディレクトリを追加 |
-l lib | リンクするライブラリを指定 |
7.3 静的リンク vs 動的リンク
| 方式 | コマンド | メリット | デメリット |
|---|---|---|---|
| 静的 | cobc -x main.cob sub.cob | 単一実行ファイル、配布が簡単 | サブ変更時に全体再コンパイル |
| 静的(.o) | cobc -c sub.cob → cobc -x main.cob sub.o | サブだけ再コンパイル可能 | .o ファイルの管理が必要 |
| 動的(.so) | cobc -m sub.cob → cobc -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 モジュール化のステップ
プログラムが大きくなったら、段階的にモジュール化する。
判断基準:
9. 実践例:給与計算モジュール
ここまでの知識を組み合わせた実動サンプル。3ファイル構成で COPY + 外部 CALL を実践する。
9.1 ファイル構成
├── salary-main.cob メインプログラム(呼び出し側)
├── calc-salary.cob 給与計算サブプログラム(呼ばれる側)
└── copybooks/
└── salary-record.cpy 共通レコード定義(COPY で共有)
9.2 共通レコード定義
*> --- 入力: 給与計算リクエスト ---
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 サブプログラム(業務ロジック)
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 メインプログラム(呼び出し側)
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.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的に整理すると:
COBOLの「冗長に見える構文」は、実は業務ロジックを正確にコード化するための仕組み。DDDが2003年に体系化した概念を、COBOLは1959年から実践していた。
関連記事
- RPG モジュール設計 — RPG IV の対応するモジュール化手法
- COBOL 文法基礎 — COBOL の基本文法リファレンス
- COBOL 業務ロジック実装 — 業務システムの設計パターン
- COBOL 本番運用 — 本番環境での運用ガイド