お疲れ様です。今日も元気にオンラインのログと格闘していることと思います。
メインフレームの現場で長く生きていると、画面やファイルから飛んでくる「想定外の文字」に泣かされる夜を何度となく経験しますよね。特にオープン系システムやWebフロントエンドと連携するバッチ処理では、EBCDICの体系から外れた文字や、使ってはならない制御文字が混入して異常終了…なんていうのは、もはやレガシー開発の風物詩です。
さて、今回はPL/Iにおける文字セット検証の要、`VERIFY`関数を取り上げます。
「識別子に予約語がないため、自由度は高いけれど一歩間違えるとカオスになる」というPL/Iの特性を踏まえつつ、この`VERIFY`が内部でどのように動き、実務のVSAM入出力やエラーハンドリング(ONユニット)の中でどう活きるのか、徹底的に紐解いていきましょう。
—
1. 予約語を持たないPL/Iと識別子の世界
本題に入る前に、PL/Iという言語のユニークな思想について少し触れておきます。
C言語やJavaなどのモダンな言語には、`if`やまり、`while`といった「予約語(Reserved Words)」が厳格に存在し、これらを変数名として使うことはできません。
しかし、PL/Iには原則として真の予約語が存在しません。
つまり、`IF`や`VERIFY`という名前の変数や配列を定義することすら、コンパイラ上は理論的に可能です(コンテキストによってキーワードか識別子かが自動判別されるためです)。
しかし、これを実務でやったらコードレビューで大目玉を食らうどころか、後任のプログラマから呪われます。
したがって、我々ベテランが守るべきコーディング標準としては、組み込み関数(Built-in Functions)名やキーワードは決して変数名に流用せず、組み込み関数を使う際は明示的に `BUILTIN` 属性を宣言する、あるいはコンパイラオプションで安全性を担保することが鉄則となります。
—
2. VERIFY関数の内部ロジックとマシーン・インストラクション
では、今回のメインテーマである `VERIFY` 関数について深掘りします。
`VERIFY(string, verify_string)` は、第1引数の文字列の中に、第2引数の文字列に含まれない文字が最初に登場する位置(文字単位のオフセット)を返す関数です。もし第1引数のすべての文字が第2引数の文字セットで完全に網羅されていれば、`0` を返します。
内部で何が行われているか?
メインフレームのアーキテクチャ(z/Architecture)を意識したことがある方ならピンと来るはずです。この処理、コンパイルされると内部的に何をしていると思いますか?
PL/Iのオプタイジング・コンパイラは、`VERIFY`関数を単なるループによる文字比較には展開しません。文字セット(第2引数)の組み合わせパターンを、内部的に256ビット(32バイト)のビットマップ・テーブル(またはトランスレーション・テーブル)に展開します。
そして、S/370およびz/Architectureの強力な文字操作命令(例えば、`TRT`:Translate and Test 命令など)を駆使し、ハードウェアレベルの高速スキャンを実行しています。
要するに、`VERIFY`は「自前でループを回してIF文で1文字ずつ比較する」ような愚直なコードを書くよりも、圧倒的に高速かつ安全に、メインフレームのCPUパワーを限界まで引き出して動作するよう作り込まれているのです。これを自前で書こうなどと決して思わないでください。コンパイラの親心(最適化)を素直に受け取るのがプロの作法です。
—
3. 実践:VSAMマスター更新バッチでのデータ検証コード
百聞は一見にしかず。実際の基幹バッチプログラムを想定したコードを見てみましょう。
ここでは、VSAM(KSDS)から読み込んだ顧客マスターレコードの氏名・住所フィールドに、許容されない特殊文字や制御コードが混入していないかを `VERIFY` でチェックし、不正なデータがあれば `ON` ユニットやエラー処理ルーチンに飛ばす、という実務に直結するパターンを実装します。
1
TESTPRG: PROC OPTIONS(MAIN);
/ ————————————————– /
/ ファイル定義 (VSAM KSDS 入力) /
/ ————————————————– /
DCL CUSTMST FILE RECORD INPUT
ENVIRONMENT(VSAM);
DCL 1 CUST_REC,
5 CUST_ID PIC ‘9(8)’, / 顧客ID /
5 CUST_NAME CHAR(30), / 顧客氏名 /
5 CUST_ADDR CHAR(50); / 顧客住所 /
/ ————————————————– /
/ 変数定義とBUILTIN宣言 /
/ ————————————————– /
DCL VERIFY BUILTIN; / 組み込み関数明示 /
DCL END_OF_FILE BIT(1) INIT(‘0’B); / EOFフラグ /
DCL ERR_COUNT FIXED BIN(31) INIT(0);/ エラーカウンタ /
/ 許可する文字セットの定義 /
/ (英大文字、スペース、および一部の記号のみ許可) /
DCL VALID_CHARS CHAR(38) STATIC
INIT(‘ ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.’);
/ ————————————————– /
/ ファイルオープン /
/ ————————————————– /
OPEN FILE(CUSTMST);
/ 終了条件(ENDFILE)のONユニット定義 /
ON ENDFILE(CUSTMST) END_OF_FILE = ‘1’B;
/ メインループ /
READ FILE(CUSTMST) INTO(CUST_REC);
DO WHILE (^END_OF_FILE);
/ ———————————————- /
/ VERIFY関数による文字セット検証 /
/ ———————————————- /
/ CUST_NAMEの中にVALID_CHARS以外の文字があるか? /
IF VERIFY(CUST_NAME, VALID_CHARS) > 0 THEN DO;
PUT SKIP EDIT (‘【ERROR】不正文字検出 (氏名): ‘, CUST_ID, CUST_NAME)
(A, X(1), A, X(1), A);
ERR_COUNT = ERR_COUNT + 1;
END;
/ 住所フィールドの検証(同様にチェック) /
IF VERIFY(CUST_ADDR, VALID_CHARS) > 0 THEN DO;
PUT SKIP EDIT (‘【ERROR】不正文字検出 (住所): ‘, CUST_ID, CUST_ADDR)
(A, X(1), A, X(1), A);
ERR_COUNT = ERR_COUNT + 1;
END;
/ 次のレコード読み込み /
READ FILE(CUSTMST) INTO(CUST_REC);
END;
/ 終了処理 /
CLOSE FILE(CUSTMST);
PUT SKIP EDIT (‘— 処理終了 — 総エラー件数: ‘, ERR_COUNT)
(A, F(5));
END TESTPRG;
—
4. 保守・移行現場で役立つデバッグとチューニングのコツ
このコードを実務に投入するにあたり、ベテランからのアドバイスをいくつか授けます。
1. 第2引数(VALID_CHARS)は `STATIC` 属性をつけろ
もしループの内部や手続きの都度、`VALID_CHARS` のような定数文字列を動的に生成したり、属性なしで定義したりすると、コンパイラや実行時環境が無駄な領域確保と初期化を行ってしまい、パフォーマンスがガタ落ちします。定数は必ず `STATIC`(または `INIT`)で静的に持たせ、テーブル展開の効率を最大化させましょう。
2. EBCDICとASCIIの混同に注意(マイグレーション時の罠)
オープン系から移行してきた若いエンジニアが最もハマるのがこれです。EBCDIC環境下では、文字の大小関係や連続性(例えば ‘A’ から ‘Z’ が連番であるか)がASCIIとは異なります。文字リテラルや範囲指定(例えば ‘A-Z’ のような表現を自前でループさせようとした場合など)を行うと、EBCDICのコードポイントの隙間(ホール)に足を取られます。`VERIFY` 関数を使う際は、明示的に許可する文字をすべて並べたストリングを定義するのが、最も安全で確実なレガシー防衛策です。
3. ONユニットと組み合わせた例外制御
今回は `IF` 文で `VERIFY` の戻り値が `0` より大きいかを判定していますが、もしこれが必須項目のブランクチェックや、数値項目のパックス辞書チェックであれば、`CHECK` 条件や独自の `ON` 条件と組み合わせることで、エラーハンドリングをメイン処理から美しく分離することも可能です。
—
おわりに
PL/Iは、古い言語と侮るなかれ、IBMメインフレームのハードウェア特性(アーキテクチャ)と極限までチューニングされたコンパイラが一体となって、恐ろしいほどの処理スループットを発揮する素晴らしい言語です。
`VERIFY` 関数のような一見地味な組み込み関数であっても、その内部挙動を理解してコードを書くのと、ただ動くからと適当にコピペするのとでは、数百万件のレコードを処理する深夜バッチの実行時間に数分の差となって現れます。
基幹システムの命を預かるプログラマとして、ぜひ「内部で何が起きているか」を想像しながら、美しいPL/Iコードを書き続けてください。応援しています!
