現場のエンジニアへ:PL/Iの「EXTERNAL属性」と賢く付き合うための作法
現場で数十年稼働している巨大なバッチシステムを紐解いていると、たまに「なぜここで変数を共有しているのか?」と頭を抱えたくなるようなコードに出くわすことがあるだろう。特に`EXTERNAL`属性を使ったグローバル変数の共有は、諸刃の剣だ。正しく使えば共通領域の管理がスマートになるが、一歩間違えれば、なぜか別のモジュールで値が書き換わっているという「悪夢のようなデバッグ」の引き金になる。
今日は、PL/Iにおける`EXTERNAL`属性の正しい作法と、コンパイル単位を跨いだデータ連携の深淵について、実務の視点から解説していこう。
—
1. EXTERNAL属性の基本とリンケージの仕組み
`EXTERNAL`属性は、複数のコンパイル単位(ソースモジュール)間で、同一のメモリ領域を共有するための指定だ。
通常、ローカル変数はその`PROCEDURE`のスタックフレーム内に閉じるが、`EXTERNAL`を付けると、コンパイラは「この変数は後でリンクエディタ(IEWL)が解決するから、名前(シンボル)だけ記録しておこう」と判断する。リンクエディタは、これら同じ名前の外部参照をメモリ上の同じアドレスにマッピングするわけだ。
ここが落とし穴
多くの初心者がやりがちなのは、「宣言時の属性不一致」だ。
例えば、一方のモジュールで `DCL G_DATA FIXED BIN(31) EXTERNAL;` とし、もう一方で `DCL G_DATA FIXED DEC(15) EXTERNAL;` と宣言してしまった場合、コンパイラは警告を出すかもしれないが、最悪の場合は正常終了してしまい、実行時に「謎の数値」に化ける。
鉄則: `EXTERNAL`で共有する変数は、必ず共通のインクルードメンバー(COPYブック)で定義し、全モジュールでそれを`%INCLUDE`させること。これがバグを未然に防ぐ唯一の道だ。
—
2. 実践的なコード例:メインとサブの連携
以下に、メインプログラムと外部サブプログラムでグローバル変数を共有する、保守性の高い構成例を示す。
/i
/ — COMMON_VAR.INC (共通定義ファイル) — /
/ 全モジュールで共通の定義を強制する /
DCL G_WORK_AREA_LEN FIXED BIN(31) EXTERNAL;
DCL G_WORK_AREA CHAR(1024) EXTERNAL;
/ — MAIN_MOD.PLI (メイン側) — /
TEST_MAIN: PROC OPTIONS(MAIN);
/ 共通変数の取り込み /
%INCLUDE COMMON_VAR;
/ 初期化 /
G_WORK_AREA_LEN = 1024;
G_WORK_AREA = ‘INITIALIZED’;
/ サブルーチンの呼び出し /
CALL SUB_PROC;
PUT SKIP LIST(‘AFTER SUB_PROC: ‘ || G_WORK_AREA);
END TEST_MAIN;
/ — SUB_MOD.PLI (サブ側) — /
SUB_PROC: PROC;
%INCLUDE COMMON_VAR;
/ 共通変数の値を書き換える /
G_WORK_AREA = ‘MODIFIED BY SUB_PROC’;
/ VSAMアクセスの戻り値やONユニットの状態を共有する際にも使われる /
END SUB_PROC;
—
3. 保守現場で知っておくべき「ONユニット」と「外部データ」
`EXTERNAL`な変数と組み合わせて最も厄介なのが、`ON CONDITION`や`ON ERROR`の制御だ。
大規模改修でありがちなのが、「サブプログラム内で`ON ERROR`をセットし、エラーが発生した際にグローバルなフラグ変数(`EXTERNAL`)を立てて、メイン側に制御を戻す」という設計。これ自体は悪くないが、「ONユニットの有効範囲」を意識しないと、思わぬ箇所で古いエラー状態が引き継がれてしまう。
- デバッグのコツ: `ON`ユニット内でグローバル変数を操作する際は、必ず`SIGNAL CONDITION`のログ出力や、`ONSOURCE` / `ONCHAR` などの`BUILTIN`関数を併用して、どのタイミングで変数が変わったかを出力させること。
/i
/ 例:エラー発生時に共通エリアへログを残す /
ON ERROR BEGIN;
DCL ERR_MSG CHAR(80) VAR;
ERR_MSG = ‘CRITICAL ERROR: ‘ || ONCODE();
G_WORK_AREA = ERR_MSG; / 共通領域にエラー情報を退避 /
PUT SKIP LIST(‘DEBUG: ‘ || G_WORK_AREA);
SIGNAL ERROR; / 呼び出し元へ再送出 /
END;
—
4. 最後に:メインフレームエンジニアとしての矜持
現代のクラウドネイティブな環境とは異なり、メインフレームのメモリ空間は、言わば「全員が同じ部屋で暮らす大家族」のようなものだ。`EXTERNAL`属性を使うということは、その部屋の家具を勝手に動かす権利を全員に与えることに等しい。
- 安易なグローバル化は避ける: 引数で渡せるなら、引数で渡すのがベストだ。
- 名前空間を汚染しない: `EXTERNAL`変数の名前は、`G_PRJ_MOD01_STATUS` のように、プロジェクト名やモジュール名を含めたユニークな命名規則を徹底すること。
我々が書いているのは、数十年後も誰かが保守しなければならない「遺産」だ。コードを簡潔に保つことも重要だが、何より「後輩が追跡可能な構造」を維持することが、システムアーキテクトとしての最大の責務だと心に留めておいてほしい。
何かあれば、またいつでも相談に来い。バッチのログを読み解くのは、案外楽しいものだぞ。
