おい、調子はどうだ?
今日もどこかのジョブネットで、膨大な量産バッチが唸りをあげている頃だな。
メインフレームの現場でPL/Iを書いていると、C言語やJavaあたりから入ってきた連中が首を傾げる瞬間によく遭遇する。その最たるものが、「PL/Iには予約語(Reserved Words)という概念が(厳密には)存在しない」という事実だ。
「えっ、じゃあ `READ` とか `ENDFILE` って変数名に使えちゃうんですか?」
――使えちゃうんだな、これが。コンパイラは文脈から「お、ここは動詞だな」「ここは変数名だな」と空気を読んで解釈してくれる。だがな、そんな自由度の高さに甘えて変な命名をしていると、ある日突然、コンパイラに裏切られて深夜の呼び出し食らうことになる。特に、ファイル入出力の終端制御、すなわち `ENDFILE` 条件(ONユニット)周りの挙動は、メインフレームエンジニアとしての腕の見せ所だ。
今日は、順次ファイルやVSAM(KSDS/ESDS)の読み込み終了時における `ENDFILE` の正しい手懐け方と、ファイルポインタの整合性を守るためのコーディング規約について、みっちり叩き込んでやる。耳の穴かっぽじって聞いてくれ。
—
1. PL/Iの「予約語がない」言語仕様と、ENDFILEの罠
まず大前提として、PL/Iの識別子(変数名)のルールを確認しておく。
PL/Iでは、キーワード(`READ`, `WRITE`, `OPEN`, `CLOSE`, `IF`, `DO` など)はコンテキストキーワード扱いだ。つまり、文脈上そこが変数名として使われていれば、コンパイラはエラーにせず変数として受け入れてしまう。
たとえば、こんなコードを書く馬鹿はいないと思うが:
1
DCL ENDFILE FIXED BIN(31); / 最悪の命名センス /
こんなことをやると、後続の `ON ENDFILE(SYSIN)` が何がなんだか分からなくなって、コンパイラが発狂するか、意図しないシンタックスエラーの迷宮に迷い込むことになる。「言語仕様上可能であっても、主要な制御キーワードや組み込み関数名は絶対に変数名に使うな」。これが現場の第一鉄則だ。
ENDFILE条件は「例外処理」ではなく「制御フロー」の一部だ
C言語の `feof()` や Javaの `hasNext()` に慣れていると、ファイル末尾の判定は「ループの条件式で毎回チェックするもの」と思いがちだ。しかし、PL/Iの順次入出力(Sequential I/O)における `ENDFILE` は、例外条件(Condition)である。
ファイル読み込み中に物理的または論理的なファイルの終端(EOF)に達した瞬間、OSとランタイム(LE: Language Environment)がそれを検知し、未処理であればプログラム異常終了(ABEND: S001など)を引き起こす。これを安全にキャッチして正常なループ脱出に導くのが `ON ENDFILE` ユニットだ。
—
2. 実践:正しいENDFILE制御とファイルクローズの作法
百聞は一見にしかずだ。実務でそのまま使える、洗練されたPL/Iのバッチ処理プログラムの骨組みを見せてやろう。
大文字ベース、適切なインデント、そしてバグを生まないための「お作法」がしっかり詰まっている。
1
/================================================================/
/ 顧客マスタ順次読み込みバッチ処理サンプル /
/================================================================/
CUST_BATCH: PROC OPTIONS(MAIN);
/ — ファイル定義 (DD名: CUSTFILE) — /
DCL CUSTFILE FILE RECORD INPUT;
/ — レコード構造体定義 — /
DCL 1 CUST_REC,
5 CUST_ID CHAR(8),
5 CUST_NAME CHAR(30),
5 FILLER CHAR(62);
/ — 制御フラグおよびカウンタ — /
DCL EOF_FLG BIT(1) INIT(‘0’B);
DCL READ_COUNT FIXED BIN(31) INIT(0);
/ — ファイルオープン — /
OPEN FILE(CUSTFILE) INPUT;
/ — ENDFILE条件の捕捉(ONユニットの定義) — /
/ ※ 該当ファイルからEOFが返された場合の割り込み処理を記述 /
ON ENDFILE(CUSTFILE)
BEGIN;
EOF_FLG = ‘1’B; / 終了フラグをONにする /
END;
/ — メイン読み込みループ — /
/ 初回読み込み /
READ FILE(CUSTFILE) INTO(CUST_REC);
DO WHILE (^EOF_FLG);
/ レコード処理ロジック /
READ_COUNT = READ_COUNT + 1;
/ 例: 20桁目以降の集計処理などを行う /
/ 次レコードの読み込み(ループ継続のトリガー) /
READ FILE(CUSTFILE) INTO(CUST_REC);
END;
/ — 後処理(ファイルクローズと整合性の担保) — /
CLOSE FILE(CUSTFILE);
PUT SKIP LIST(‘TOTAL READ RECORDS = ‘ || TRIM(TO_CHAR(READ_COUNT)));
RETURN;
END CUST_BATCH;
—
3. ベテランが教えるデバッグのコツと「絶対に守るべき規約」
上記のコードを見て、「おっ、王道だな」と思ったお前、甘い。
ここからは、数々の障害対応やレガシーマイグレーションの現場で血を流してきたベテランだからこそ言える、「絶対にハマる罠と回避策」を授けよう。
① ONユニットのスコープ(有効範囲)を意識しろ
`ON ENDFILE(CUSTFILE)` の宣言は、そのブロック内、あるいは動的な呼び出し階層において有効だ。もしサブプロシージャや別のルーチンを挟む場合、意図しないスコープでONユニットが上書きされたり、思わぬところで発火したりする。
可能な限り、ファイルを開く(OPEN)直前でONユニットを定義し、閉じる(CLOSE)直前、あるいはプロシージャの適切なスコープ内で完結させること。
② ファイルポインタの整合性と「二重読み込み防止」
上記のコードでは、`DO WHILE` に入る前に1回 `READ` し、ループの最後でも `READ` をしている(いわゆる「プライミング・リード(Priming Read)」パターンだ)。
これがなぜ重要か分かるか?
もし `DO WHILE (^EOF_FLG)` の中で先に処理を回し、ループの最後に `READ` を置かないと、EOFに達した瞬間の判定タイミングが狂い、最後尾のレコードを2回処理してしまったり、逆にデータを取りこぼしたりする。
また、`ENDFILE` 条件が発生したあとに、さらに `READ` を発行しようものなら、ランタイムエラー(IBMなら `IBM0211S` あたりのメッセージか、最悪の場合はS031などの入出力ABEND)の餌食になる。`EOF_FLG` で確実にガードがかかっているか、毎度コードレビューで目を光らせる必要がある。
③ CLOSEの確実な実行(異常終了時の配慮)
サンプルコードでは正常系のみを書いているが、実務では処理途中でエラー(異常終了)が発生することが多々ある。
PL/Iでは、プログラムが異常終了した際にもランタイムがオープン中のファイルを自動クローズしてくれる機能(またはJESのジョブ終了処理)があるが、ファイルポインタの不整合やロック(VSAMの場合)を残さないために、ON ERROR条件やトランザクション制御の中で確実に `CLOSE` を呼ぶ設計にしておくのがプロの仕事だ。
—
まとめ
PL/Iの構文は自由度が高い分、書き手のセンスと基礎知識の有無がそのままコードの品質に跳ね返る。
「予約語がないから何でも書ける」ではなく、「何でも書けるからこそ、チームで厳格なコーディング標準を設けて規律を保つ」。これこそが、メインフレームの基幹システムを何十年も無事故で稼働させ続けるための極意だ。
次にバッチプログラムの改修や新規作成を行うときは、今日の話を思い出してくれ。
お前の書いたその綺麗なコードが、夜間のバッチ윈ドー(実行時間帯)を救うことになるんだからな。それじゃ、次のタスクに取り掛かるとしようか。
