【実務・中級編】ON ENDFILEユニットのスタック構造と制御フロー – PL/Iの基本構文とデータ制御実践ガイド

こんにちは。メインフレームの現場で、日々COBOLやPL/Iの巨大なソースコードと格闘しているエンジニアの皆さん。

今回は、PL/Iの数ある強力な機能の中でも、特に「ONユニットのスタック構造」と「ファイル終了(ENDFILE)時の制御フロー」について、徹底的に解説しようと思う。

レガシーシステムのバッチ改修や、オープン系へのマイグレーション案件などで、このあたりの挙動を誤ったがために「無限ループが発生した」「意図せぬところでファイルが閉じられた」というトラブルに遭遇した人は少なくないはずだ。PL/IはCOBOLに比べて自由度が高い分、言語仕様の裏側を正しく理解していないと、痛い足元をすくわれる。

今日は、ベテランの私が現場のノウハウを交えて、このモヤモヤをすっきりと解消してやろう。

1. 予約語を持たないPL/Iと、ONユニットの基本思想

まず大前提として、PL/Iには「厳格な予約語(Reserved Words)」というものがほとんど存在しない。`IF` や `READ` でさえも、コンテキストによっては変数名として使えてしまうという、現代の言語から見れば狂気とも言える柔軟性を持っている。

そんなPL/Iの例外処理メカニズムが ON条件(ON-condition) だ。
ファイル終了を表す `ENDFILE` もその一つで、システムが割り込みを検知したときに、あらかじめプログラマが定義した手続き(ONユニット)を非同期的に呼び出す仕組みになっている。

ここで多くのエンジニアが勘違いしやすいポイントがある。
「ONユニットは、JUMP文やサブルーチンコールとは違う。一種の動的なハンドラーの登録である」 という点だ。

2. ONユニットの「スタック構造」と REVERT文の魔力

PL/IのONユニットは、実行時にスタック(LIFO: Last-In, First-Out)で管理される。

同じファイル名(あるいは条件)に対して複数の `ON ENDFILE` 文が実行されると、古いONユニットの上に新しいONユニットが「積み上げられて」いく。そして、条件が発生した際には、スタックの一番上(直近に実行されたもの)のONユニットが発火するのだ。

ここで重要になるのが `REVERT` 文 だ。
一度有効化したONユニットを無効化し、一つ前の(親の)状態に戻すためには `REVERT` が不可欠となる。もし `REVERT` を忘れたままブロックを抜けると、思わぬスコープ汚染や予期せぬ制御フローの暴走を引き起こす。

実際のバッチ処理で、サブプロシージャやループの中で安易に `ON ENDFILE` を多重定義し、スタックを溢れさせたり戻し忘れたりするバグは、現場のコードレビューで最も厳しくチェックすべきポイントの一つである。

3. 実践!VSAM/順編成ファイル処理におけるON ENDFILE制御

百聞は一見に如かず。実際のメインフレームのバッチ処理を想定した、実用的なPL/Iのサンプルコードを見てみよう。
ここでは、マスタファイル(順編成またはKSAM等のVSAM)を読み込み、ファイル終了を検知して安全にループを抜ける標準的なパターンを記述している。

1
/ утра /
/ プログラム名: MSTRDUMP /
/ 概要: 顧客マスタファイルを読み込み、終了条件を制御する /
/ /
MSTRDUMP: PROC OPTIONS(MAIN);

DCL MSTR_FILE FILE RECORD INPUT
ENV(FB RECSIZE(80)); / 80バイト固定長ファイル /

DCL 1 MSTR_REC,
10 MSTR_ID PIC ‘9(008)’, / 顧客ID /
10 MSTR_NAME CHAR(072); / 顧客名 /

DCL EOF_FLAG BIT(1) INIT(‘0’B); % 終了フラグ

/ 顧客マスタのオープン /
OPEN FILE(MSTR_FILE);

/ ==================================================== /
/ 1. ENDFILE条件に対するONユニットの定義(スタックへの積込) /
/ ==================================================== /
ON ENDFILE(MSTR_FILE) BEGIN;
EOF_FLAG = ‘1’B; % 終了フラグをONにする
PUT SKIP LIST(‘ 顧客マスタのファイル終了を検知しました ‘);
END;

/ メインの読み込みループ /
DO WHILE(^EOF_FLAG);

/ レコード読み込み /
READ FILE(MSTR_FILE) INTO(MSTR_REC);

/ 終了フラグが立っていなければ処理を続行 /
IF ^EOF_FLAG THEN DO;
/ ここに実際のビジネスロジック(編集・更新など)が入る /
PUT SKIP EDIT (MSTR_ID, ‘ : ‘, MSTR_NAME) (A, A, A);
END;

END;

/ ==================================================== /
/ 2. 終了処理とONユニットの明示的な復帰(REVERTの実行) /
/ ==================================================== /
REVERT ENDFILE(MSTR_FILE); / スタックからこのONユニットを除去 /

/ ファイルのクローズ /
CLOSE FILE(MSTR_FILE);

PUT SKIP LIST(‘ 正常終了しました ‘);

END MSTRDUMP;

コードの解説と実務上のワンポイント

1. `ON ENDFILE(MSTR_FILE) BEGIN; … END;`
ここでは複数文を実行するため `BEGIN … END` ブロックを使用している。単一文であれば `ON ENDFILE(MSTR_FILE) EOF_FLAG = ‘1’B;` のようにも書けるが、実務ではログ出力やエラーカウントなどの複数処理を行うことが多いため、ブロック化が基本だ。
2. `READ` ステートメントと例外のタイミング
ファイルが物理的なEOFに達した状態で `READ` が実行されると、制御は直ちに `ON` ユニットに移る。その後、`BEGIN` ブロックの処理が終わると、原則として `READ` 文の直後のステートメントへ制御が戻る(あるいは今回のコードのようにフラグでループを抜ける)。
3. `REVERT ENDFILE(MSTR_FILE);` の重要性
プログラムの終了直前やスコープを抜ける際、明示的に `REVERT` を呼ぶことで、処理系が保持しているON条件のスタックをクリーンな状態に戻している。これをサブルーชาติ間や複雑なネストの中でサボると、呼び出し元の予期せぬ位置で古いENDFILEトラップが発火する大惨事につながる。

4. デバッグと改修時の鉄則:先輩からのアドバイス

最後に、保守現場でPL/IのONスタック関連の不具合に直面したときの、私なりのデバッグのコツを伝授しよう。

  • 「どこでONが定義されたか」を常に追跡せよ

大規模なシステムでは、共通ルーチンやINCLUDEファイルの中で暗黙的に `ON ENDFILE` が定義されていることがある。動的スタックゆえに、ソースコードを上から読んだだけではどのONユニットが有効か分からないことがあるため、コンパイラリスト(XREFやSTMT)を確認する習慣をつけよう。

  • GOTOによる大ジャンプの乱用に注意

古いPL/Iコードには、`ON ENDFILE` の中で `GOTO 抜け出しラベル;` と書いて、ループをごっそり脱出するスタイルが散見される。これ自体は言語仕様上許容されているが、スタックやリソースのクリーンアップ(CLOSEやREVERT)がスキップされがちになり、スパゲッティコードの元凶となる。現代の保守改修では、上記のサンプルコードのように フラグ制御(BIT(1)変数の併用) にリファクタリングするのが安全かつスマートだ。

レガシーシステムの寿命は、我々エンジニアのこうした基礎知識の深さによって支えられている。言語の挙動を完全に手の内に収め、後輩たちに「おっ、さすがだな」と言わせる質の高いコードを書き続けてほしい。

それでは、次の現場のトラブルシューティングまた会おう。

タイトルとURLをコピーしました