【実務・中級編】ON ENDFILEユニットによるファイル終端処理の制御構造 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは。長年、金融や流通の基幹系メインフレームでPL/Iバッチの泥臭い改修と向き合ってきたシステムアーキテクトの私だ。

今これを読んでいる君は、おそらく「何十年も前に先輩が書いた巨大なPL/Iソースコードの保守」に頭を悩ませているか、あるいは「オープン系へ移行するためのレガシー調査」の真っ只中にいることだろう。

今回は、PL/Iのデータ制御において避けて通れない「ON ENDFILEユニットによるファイル終端処理」と、それに密接に関わる「識別子と予約語の絶妙な関係(実はPL/Iには明確な予約語がないという変態的な仕様)」について、現場の知見を交えて徹底的に解説しよう。

1. PL/Iの「予約語がない」という驚愕の仕様と識別子

まず前提として、C言語やJava、COBOLなどで当たり前にある「予約語(Keyword)」の概念を、君の頭からいったん綺麗に消し去ってほしい。

驚くことに、PL/Iの言語仕様上、固定的な「予約語」は存在しない。
`IF`、`THEN`、`READ`、さらには今回解説する`ENDFILE`や`ON`でさえも、すべて「文脈依存のキーワード(Contextual Keyword)」に過ぎないのだ。

どういうことか?
PL/Iでは、以下のようなコードを書くことが理論上できてしまう。

1
/ BEGINという名前の変数に10を代入する、狂気の構文 /
BEGIN = 10;

コンパイラは、その位置にある単語が前後の文脈から見て「命令(文)」なのか「変数名(識別子)」なのかを判断している。そのため、昔の職人たちはうっかり`READ`や`WRITE`を変数名に使ってしまい、コンパイラを盛大に混乱させるという事故をよくやらかしたものだ。
自社で引き継いだコードに「おいおい、なんでこんな変数名にしたんだ…」というものが散見されるのは、このPL/Iの仕様が原因であるケースが多い。

識別子(変数名など)のルールはシンプルで、

  • 最大31文字(Enterprise PL/Iではもっと長いが、保守性を考えると12〜16文字程度に抑えるのが吉)
  • 英字(A〜Z)、ドル記号($)、シャープ記号(#)、アットマーク(@)で始まり、2文字目以降は数字も使用可能。
  • 単語の区切りには必ず「ブレーク文字(アンダースコア `_`)」を使うこと。ハイフン(`-`)を使うと減算演算子と解釈されて大惨事になるので絶対に避けること。

2. ON ENDFILEユニットの制御フローとトラップ

さて、本題のファイル終端(ENDFILE)処理だ。
COBOLの `AT END` や、C言語の `feof()` に慣れた身体にとって、PL/Iの「割込み処理(ONユニット)」モデルは非常に独特で、初めて見た時は面食らうはずだ。

割込み駆動型のファイル終端制御

PL/Iの `READ` ステートメントでファイルの物理的な終端(EOF)に達すると、コンパイラが自動的に例外を検知し、対応する `ON ENDFILE` ユニットへと制御がジャンプする。

ここで多くの若手エンジニアがハマる罠がある。
「ONユニットはループの一部ではなく、例外発生時のトラップ(例外ハンドラ)である」という点だ。

1
/ 良くあるアンチパターン /
ON ENDFILE(MASTER_FILE)
/ ここに直接READを書いたり、ループを抜けようとして苦戦する /

正しい制御フローを構築するためには、フラグ変数(例: `EOF_FLAG`)を噛ませて、通常の制御ループと綺麗に切り分ける必要がある。

3. 実践!VSAMレコード入出力とON ENDFILEの模範コード

それでは、現場のバッチプログラムでそのまま使える、標準的な構成のPL/Iソースコードを見てみよう。
VSAM(KSDSまたはESDS)からの順次読み込みを想定し、通常の終了処理と、テスト時などに重宝する `SIGNAL ENDFILE` による強制終了シミュレーションを組み込んだ実践的なコードだ。

1
M001: PROC OPTIONS(MAIN);

/ ————————————————————- /
/ 変数宣言領域 /
/ ————————————————————- /
DCL MASTER_FILE FILE RECORD INPUT; / マスターファイル定義 /
DCL EOF_FLAG CHAR(1) INIT(‘OFF’); / 終了フラグ /
DCL READ_COUNT FIXED BIN(31) INIT(0); / 読み込み件数カウンタ /

/ マスターレコードの構造体(レイアウト) /
DCL 1 MST_REC,
2 MST_ID CHAR(5), / 顧客ID /
2 MST_NAME CHAR(30), / 顧客名 /
2 MST_FILLER CHAR(65); / 予備領域 /

/ ————————————————————- /
/ ONユニット(例外処理)の定義 /
/ ————————————————————- /
ON ENDFILE(MASTER_FILE) BEGIN;
EOF_FLAG = ‘ON’; / 終了フラグを立てる /
PUT SKIP LIST(‘ NOTICE: END OF FILE DETECTED FOR MASTER_FILE ‘);
END;

/ ————————————————————- /
/ ファイルオープン /
/ ————————————————————- /
OPEN FILE(MASTER_FILE);

PUT SKIP LIST(‘ BATCH PROCESSING STARTED ‘);

/ 初回読み込み(プリレード) /
READ FILE(MASTER_FILE) INTO(MST_REC);

/ ————————————————————- /
/ メインループ処理 /
/ ————————————————————- /
DO WHILE (EOF_FLAG = ‘OFF’);

/ 読み込み件数のカウント /
READ_COUNT = READ_COUNT + 1;

/ 実務におけるデータ加工・ビジネスロジック(ここでは省略) /
/ 例: PUT SKIP LIST(MST_ID, MST_NAME); /

/ テスト・デバッグ用のシミュレーション処理: /
/ 1000件目に強制的にENDFILEシグナルを送り、挙動を確認する /
IF READ_COUNT = 1000 THEN DO;
PUT SKIP LIST(‘ DEBUG: SIGNALING ENDFILE MANUALLY ‘);
SIGNAL ENDFILE(MASTER_FILE);
LEAVE; / ループの強制脱出 /
END;

/ 次レコードの読み込み /
READ FILE(MASTER_FILE) INTO(MST_REC);

END;

/ ————————————————————- /
/ 終了処理 /
/ ————————————————————- /
CLOSE FILE(MASTER_FILE);

PUT SKIP LIST(‘ BATCH PROCESSING COMPLETED. TOTAL READ = ‘, READ_COUNT);

END M001;

4. 現場のシニアから伝えるデバッグのコツと注意点

上記のコードを見て、「おっ、`SIGNAL ENDFILE` なんてカッコいい機能があるんだな」と思ったかもしれない。
この `SIGNAL` ステートメントは、実際のファイル実体が存在しない単体テスト環境や、例外処理のモックテストにおいて非常に強力な武器になる。わざわざ100万件のテストデータを用意しなくても、特定の条件でファイルを読み終えたふりをさせることができるからだ。

しかし、保守現場では以下の点に十分注意してほしい。

1. ONユニットの有効範囲(スコープ)
PL/Iの `ON` ユニットは動的スコープを持つ。プロシージャがネストしている場合、どこで `ON` が宣言されたかによって挙動が変わるため、基本的にはメインプロシージャの早い段階で明確に定義し、迷子にならないようにすること。
2. ループ構造と `READ` の位置
PL/Iでレコード入出力を行う際、ループに入る前に1回 `READ`(プリレード)し、ループの最後でもう一度 `READ` するというパターン(今回紹介した形)が最もバグりにくい。これを怠ると、EOF判定のタイミングがズレて最終レコードを2回処理したり、逆にスキップしたりする「メインフレームあるあるの恐怖のバグ」を生むことになる。

レガシーシステムの維持は地味な作業の連続だが、PL/Iのこうした奥深い仕様を理解していれば、どんなに複雑なスパゲッティコードであっても必ず構造を読み解くことができる。
今日の解説が、君の次なるバッチ改修の助けになれば幸いだ。次回の現場でお会いしよう。

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