はじめに
メインフレームのCOBOLプログラミングでは、プログラムの規模が大きくなるにつれて、変数名の管理が複雑になりがちです。特に、入れ子になったサブルーチンやパラグラフ(プロシージャ)間での変数の受け渡しは、意図しないバグの原因となることがあります。
この「LOCAL」や「NONLOCAL」といった概念(COBOLでは直接的なキーワードではありませんが、その考え方を理解することが重要です)を理解することで、変数のスコープ(有効範囲)を明確に制御し、プログラムの可読性や保守性を向上させることができます。
「LOCAL」属性(またはそれに相当する考え方)は、変数がその定義されたブロック内でのみ有効であることを保証します。これにより、親ブロックや他のブロックで同じ名前の変数があっても、互いに影響を与えることを防ぎます。これは、変数名の衝突による意図しないデータの上書きといった、ありがちなバグを防ぐための強力な手段となります。
基礎知識:スコープとは?
プログラムにおける「スコープ」とは、ある変数が参照可能(使用可能)な範囲のことを指します。COBOLでは、プログラム全体、`PROCEDURE DIVISION`内の各`PARAGRAPH`や`SECTION`、あるいは`CALL`で呼び出される外部サブルーチンなどが、それぞれ独立したスコープを持つと考えることができます。
通常、COBOLでは、上位のプログラム(呼び出し元)で定義された変数は、`CALL`文で呼び出された下位プログラム(呼び出し先)から参照できる場合があります。これを「ホスト結合」や「暗黙の変数受け渡し」と呼ぶことがあります。これは便利である反面、意図せずに上位の変数を下位プログラムで変更してしまうリスクをはらんでいます。
JavaScriptのクロージャにおけるスコープの考え方に似ています。親スコープの変数を子スコープから参照できる(または変更できる)という点が共通しています。
実装/解決策:LOCAL/NONLOCALの考え方をCOBOLで実現する
COBOLには、JavaScriptのような「LOCAL」や「NONLOCAL」という直接的なキーワードは存在しません。しかし、その考え方を実現するための方法がいくつかあります。
1. `WORKING-STORAGE SECTION`の変数スコープを意識する
`WORKING-STORAGE SECTION`で宣言された変数は、プログラム全体で共通のスコープを持ちます。しかし、`CALL`文で別のプログラムを呼び出す場合、その変数がどのように渡されるかを理解することが重要です。
- By Reference (デフォルト): `CALL`文で引数として渡された変数は、呼び出し元と呼び出し先で同じメモリ領域を共有します。呼び出し先で変更すると、呼び出し元にも反映されます。
- By Value: `CALL`文で`BY VALUE`を指定して渡された変数は、呼び出し先ではコピーとして扱われます。呼び出し先で変更しても、呼び出し元には影響しません。これは「LOCAL」の考え方に近いです。
2. 外部サブルーチン/プログラムでの変数管理
独立したプログラムとして作成されたサブルーチンや、`CALL`で呼び出される別のCOBOLプログラムでは、そのプログラム内で`WORKING-STORAGE SECTION`を定義することで、変数スコープを限定できます。呼び出し元から渡された引数以外は、そのプログラム内でのみ有効となります。
3. `LINKAGE SECTION`の理解
外部プログラムから呼び出されたプログラムでは、`LINKAGE SECTION`を使って、呼び出し元から渡された変数を参照します。`LINKAGE SECTION`で定義された変数は、呼び出し元で定義された変数を指し示すポインタのようなものです。ここで定義された変数を変更すると、呼び出し元の変数も変更されます。
サンプルプログラム:By ValueによるLOCALスコープの模倣
ここでは、`CALL`文で`BY VALUE`を使って、呼び出し先プログラムでローカルな変数のように振る舞わせる例を示します。
IDENTIFICATION DIVISION.
PROGRAM-ID. MAINPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MAIN-COUNTER PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
MAIN-LOGIC.
DISPLAY “メインプログラム開始”.
ADD 1 TO WS-MAIN-COUNTER.
DISPLAY “WS-MAIN-COUNTER (変更前): ” WS-MAIN-COUNTER.
- サブルーチンを呼び出す (BY VALUEで渡す)
CALL ‘SUBPROG’ USING BY VALUE WS-MAIN-COUNTER.
DISPLAY “WS-MAIN-COUNTER (変更後): ” WS-MAIN-COUNTER.
DISPLAY “メインプログラム終了”.
STOP RUN.
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
- SUBPROG内でローカルに扱うための変数を定義
- (外部から渡される引数とは別に)
01 WS-LOCAL-TEMP PIC X(10).
LINKAGE SECTION.
- 呼び出し元から渡される引数を受け取る
- BY VALUEで渡された値は、この変数にコピーされる
01 LS-RECEIVED-VALUE PIC 9(3).
PROCEDURE DIVISION USING LS-RECEIVED-VALUE.
SUBPROG-LOGIC.
DISPLAY ” サブルーチン開始”.
DISPLAY ” LS-RECEIVED-VALUE (受け取った値): ” LS-RECEIVED-VALUE.
- 受け取った値を変更してみる
ADD 5 TO LS-RECEIVED-VALUE.
DISPLAY ” LS-RECEIVED-VALUE (変更後): ” LS-RECEIVED-VALUE.
- ローカル変数WS-LOCAL-TEMPを使用
MOVE “TEST” TO WS-LOCAL-TEMP.
DISPLAY ” WS-LOCAL-TEMP: ” WS-LOCAL-TEMP.
DISPLAY ” サブルーチン終了”.
GOBACK.
コードの解説:
- `MAINPROG`では、`WS-MAIN-COUNTER`という変数を定義し、値をインクリメントしています。
- `CALL ‘SUBPROG’ USING BY VALUE WS-MAIN-COUNTER.` の部分が重要です。`BY VALUE`を指定することで、`WS-MAIN-COUNTER`の値のコピーが`SUBPROG`に渡されます。
- `SUBPROG`の`LINKAGE SECTION`では、`LS-RECEIVED-VALUE`として受け取ります。`BY VALUE`で渡された値は、この`LS-RECEIVED-VALUE`にコピーされるため、`SUBPROG`内で`LS-RECEIVED-VALUE`を変更しても、`MAINPROG`の`WS-MAIN-COUNTER`には影響しません。
- `SUBPROG`の`WORKING-STORAGE SECTION`で宣言された`WS-LOCAL-TEMP`は、`SUBPROG`内でのみ有効な、真にローカルな変数です。
この例では、`WS-MAIN-COUNTER`は`SUBPROG`から見ると「NONLOCAL」な変数ですが、`BY VALUE`で渡すことで、`SUBPROG`内での変更が「LOCAL」な変数のように扱われることをシミュレートしています。
応用・注意点
変数名の衝突に注意
外部プログラム(サブルーチン)を移行する際や、新たに作成する際には、呼び出し元と呼び出し先で変数名が重複していないか十分に確認してください。特に、`By Reference`で変数を渡す場合、意図せず親の変数を書き換えてしまうバグが発生しやすくなります。
例えば、親プログラムで`CUSTOMER-ID`という変数があり、サブルーチンでも`CUSTOMER-ID`という名前で別の意味の変数(あるいは引数)を定義してしまうと、`By Reference`で渡された場合に予期せぬ動作を引き起こす可能性があります。
一意な変数名の使用
このようなバグを防ぐためには、以下の対策が有効です。
- 命名規則の徹底: プログラム全体で一貫した命名規則を定め、変数名が重複しないようにします。例えば、サブルーチンごとにプレフィックスをつける(例: `SUB1-CUSTOMER-ID`, `SUB2-CUSTOMER-ID`)などです。
- By Value の活用: 変更を加えられたくない変数は、積極的に`BY VALUE`で渡すようにします。
- コメントの活用: 変数のスコープや受け渡し方法について、コード内に詳細なコメントを残すことで、後からコードを読む人が混乱するのを防ぎます。
LOCAL/NONLOCALという直接的なキーワードはありませんが、COBOLにおける変数のスコープを正しく理解し、`CALL`文の引数渡し方法(`BY REFERENCE` vs `BY VALUE`)や、プログラム間のデータ受け渡し方法を適切に管理することで、堅牢で保守しやすいCOBOLプログラムを作成することができます。

コメント