後輩よ、メインフレームのファイル処理は「ENDFILE」を制する者が勝つ!安全な終了処理の極意を伝授しよう
おい、そこの君! バッチ処理の改修で、ファイル読み込みの終わり際に「あれ、なんでここ落ちるんだ?」とか、「データは全部読み込めたはずなのに、プログラムが延々と回り続けてるぞ?」なんて経験、ないか? もし君が頷いたなら、今日のテーマは君の血となり肉となるだろう。メインフレームの世界でPL/Iを使ってバッチ処理を書くなら、ENDFILE条件の扱い方は基本中の基本であり、同時に奥が深い。これを疎かにすると、安定稼働を求める基幹システムでは即座にトラブルメーカー認定だ。
俺も数々の大規模バッチ改修を経験してきたが、このENDFILE処理の不備でどれだけの夜を徹したことか。だが、一度正しい知識とコーディング標準を身につければ、もう恐れることはない。今日は、その安全かつ確実なENDFILE条件による終了処理の極意を、実践的なコードを交えて伝授する。
なぜENDFILE条件が重要なのか?
まず、根本から理解しよう。PL/Iプログラムがファイルを読み込むとき、データが尽きれば必ず「ファイルの終わりに到達した」という状態になる。この状態をPL/IではENDFILE条件と呼ぶ。この条件が発生したときに、プログラムが「どう振る舞うべきか」を明示的に教えてやらなければ、OSやPL/Iランタイムは困惑し、結果としてプログラムは異常終了するか、意図しない無限ループに陥る。
メインフレームのバッチ処理は、膨大なデータを正確に、そして確実に処理し、次のステップへ渡すことが使命だ。ファイルの読み込みが完了したことを正しく検知し、安全に後処理を行うことは、システム全体の安定性を保証する上で絶対に欠かせないんだ。
PL/Iプログラムの基本構造とONユニットの立ち位置
PL/Iプログラムの基本は、`PACKAGE`と`PROCEDURE`だ。特にバッチ処理の入り口となるメインプログラムは、通常以下のような構造をとる。
SAMPLE_PACKAGE: PACKAGE OPTIONS(MAIN);
/ — ファイル定義 — /
DCL SYSIN FILE INPUT SEQUENTIAL ENV(F RECSIZE(80)); / 入力ファイル定義 /
DCL SYSPRINT FILE OUTPUT PRINT ENV(F RECSIZE(133)); / 出力ファイル定義 /
/ — レコードバッファ定義 — /
DCL IN_REC CHAR(80); / 入力レコードバッファ /
/ メインプロシージャ /
SAMPLE_PROCEDURE: PROCEDURE OPTIONS(MAIN);
/
- ここにENDFILEのONユニットを記述する!
- メインプロシージャの初期段階でONユニットを確立することが重要。
- これにより、READステートメントが実行される前に条件ハンドラが有効になる。
/
ON ENDFILE(SYSIN) BEGIN;
/ ファイル終了時の処理 /
PUT SKIP LIST(‘INFO: ファイルSYSINの終わりに達しました。’);
GOTO END_PROCESSING; / 処理終了ラベルへジャンプし、ループから脱出 /
END;
/ — ファイルオープン — /
PUT SKIP LIST(‘INFO: 処理を開始します。’);
OPEN FILE(SYSIN);
OPEN FILE(SYSPRINT);
PUT SKIP LIST(‘INFO: ファイルをオープンしました。’);
/ — メイン処理ループ — /
DO WHILE(‘1’B); / 無限ループに見えるが、ENDFILE条件でGOTOにより脱出する /
READ FILE(SYSIN) INTO(IN_REC); / レコード読み込み /
/ ここで読み込んだIN_RECを使った業務処理 /
CALL PROCESS_RECORD_SUB(IN_REC); / サブプロシージャ呼び出し /
END; / DO WHILE /
END_PROCESSING: ; / ENDFILEからのジャンプ先、処理終了ラベル /
/ — 後処理(ファイルクローズなど) — /
PUT SKIP LIST(‘INFO: ファイルクローズ処理を開始します。’);
CLOSE FILE(SYSIN);
CLOSE FILE(SYSPRINT);
PUT SKIP LIST(‘INFO: ファイルをクローズしました。’);
PUT SKIP LIST(‘INFO: 処理を正常終了します。’);
RETURN; / メインプロシージャ終了 /
/ — サブプロシージャ: レコード処理 — /
PROCESS_RECORD_SUB: PROCEDURE(P_RECORD_DATA);
DCL P_RECORD_DATA CHAR();
/ レコード処理の具体的なロジックはここに記述 /
PUT FILE(SYSPRINT) SKIP LIST(‘処理中レコード:’, P_RECORD_DATA);
END PROCESS_RECORD_SUB;
END SAMPLE_PROCEDURE;
END SAMPLE_PACKAGE;
見ての通り、`ON ENDFILE(SYSIN) BEGIN; … END;` のブロックがメインプロシージャの早い段階で定義されている。これがONユニットだ。ファイル処理の途中でENDFILE条件が発生すると、PL/Iランタイムは即座にこのONユニットへ制御を移す。
ON ENDFILEユニットの構造と制御フロー
ONユニットは、特定の条件が発生したときに実行される割り込み処理のようなものだ。ENDFILE条件の場合、`ON ENDFILE(ファイル名) DO; … END;` または `BEGIN; … END;` の形式で記述する。
ON ENDFILE(SYSIN) BEGIN;
/
- ここがENDFILE条件発生時に実行されるブロックだ。
- 重要なのは、このONユニット内で「何をすべきか」を明確にすること。
- 1. ログ出力 (ENDFILE発生を記録)
- 2. (必要であれば) 処理終了フラグの設定
- 3. 最も重要なのは、メイン処理ループからの脱出!
/
PUT SKIP LIST(‘ INFO: ENDFILE条件発生: ファイル ‘ || ONFILE || ‘ ‘);
PUT SKIP LIST(‘ ONCODE: ‘ || ONCODE); / ENDFILEなら7000番台のONCODEを返す /
GOTO END_PROCESSING; / これが最もシンプルで確実なループ脱出方法だ /
END; / ON ENDFILE(SYSIN) の終わり /
制御フローの解説
1. 通常処理: プログラムは`READ FILE(SYSIN) INTO(IN_REC);` のような入力ステートメントを繰り返し実行する。
2. ENDFILE発生: `READ`ステートメントの実行中に、ファイル内のデータが全て読み込まれ、ファイルの終わりに到達すると、ENDFILE条件が発生する。
3. ONユニットへの割り込み: PL/Iランタイムは、即座に該当する`ON ENDFILE(SYSIN)`で定義されたONユニットへ制御を移す。
4. ONユニット内の処理: ONユニット内のステートメント(例: ログ出力、`GOTO END_PROCESSING;`)が実行される。
5. ループからの脱出: `GOTO END_PROCESSING;` によって、メイン処理ループから完全に抜け出し、`END_PROCESSING`ラベル以降の後処理ブロックへ制御が移る。
6. 後処理: ファイルのクローズなどの最終処理が実行され、プログラムが正常終了する。
ここで特に注意してほしいのは、ONユニット内で`GOTO`を使ってメインループから脱出することだ。
もし`GOTO`を使わずにONユニットを抜けてしまうと、PL/IランタイムはENDFILEを発生させた入力ステートメントの直後、つまり`READ`ステートメントの直後へ制御を戻そうとする。しかしファイルはもう終端に達しているので、次の`READ`もまたENDFILE条件を発生させ、ONユニットへ…という無限ループに陥ってしまう。これを避けるためにも、`GOTO`による明示的な脱出はPL/Iにおける強力なイディオムなんだ。
実践的なPL/Iコード例とBUILTIN関数の活用
もう少し実用的なコードを見てみよう。ここでは、入力ファイルからレコードを読み込み、簡単な処理をして出力ファイルに書き出す典型的なバッチ処理を想定する。VSAMファイルであっても、順次読み込みを行う場合は同様のENDFILE処理が適用されるぞ。
/=====================================================================/
/ PGMID: SAMPLE01 /
/ DESC : PL/I ENDFILE条件処理サンプルプログラム /
/ 入力ファイルを読み込み、ENDFILEで終了処理を行う /
/=====================================================================/
SAMPLE_PACKAGE: PACKAGE OPTIONS(MAIN);
/ — ファイル定義 — /
DCL SYSPRINT FILE STREAM OUTPUT PRINT ENV(F RECSIZE(133)); / 標準出力 /
DCL IN_DD FILE INPUT SEQUENTIAL ENV(F RECSIZE(80)); / 入力ファイル /
DCL OUT_DD FILE OUTPUT SEQUENTIAL ENV(F RECSIZE(100)); / 出力ファイル /
/
- VSAMファイルを順次読み込む場合も、ファイル定義は類似し、
- ENDFILE条件の発生と処理は基本的に同じロジックで対応可能だ。
- 例: DCL VSAM_KSDS FILE INPUT SEQUENTIAL KEYED ENV(VSAM KSDS);
/
/ — レコードバッファ定義 — /
DCL 1 IN_REC_AREA, / 入力レコード構造体 /
2 IN_KEY CHAR(10),
2 IN_DATA CHAR(70);
DCL 1 OUT_REC_AREA, / 出力レコード構造体 /
2 OUT_KEY CHAR(10),
2 OUT_PAD CHAR(10) INIT(‘ ‘), / パディング /
2 OUT_DATA CHAR(80);
/ — 変数定義 — /
DCL REC_COUNT FIXED BIN(31) INIT(0); / 処理レコード件数カウンター /
/ — メインプロシージャ — /
SAMPLE_MAIN_PROC: PROCEDURE OPTIONS(MAIN);
/
- ON ENDFILEユニットの定義
- 入力ファイルIN_DDに対するENDFILE条件が発生した際に実行される。
/
ON ENDFILE(IN_DD) BEGIN;
PUT SKIP LIST(‘ INFO: ENDFILE条件発生: ファイル ‘ || ONFILE || ‘ ‘);
PUT SKIP LIST(‘ ONCODE: ‘ || ONCODE); / ONCODEはENDFILEの場合、7000番台を返す /
GOTO END_PROCESSING; / ここでメイン処理ループから脱出 /
END;
/
- 万が一、他の予期せぬエラーが発生した場合のONユニット
- 実運用では必須。ONCODEを調べてエラーメッセージを出す。
- ON ERRORの定義は、ON ENDFILEより後に記述するのが一般的。
- 特定の条件ハンドラが優先されるため、具体的な条件から順に定義していく。
/
ON ERROR BEGIN;
PUT SKIP LIST(‘ ERROR: 予期せぬエラーが発生しました。 ‘);
PUT SKIP LIST(‘ ONCODE: ‘ || ONCODE); / 発生したエラーコード /
PUT SKIP LIST(‘ ONFILE: ‘ || ONFILE); / エラー発生時のファイル名 (あれば) /
PUT SKIP LIST(‘ ONLOC : ‘ || ONLOC); / エラー発生時のプロシージャ名 /
STOP; / プログラムを強制終了 /
END;
/ — ファイルオープン — /
PUT SKIP LIST(‘INFO: 処理を開始します。’);
OPEN FILE(IN_DD);
OPEN FILE(OUT_DD);
PUT SKIP LIST(‘INFO: ファイルをオープンしました。’);
/ — メイン処理ループ — /
DO WHILE(‘1’B); / ENDFILE発生でGOTO脱出を前提とした無限ループ /
/ レコード読み込み /
READ FILE(IN_DD) INTO(IN_REC_AREA);
REC_COUNT = REC_COUNT + 1;
/ 読み込んだレコードに対する業務処理 /
CALL PROCESS_RECORD_SUB(IN_REC_AREA, OUT_REC_AREA);
/ 処理結果をファイルに出力 /
WRITE FILE(OUT_DD) FROM(OUT_REC_AREA);
/ 進行状況のログ出力 (例: 1000件ごとに) /
IF MOD(REC_COUNT, 1000) = 0 THEN DO;
PUT SKIP LIST(‘INFO: ‘ || REC_COUNT || ‘件のレコードを処理しました。’);
END;
END; / DO WHILE(‘1’B) /
END_PROCESSING: ; / ENDFILEからのGOTO先ラベル /
/ — 後処理 — /
PUT SKIP LIST(‘INFO: ファイルクローズ処理を開始します。’);
CLOSE FILE(IN_DD);
CLOSE FILE(OUT_DD);
PUT SKIP LIST(‘INFO: ファイルをクローズしました。’);
PUT SKIP LIST(‘INFO: 合計 ‘ || REC_COUNT || ‘件のレコードを処理しました。’);
PUT SKIP LIST(‘INFO: 処理を正常終了します。’);
RETURN; / メインプロシージャ終了 /
/ — サブプロシージャ: レコード処理 — /
PROCESS_RECORD_SUB: PROCEDURE(P_IN_REC, P_OUT_REC);
DCL 1 P_IN_REC,
2 P_IN_KEY CHAR(10),
2 P_IN_DATA CHAR(70);
DCL 1 P_OUT_REC,
2 P_OUT_KEY CHAR(10),
2 P_OUT_PAD CHAR(10),
2 P_OUT_DATA CHAR(80);
/ 入力データを加工して出力レコードにセットする例 /
P_OUT_KEY = P_IN_KEY;
P_OUT_DATA = ‘PROCESSED:’ || P_IN_DATA; / 適当な加工例 /
END PROCESS_RECORD_SUB;
END SAMPLE_MAIN_PROC;
END SAMPLE_PACKAGE;
BUILTIN関数の活用
上記のコード例では、いくつかPL/IのBUILTIN関数を使っている。これらはデバッグや情報出力に非常に役立つので、ぜひ覚えてほしい。
- `ONCODE`: 現在発生している条件(ENDFILE、ERRORなど)に対応する数値コードを返す。ENDFILE条件の場合は7000番台(例えば7001)を返す。これでどんな条件が発生したか特定できる。
- `ONFILE`: 条件が発生したファイル名を返す。複数のファイルを扱うプログラムでは、どのファイルでENDFILEが発生したのかを特定するのに非常に有効だ。
- `ONLOC`: 条件が発生したプログラム内の位置(プロシージャ名)を返す。これもデバッグ時に役立つ情報だ。
これらをログ出力に含めることで、万が一の事態が発生した際に、素早く原因を特定するための手がかりとなる。特にメインフレーム環境では、SYSOUTやSYSPRINTに出力される情報が唯一のデバッグ情報となることも多いため、詳細なログ出力は非常に重要なんだ。
現場で役立つ追加のヒントと注意点
1. ONユニットのスコープ:
ONユニットは定義されたプロシージャ内で有効だ。もし複数のプロシージャで異なるファイルを開く場合、それぞれのプロシージャのスコープ内でONユニットを定義するか、最も外側のメインプロシージャで全てのONユニットを定義する、といった設計が必要になる。通常はメインプロシージャ内でまとめて定義するのが一般的で、シンプルで管理しやすい。
2. VSAMファイルのENDFILE:
上記の例はSEQUENTIALファイルだが、VSAM (KSDS, ESDS, RRDS) を順次読み込む場合も、ファイルの終端に達すればENDFILE条件が発生する。処理方法は全く同じだ。VSAMを直接アクセス (DIRECT ACCESS) する場合は、KEY条件やRECORD条件の扱いが重要になるが、ENDFILEは基本的に順次読み込み時に考慮すべきだ。
3. ONユニット内での処理は最小限に:
ENDFILEのONユニット内で複雑な処理を行うのは避けるべきだ。ONユニットは割り込み処理なので、できるだけ早く制御をメイン処理に戻すか、後処理にジャンプすることが望ましい。複雑な処理は、`GOTO`でジャンプした後の後処理ブロックで実行するように設計しよう。
4. 複数ファイルのENDFILE処理:
もし複数の入力ファイルを同時に処理し、それぞれのENDFILEを個別に検出したい場合は、ファイルごとにON ENDFILEユニットを定義する。
ON ENDFILE(IN_DD1) BEGIN; / IN_DD1のENDFILE処理 / GOTO END_PROCESSING; END;
ON ENDFILE(IN_DD2) BEGIN; / IN_DD2のENDFILE処理 / GOTO END_PROCESSING; END;
ただし、全てのファイルを読み終えてから後処理に進む、という設計の場合は、ファイルごとにENDFILEフラグを設定し、全てのフラグがONになったらループを抜ける、というロジックが必要になる場合もある。これは設計次第だが、シンプルさを優先するならGOTOが強力だ。
5. テストデータは必ずENDFILE条件を発生させる:
テストを行う際は、必ず入力ファイルのデータが途中で終わるケース(ENDFILE条件を発生させるケース)を検証すること。データがない空ファイルや、データが1件だけのファイルなど、エッジケースでの挙動も確認しておくこと。
まとめ
どうだ、ENDFILE条件とONユニットの重要性がよく分かっただろう? PL/Iにおけるファイル処理の終了ロジックは、基幹システムの安定稼働を左右する非常に重要な要素だ。
- ON ENDFILEユニットを適切に定義し、ファイル終了時の割り込み処理を記述すること。
- ONユニット内では`GOTO`を使ってメイン処理ループから安全に脱出すること。
- `ONCODE`や`ONFILE`といったBUILTIN関数を活用し、デバッグに役立つ情報をログに出力すること。
これらのポイントをしっかり押さえて、信頼性の高いPL/Iプログラムを書き上げてほしい。メインフレームのバッチ処理は地味に見えるかもしれないが、その裏には確固たる論理と、安定稼働への強いこだわりがある。君たち後輩には、そのこだわりをしっかりと受け継ぎ、より堅牢なシステムを構築していってほしいと願っている。
頑張れよ!
