おい、最近入った若手がまたやらかしたんだよ。
VSAMから読み込んだ生データの口座番号や日付を、オンライン画面や帳票に出力するためにピクチャ句で編集しようとしたところまでは良かったんだが……ストレージのオーバーレイを起こして、夜間バッチを盛大にブッ飛ばしやがった。
「先輩、`PIC ’99/99/99’` って指定したら、データ長が勝手に変わるなんて聞いてません!」って泣きついてきたんだが、まあ無理もない。C言語やJavaしか触ったことがない世代にとって、PL/Iのピクチャ編集文字、特に `B`(空白挿入)や `/`(スラッシュ挿入)がデータ型そのものを変えてしまうという仕様は、魔術のように見えるらしい。
今日はな、このPL/Iのピクチャ編集における「メモリ確保の罠」と、データ長がどう膨らむのかという現場の勘所を、実際のコードを交えて徹底的に叩き込んでやる。メインフレームの基幹系を背負う者として、このへんのバイナリと文字のせめぎ合いは絶対に押さえておかなきゃいかんのだ。
—
1. 現場を震撼させる「ピクチャ編集」の正体
PL/Iの `PICTURE` 属性(通称 `PIC`)は、単なる表示用のフォーマッターではない。COBOLの `PICTURE` も似たようなもんだが、PL/Iの場合は変数のデータ属性そのものを決定する強力な武器だ。
特に日付や口座番号、あるいは金融機関特有のコード体系を扱う際、以下のような編集文字を使う。
- `B`: 指定した位置にブランク(空白)を挿入する。
- `/`: 指定した位置にスラッシュを挿入する。
ここで問題になるのが、「挿入された文字(`/` や `B`)も、メモリ上では1バイトの文字として実体を持つ」 という点だ。
例えば、生データとして `000000`(6桁の数字)が入っているゾーン十進数(あるいは文字形式)のエリアがあるとしよう。これを `PIC ’99/99/99’` で受けると、どうなるか?
画面や帳票には `00/00/00` と綺麗に表示される。ここまではいい。だが、この編集済み変数のデータ長(バイト数)は、元の6バイトから「8バイト」に膨れ上がっているのだ。
痛い目を見る瞬間:レコード入出力とVSAMのパニック
バッチ処理でVSAMファイル(KSDSやESDS)や固定長/可変長の順次ファイルを読み書きする際、レコードレイアウト(コピーブック)の定義と、プログラム内のワーク領域のサイズが1バイトでもズレるとどうなるか。
そう、S0C4(保護例外) か、あるいは後続項目のデータが盛大にズレるという、夜間オペレータ青ざめもののサイレントエラーを引き起こす。
「画面に出すだけだから」と軽い気持ちでワーク変数の `PIC` 定義をいじると、ストリーム入出力やレコード全体のバッファレイアウトを破壊する。これが、レガシー現場の新人キラーとして代々語り継がれている罠なのさ。
—
2. 実践コード:正しいデータ長の見極め方とバッチ処理
百聞は一見にしかずだ。実際のメインフレームの夜間バッチを想定した、実用的なPL/Iのサンプルコードを見せてやろう。
ここでは、生の日付データ(`6桁`)と口座番号(`10桁`)を読み込み、適切に編集して出力用バッファに組み立てる一連の流れを記述している。大文字でしっかりと書かれた、我が社の誇る標準的なコーディングスタイルだ。
1
/ ================================================================ /
/ プログラム名: DATEDIT – ピクチャ編集とデータ長制御のサンプル /
/ 概要: 生データを安全にピクチャ編集し、ファイル出力する /
/ ================================================================ /
DATEDIT: PROC OPTIONS(MAIN);
DCL 1 IN_RECORD,
5 IN_DATE CHAR(6), / 入力日付: 20231031 (6バイト) /
5 IN_ACCOUNT CHAR(10); / 入力口座: 1234567890 (10バイト) /
/ 【重要】編集後の変数は、挿入文字分の長さに注意する! /
/ PIC ’99/99/99′ は数字6桁 + スラッシュ2文字 = 計 8バイト /
/ PIC ‘XXXX/XXXX/XX’ のような文字編集も同様に文字数が膨らむ /
DCL 1 OUT_RECORD,
/ 日付用: 6桁数字 + 2スロッシュ = 8バイト /
10 OUT_DATE_EDIT PIC ’99/99/99′,
10 FILLER1 CHAR(2) VALUE(‘ ‘),
/ 口座用: 10桁数字 + 2ハイフン = 12バイト /
10 OUT_ACCT_EDIT PIC ‘9999-9999-99’;
DCL SYSIN FILE STREAM INPUT;
DCL SYSPRINT FILE STREAM OUTPUT;
DCL EOF_FLAG CHAR(1) INIT(‘OFF’);
/ 終了条件(ENDFILE)の監視用ONユニット /
ON ENDFILE(SYSIN) EOF_FLAG = ‘ON’;
OPEN FILE(SYSIN) INPUT, FILE(SYSPRINT) OUTPUT;
/ メインループ /
DO WHILE(EOF_FLAG = ‘OFF’);
READ FILE(SYSIN) INTO(IN_RECORD);
IF EOF_FLAG = ‘ON’ THEN LEAVE;
/ — ここがポイント:代入時のピクチャ自動編集 —————- /
/ 右側の生データが、左側のピクチャ形式に暗黙の変換・編集される /
OUT_DATE_EDIT = IN_DATE;
OUT_ACCT_EDIT = IN_ACCOUNT;
/ 編集後の実データ長をBUILTIN関数(LENGTH)で確認してみる /
PUT SKIP EDIT (
‘編集前日付: ‘, IN_DATE,
‘ / 編集後日付(LEN=’, LENGTH(OUT_DATE_EDIT), ‘): ‘, OUT_DATE_EDIT
) (A, A, A, F(2), A, A);
PUT SKIP EDIT (
‘編集前口座: ‘, IN_ACCOUNT,
‘ / 編集後口座(LEN=’, LENGTH(OUT_ACCT_EDIT), ‘): ‘, OUT_ACCT_EDIT
) (A, A, A, F(2), A, A);
/ 実際にはここでOUT_RECORDをファイルに出力する処理が入る /
/ WRITE FILE(OUTFILE) FROM(OUT_RECORD); /
END;
CLOSE FILE(SYSIN), FILE(SYSPRINT);
PUT SKIP EDIT (‘NORMAL TERMINATED.’) (A);
END DATEDIT;
—
3. コードの解説とアーキテクトからの教訓
このコードをコンパイルして実行したとき、`LENGTH(OUT_DATE_EDIT)` が返す値は `6` ではなく `8` になる。ここを勘違いしていると、出力レコード全体のレイアウト定義(構造体や01レベルのオフセット)が後ろに2バイトズレてしまい、次の項目がゴミデータまみれになるか、最悪の場合はアボートする。
現場で役立つデバッグと設計のコツ
1. レコードレイアウト(コピーブック)内でのピクチャ使用の是非
原則として、VSAMや順次ファイルに直接書き込むレコード構造体(I/O用バッファ)の中に、`/` や `B` を含むピクチャ変数を直接定義するのは避けるべきだ。
I/O用はあくまで `CHAR(N)` や `FIXED BINARY` などの生データ領域(ワークエリア)として受け取り、画面表示や帳票作成、あるいは他システム連携用の「出力専用ワーク領域」に移送するタイミングで初めてピクチャ編集を適用するのが、トラブルを防ぐための鉄則だ。
2. 変数の代入における「暗黙の型変換」のコスト
PL/Iは非常に懐が深い言語で、`CHAR(6)` から `PIC ’99/99/99’` への代入を自動的に(暗黙的に)やってくれる。しかし、この裏ではコンパイラが文字のパースや編集文字の挿入処理を行っている。
何百万件も回る基幹系の大規模バッチで、不要かつ複雑なピクチャ編集をインラインで多用すると、CPU時間を無駄に食う原因になる。必要な場所でだけ絞って使うのがプロの技だ。
3. ONユニットと例外処理の構え
今回のコードでも `ON ENDFILE` を使っているが、PL/Iの例外処理(ONユニット)はフロー制御の要だ。データ変換時に万が一数値以外の文字(スペースやゴミ)が `PIC ’99/99/99’` に飛び込んできた場合、CONVERSION条件(シグナル)が発生してアボートする。
もし不正データが混入する可能性が少しでもあるなら、あらかじめ `ON CONVERSION` ブロックを記述してエラータスクに逃げるか、事前に `VERIFY` などのBUILTIN関数で入力チェックを行っておくこと。これが夜間呼び出しを防ぐエンジニアの知恵というものさ。
—
さあ、理屈は分かったか?
明日からのバッチ改修では、「画面や帳票に出すための見た目」と「メモリ上で占有する物理的なバイト数」を常に頭の中でリンクさせながらコードを書くように。
メインフレームのシステムは、こうした細部へのこだわりの積み重ねで動いているんだ。期待してるぞ。
