はじめに:PL/Iの「予約語を持たない自由」がもたらす罠と、基幹システムの死角
汎用機(メインフレーム)の心臓部で今なお稼働し続けるPL/Iアプリケーション。その言語設計思想は、FORTRANの数値計算能力、COBOLの事務処理能力、そしてALGOLの構造化プログラミングの概念を高次元で融合させることにありました。
中でも特異なのが、「PL/Iには真の予約語(Reserved Words)が存在しない」という仕様です。
例えば、`IF`や`READ`、さらには今回のテーマである`ENDFILE`さえも、コンテキストキーワード(文脈依存語)として扱われます。極端な話、プログラマが変数名として `DECLARE ENDFILE FIXED BIN(31);` と宣言したとしても、コンパイラは前後の文脈からそれを変数と解釈するか、制御構文の一部と解釈するかを完璧に判別します。
この圧倒的な言語の柔軟性は、裏を返せば、一歩設計を誤った瞬間にコンパイラを欺き、本番稼働中のバッチ処理で致命的な無限ループや突発的なアベンド(ABEND)を引き起こす「諸刃の剣」となります。
本稿では、順次ファイルの読み込み終了を捕捉する `ON ENDFILE` 条件の制御構造に焦点を当て、ファイルポインタの整合性を担保したまま安全にクローズ処理を完結させるための実務的コーディング規約を、コンパイラの内部挙動やマイグレーションの視点から徹底的に紐解きます。
—
1. `ON ENDFILE` のコンテキスト制御と「ヌルループ」の恐怖
順次ファイル(Sequential Dataset)の読み込みにおいて、ファイル終端(EOF)に達した際のハンドリングは、基幹システムの堅牢性を左右する最も重要なポイントの一つです。
多くのエンジニアが犯す最初の過ちは、`ON ENDFILE` を単なる「ループを抜けるためのフラグ立て」と勘違いし、以下のようなコードを書いてしまうことです。
1
/ 悪い例:条件分岐の罠にはまる典型的なコーディング /
DCL IN_FILE FILE RECORD INPUT;
DCL EOF_FLG CHAR(1) INIT(‘0’);
ON ENDFILE(IN_FILE) EOF_FLG = ‘1’;
OPEN FILE(IN_FILE);
DO WHILE (EOF_FLG = ‘0’);
READ FILE(IN_FILE) INTO(REC_AREA);
/ ここでEOFに達しても、DO WHILEの評価はループの先頭まで行われない /
CALL PROCESS_RECORD();
END;
CLOSE FILE(IN_FILE);
このコードの何が問題か。PL/Iの `ON` ユニット(例外処理機構)は、ハードウェアまたはアクセサからのシグナルを受けて非同期的に割り込み処理を行います。`ENDFILE` 条件が発生して `EOF_FLG = ‘1’` が実行された後、制御は `READ` ステートメントの直後に戻ります。
つまり、最終レコードを読み込んだ直後の `READ` ではデータが取得できていないにもかかわらず、その直後の `CALL PROCESS_RECORD();` が実行されてしまい、未初期化のレコード領域を参照してS0C4などのメモリアクセス例外(アベンド)を引き起こすか、最悪の場合は不正データを後続のDB2へと流し込むことになります。
堅牢な `ON ENDFILE` 制御構造の基本
正しい制御構造では、`READ` の直後に必ず条件判定を挟むか、あるいは `GOTO`(または構造化された `LEAVE`)を `ON` ユニット内で完結させるアプローチを取ります。基幹システムのバッチアーキテクチャにおいて最も信頼性が高いとされるパターンを見てみましょう。
1
/ 正しい例:ONユニット内でループを即座に脱出する構造 /
MAIN_PROC: PROC OPTIONS(MAIN);
DCL MST_FILE FILE RECORD INPUT
ENVIRONMENT(FB BLOCKSIZE(27920) LRECL(80));
DCL 1 MST_REC,
10 MST_KEY CHAR(8),
10 MST_DATA CHAR(72);
DCL IO_EOF BIT(1) ALIGNED INIT(‘0’B);
/ ファイル終了時の割込処理:即座にフラグを立ててブロックを抜ける /
ON ENDFILE(MST_FILE)
чих: BEGIN;
IO_EOF = ‘1’B;
GOTO READ_EX_LBL;
END;
OPEN FILE(MST_FILE);
/ 初回先行読み込み(プライマリー・リード) /
CALL READ_ROUTINE();
DO WHILE (^IO_EOF);
/ 業務ロジックの実行 /
CALL BUSINESS_LOGIC();
/ 次レコードの読み込み /
CALL READ_ROUTINE();
END;
CLOSE FILE(MST_FILE);
RETURN;
READ_ROUTINE: PROC;
READ FILE(MST_FILE) INTO(MST_REC);
RETURN;
READ_EX_LBL:
/ EOF到達時のジャンプ先 /
RETURN;
END MAIN_PROC;
この構造の肝は、「データの有無を確実に担保してから処理を進める(先行読み込み方式)」点にあります。`ENDFILE` が検知された瞬間、`ON` ユニット内の `GOTO` によって無駄なレコード処理をバイパスし、安全にループ外へ脱出します。
—
2. ファイルポインタの整合性と正常クローズの哲学
「ファイルを閉じる(`CLOSE`)」という操作は、単にOSやアクセス方式(BSAM/QSAM)に対してデータセットの解放を命じるだけではありません。バッファ内に残存するダーティデータのフラッシュ、カタログ管理テーブル(VTOC / カタログ)の更新、そして何よりもファイルポインタ(Current File Position)の正確なリセットを意味します。
異常終了時(アベンド)のクローズ漏れとJESログの呪縛
もし `ON ENDFILE` やその他の入出力エラー(`ON ERROR`, `ON TRANSMIT` 等)のハンドリングに失敗し、異常終了コードを返したままファイルがオープンしっぱなしになった場合、次のような深刻な実務上のトラブルが発生します。
1. ENQ競合(ロックの解放漏れ): 次回バッチ実行時に `VSAM` や排他制御がかかった順次ファイルに対して `IEC161I` などのオープンエラー(ABEND S001-4等)が多発する。
2. ダンプ解析の困難化: システマティックなクローズ処理を通らないままアベンドすると、PL/Iのランタイム・コントロール・ブロック(TCAやPCB)が破損し、CEE3203Sなどの言語環境エラーの特定が極めて困難になる。
最適化オプション(`OPTIMIZE`)とポインタのズレ
IBM Enterprise PL/I コンパイラにおいて、`OPTIMIZE(FULL)` などの高度な最適化を適用すると、コンパイラはレジスタ割り当てを極限まで効率化します。この時、I/Oバッファ上のポインタ操作がインライン展開され、意図せぬタイミングでファイルポインタの同期が崩れることがあります。
これを防ぐためには、ファイル操作をカプセル化するサブルーチン群において、必ず `CHECK` や `STRICT` 関連のコンパイラオプション、あるいは以下のようなデータ属性の整合性を厳格に維持する必要があります。
1
/ ポインタとベース変数を用いた動的バッファ制御のエッジケース対策 /
DCL P_REC_BUFFER POINTER;
DCL 1 BASED_REC BASED(P_REC_BUFFER),
5 REC_ID FIXED BIN(15),
5 REC_BODY CHAR(1024);
/ 動的にメモリを割り当てて読み込む場合のエンド制御 /
ALLOCATE BASED_REC;
READ FILE(IN_FILE) SET(P_REC_BUFFER);
このようなポインタベースの入出力を行う場合、`ENDFILE` 到達時にポインタが指し示すアドレスが無効領域を指したまま `CLOSE` を迎えると、ランタイムストレージの破損に直結します。`ENDFILE` を検知した際は、必ずポインタを `NULL()` に明示的にクリアする防衛的プログラミングが不可欠です。
1
ON ENDFILE(IN_FILE) BEGIN;
IO_EOF = ‘1’B;
P_REC_BUFFER = NULL(); / 迷子ポインタの発生を防止 /
END;
—
3. レガシー移行(Java / C# / DB2 / CICS)におけるエッジケース対策
現在、多くの企業がメインフレームからの脱却(レガシーマイグレーション)を推進していますが、PL/I特有の「予約語を持たない柔軟な構文」や「独自のエラーハンドリング」は、JavaやC#などのオブジェクト指向言語へ書き換える際にエンジニアを大いに悩ませます。
① パックデシマル(COMP-3)の内部符号反転バグとEOFの連鎖
PL/Iで定義された `FIXED DECIMAL`(パックデシマル)項目をマイグレーション先(Java等)で処理する際、ファイル終端のパディングや不正なゾーンビット混入により、EOF判定の直前で数値変換エラー(NumberFormatException)を引き起こすケースが後を絶ちません。
特に、ホスト側で符号ニブル(`C`, `D`, `F` 等)が反転している古いデータセットを読み込んでいる場合、`ENDFILE` 到達前の最終レコード判定でバグが顕在化します。移行先では、ホストのエミュレーション層(またはコンバーター)において、ゾーン・パック形式の厳密なパース検証を事前に行う必要があります。
② 埋め込みSQL(DB2)とCICSオンライン処理におけるエッジケース
バッチ処理の順次ファイル(`ENDFILE`)の概念を、CICSオンラインのインプット・メッセージやDB2のカーソル処理(`SQLCODE +100`)にそのまま持ち込もうとして大失敗するケースがあります。
- CICS環境: `ENDFILE` ではなく、端的には `QZERO` や `ENDFILE` に相当するブラウズ終了条件を `ENDFILE` 構文ではなく、EDF(Execution Diagnostic Facility)やRESPコードで明示的にハンドリングしなければなりません。
- DB2カーソル: PL/Iの `ON ENDFILE` はファイルに対してのみ有効であり、DB2カーソルの終端(`SQLCODE = 100`)には適用できません。これを混同し、カーソルフェッチのループでPL/Iのファイル用 `ENDFILE` を使おうとしてコンパイルエラーや論理破綻を起こすアーキテクトが後を絶ちません。DB2の場合は、あくまでも `IF SQLCODE = 100 THEN …` による明示的な制御が鉄則です。
—
結びに代えて:システムアーキテクトとしての矜持
PL/Iという言語は、その歴史の長さゆえに「古いもの」として片付けられがちです。しかし、その内部で動くコンパイラの最適化ロジック、例外処理(`ON` 条件)の洗練された非同期割り込み機構、そしてファイルシステムとの密接な連携は、現代のどのモダン言語のフレームワークよりも「ハードウェアに近い場所でいかに堅牢に動かすか」というエンジニアリングの真髄を教えてくれます。
`ON ENDFILE` とファイルクローズの制御――たった数行の例外処理とループ構造の書き方に、システムの生死を分ける知見が宿っています。テックリードとしてレガシーコードに向き合う時、単なる「機械的な言語の置き換え(リライト)」ではなく、そのコードが背負うアーキテクチャの文脈を深く理解し、次世代の堅牢なシステムへと昇華させることこそが、我々アーキテクトに課された責務なのです。
