【実務・中級編】VERIFY関数による文字列内の不正文字検出ロジック – PL/Iの基本構文とデータ制御実践ガイド

PL/I VERIFY関数を使いこなせ!入力チェックでバッチ処理の安定性を劇的に向上させる方法

おい、君たち。PL/Iでバッチ処理を書いていて、入力データのチェック、どうしてる? 「え、なんか怪しい文字が入ってたらエラーで止まるようにしてますけど…」なんて、ちょっと甘い考えは捨ててもらおうか。今回は、PL/Iに標準搭載されている `VERIFY` 関数を武器にして、入力チェックの精度と堅牢性を格段に上げる方法を伝授する。ベテランの現場で培った、あの「なるほど!」と思わせるコツと、すぐに使えるコード例もたっぷり用意したから、しっかりついてくるんだぞ。

そもそもVERIFY関数って何だっけ?

まず基本から。`VERIFY` 関数は、ある文字列の中に、指定した「許可する文字」のセットに含まれない文字があるかどうかを調べるための関数だ。具体的には、

  • 第一引数: 検査対象の文字列
  • 第二引数: 許可する文字のセット(文字列)

この二つを渡すと、第一引数の文字列で、第二引数のセットに 含まれない 最初の文字の位置(0から始まるインデックス)を返す。もし、第一引数の文字列が全て第二引数のセットに含まれる文字で構成されていれば、第一引数の文字列の長さを返すんだ。

例えば、こんな感じだ。

DECLARE INPUT_STRING CHAR(10) VARYING;
DECLARE ALLOWED_CHARS CHAR(5) INITIAL(‘0123456789’); / 数字のみ許可 /
DECLARE POS FIXED BINARY(15);

INPUT_STRING = ‘12345’;
POS = VERIFY(INPUT_STRING, ALLOWED_CHARS);
/ POS の値は 6 (文字列長) になる /

INPUT_STRING = ’12A45′;
POS = VERIFY(INPUT_STRING, ALLOWED_CHARS);
/ POS の値は 2 (A の位置) になる /

「へえ、意外とシンプルだな」と思っただろう? そうなんだ。でも、このシンプルさが、後述する実践的な入力チェックで絶大な威力を発揮する。

なぜVERIFY関数が入力チェックに有効なのか?

「いやいや、ループで一文字ずつチェックしたって同じじゃないの?」という声が聞こえてきそうだ。確かに、昔ながらのやり方で、`DO` ループと `SUBSTR` 関数、そして `INDEX` 関数を組み合わせて、一つずつ文字をチェックすることもできる。だが、`VERIFY` 関数を使うことには、いくつかの大きなメリットがある。

1. コードの簡潔性: ループ処理を自前で書く必要がなくなり、コードが格段にスッキリする。保守性向上に直結するぞ。
2. パフォーマンス: 内部的に最適化されているため、手書きのループ処理よりも高速に動作することが期待できる。特に、長大な文字列や大量のデータを扱うバッチ処理では、この差が無視できなくなる。
3. 意図の明確化: `VERIFY` という名前の通り、「この文字列には、許可されていない文字が含まれていないか?」という意図がコードを見ただけで明確になる。これは、開発者だけでなく、後でコードを読みに来る人にとっても大きな助けとなる。

実践!入力チェックルーチンでのVERIFY関数活用パターン

さて、ここからが本番だ。実際のバッチ処理で、`VERIFY` 関数をどう活用していくか、いくつかのパターンを見ていこう。

パターン1:基本の数字チェック(数値項目)

最も基本的なのは、数値として扱いたい項目が、本当に数字だけで構成されているかを確認するケースだ。

シナリオ: 顧客マスターの更新処理で、顧客ID(数字10桁)が不正な値でないかチェックする。

コード例:

/

  • INPUT_DATA_RECORD: 入力レコード (構造化されていると仮定)
  • – CUST_ID_FIELD CHAR(10)
  • CHECK_VALID_INPUT: 入力レコードの有効性をチェックするサブルーチン

/
CHECK_VALID_INPUT: PROC(INPUT_DATA_RECORD);
DCL INPUT_DATA_RECORD LIKE MY_INPUT_RECORD; / 入力レコード構造体 /
DCL ALLOWED_DIGITS CHAR(10) INITIAL(‘0123456789’);
DCL VERIFY_RESULT FIXED BINARY(15);

/ — 顧客IDのチェック — /
VERIFY_RESULT = VERIFY(INPUT_DATA_RECORD.CUST_ID_FIELD, ALLOWED_DIGITS);
IF VERIFY_RESULT > 0 THEN DO;
/

  • 許可されていない文字が見つかった場合。
  • VERIFY_RESULT は不正文字の位置 (1から始まる) を示す。
  • ここでエラーメッセージを出力したり、レコードをスキップしたりする処理を行う。

/
PUT SKIP LIST(‘ERROR: Invalid character in CUST_ID_FIELD at position ‘ ||
VERIFY_RESULT || ‘. Value: ‘ || INPUT_DATA_RECORD.CUST_ID_FIELD);
/ エラー処理を記述 /
/ 例: CALL ERROR_HANDLING_ROUTINE(RECORD_NUMBER, ‘CUST_ID’, VERIFY_RESULT); /
RETURN; / このレコードは処理しない /
END;
/ ここまで来たら CUST_ID_FIELD は数字のみで構成されている /

/ — 他の項目のチェックも同様に記述 — /
/ 例: 商品コード、金額など /

END CHECK_VALID_INPUT;

解説:
`VERIFY` 関数が返す値が0より大きいということは、`ALLOWED_DIGITS` に含まれない文字が `CUST_ID_FIELD` の中に存在することを意味する。その `VERIFY_RESULT` の値は、不正な文字が見つかった最初の位置(PL/Iでは0から始まるインデックスなので、表示する際は+1すると人間にも分かりやすい)を示してくれる。ここでは、エラーメッセージを出力して、そのレコードの処理を中断する例を示した。実際のシステムでは、エラーコードを設定したり、エラーファイルに記録したりする処理が続くことになるだろう。

パターン2:英数字チェック(コード項目)

商品コードや、英数字で構成されるIDなど、許可する文字セットが広がる場合も `VERIFY` 関数は有効だ。

シナリオ: 商品マスタの更新処理で、商品コード(英大文字と数字)が不正な値でないかチェックする。

コード例:

/

  • INPUT_DATA_RECORD: 入力レコード (構造化されていると仮定)
  • – PRODUCT_CODE CHAR(8)
  • CHECK_PRODUCT_CODE: 商品コードの有効性をチェックするサブルーチン

/
CHECK_PRODUCT_CODE: PROC(INPUT_DATA_RECORD);
DCL INPUT_DATA_RECORD LIKE MY_INPUT_RECORD;
DCL ALLOWED_ALPHANUMERIC CHAR(36) INITIAL(
‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’ ||
‘0123456789’
);
DCL VERIFY_RESULT FIXED BINARY(15);

/ — 商品コードのチェック — /
VERIFY_RESULT = VERIFY(INPUT_DATA_RECORD.PRODUCT_CODE, ALLOWED_ALPHANUMERIC);
IF VERIFY_RESULT > 0 THEN DO;
PUT SKIP LIST(‘ERROR: Invalid character in PRODUCT_CODE at position ‘ ||
VERIFY_RESULT || ‘. Value: ‘ || INPUT_DATA_RECORD.PRODUCT_CODE);
/ エラー処理 /
RETURN;
END;
/ OK: PRODUCT_CODE は英大文字と数字のみ /

END CHECK_PRODUCT_CODE;

解説:
許可する文字セットを `ALLOWED_ALPHANUMERIC` として定義している。この文字列に、許可したい全ての英大文字と数字を含める。あとは、先ほどと同じように `VERIFY` 関数でチェックするだけだ。

パターン3:特定の記号を含むチェック

全角文字や、特定の記号(ハイフン、スラッシュなど)を許可したい場合も、許可文字セットを工夫すれば対応できる。

シナリオ: 住所フィールド(全角カナと一部記号を許可)のチェック。

コード例:

/

  • INPUT_DATA_RECORD: 入力レコード (構造化されていると仮定)
  • – ADDRESS_FIELD CHAR(100) VARYING
  • CHECK_ADDRESS_FIELD: 住所フィールドの有効性をチェックするサブルーチン

/
CHECK_ADDRESS_FIELD: PROC(INPUT_DATA_RECORD);
DCL INPUT_DATA_RECORD LIKE MY_INPUT_RECORD;
DCL ALLOWED_ADDRESS_CHARS CHAR(100) VARYING; / 動的に生成する場合も /
DCL VERIFY_RESULT FIXED BINARY(15);

/

  • 許可する文字セットを定義。
  • 全角カナ、スペース、中黒(・)、カンマ(,)、ダッシュ(-)などを想定。
  • 注意: 文字コードによっては、全角文字の表現に注意が必要。
  • (EBCDIC環境での全角カナの範囲など)

/
ALLOWED_ADDRESS_CHARS = ‘ ABCDEFGHIJKLMNOPQRSTUVWXYZ’ || / 全角スペース、英大文字 /
‘アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン’ || / 全角カナ /
‘、。「」・ー―,”()-/’ ; / 句読点、記号類 /

VERIFY_RESULT = VERIFY(INPUT_DATA_RECORD.ADDRESS_FIELD, ALLOWED_ADDRESS_CHARS);
IF VERIFY_RESULT > 0 THEN DO;
PUT SKIP LIST(‘WARNING: Potentially invalid character in ADDRESS_FIELD at position ‘ ||
VERIFY_RESULT || ‘. Value: ‘ || INPUT_DATA_RECORD.ADDRESS_FIELD);
/

  • 住所フィールドの場合、致命的なエラーとせず、
  • 警告として記録し、後続処理に進む場合もある。
  • ここでは警告として処理。

/
/ 例: CALL WARNING_LOGGING(RECORD_NUMBER, ‘ADDRESS’, VERIFY_RESULT); /
END;
/ OK: ADDRESS_FIELD は許可された文字セットで構成されている /

END CHECK_ADDRESS_FIELD;

解説:
このパターンで最も注意すべきは、`ALLOWED_ADDRESS_CHARS` の定義だ。EBCDIC環境では、全角文字の範囲がASCIIとは異なるため、コンパイラオプションや、必要であれば `TRANSLATE` 関数などを駆使して、正しく文字セットを定義する必要がある。ここでは、全角カナや記号を想定して定義したが、実際の要件に合わせて柔軟に変更すること。また、住所のようなフィールドでは、多少の「不正」な文字があったとしても、システム全体を停止させるほどではない場合もある。そんな時は、エラーとするのではなく、警告として記録し、後続処理に進むという判断もあり得る。

ONユニットとの連携

`VERIFY` 関数は、単独で使うだけでなく、ONユニットと組み合わせることで、より強力なエラーハンドリングを実現できる。例えば、`FIXEDOVERFLOW` ONユニットで発生したオーバーフローエラーを捕捉し、その際に対象の数値フィールドを `VERIFY` 関数でチェックするといった連携も考えられる。

ON FIXEDOVERFLOW CALL CHECK_FIXED_OVERFLOW; / ONユニット設定 /

CHECK_FIXED_OVERFLOW: PROC;
DCL OVERFLOW_DATA CHAR(20); / オーバーフローした値 (文字列として取得) /
DCL ALLOWED_DIGITS CHAR(10) INITIAL(‘0123456789’);
DCL VERIFY_RESULT FIXED BINARY(15);

/

  • OVERFLOW() BUILTIN関数などで、オーバーフローした値を文字列として取得。
  • (環境やコンパイラバージョンによって取得方法が異なる場合がある)
  • ここでは仮にOVERFLOW_DATAという変数に格納されるとする。

/

VERIFY_RESULT = VERIFY(OVERFLOW_DATA, ALLOWED_DIGITS);
IF VERIFY_RESULT > 0 THEN DO;
PUT SKIP LIST(‘FIXEDOVERFLOW detected. Invalid characters in overflowed data: ‘ ||
OVERFLOW_DATA || ‘ at position ‘ || VERIFY_RESULT);
/ 適切なエラー処理 /
END;
ELSE DO;
PUT SKIP LIST(‘FIXEDOVERFLOW detected. Data is numeric: ‘ || OVERFLOW_DATA);
/ 数値としては有効だがオーバーフローした、という情報として記録 /
END;
END CHECK_FIXED_OVERFLOW;

解説:
`FIXEDOVERFLOW` のような例外状態が発生した際、その原因となったデータが本当に期待する形式(例えば数字)になっているのかを確認するために `VERIFY` 関数が役立つ。これにより、単なるオーバーフローエラーだけでなく、データ形式の不正に起因するオーバーフローの可能性も検知できる。

レコード入出力、VSAMアクセスとの組み合わせ

もちろん、`VERIFY` 関数は、レコード入出力やVSAMアクセスで読み込んだデータに対しても、そのまま適用できる。

DCL INPUT_RECORD … ; / 入力レコード定義 /
DCL ALLOWED_CHARS CHAR(10) INITIAL(‘0123456789.’); / 数字と小数点のみ許可 /
DCL VERIFY_RESULT FIXED BINARY(15);

/ VSAMファイルからのレコード読み込み /
READ FILE(MYVSAMFILE) INTO(INPUT_RECORD);

/ 読み込んだレコードの金額フィールドをチェック /
VERIFY_RESULT = VERIFY(INPUT_RECORD.AMOUNT_FIELD, ALLOWED_CHARS);
IF VERIFY_RESULT > 0 THEN DO;
PUT SKIP LIST(‘Invalid character in AMOUNT_FIELD: ‘ || INPUT_RECORD.AMOUNT_FIELD);
/ エラー処理 /
END;

解説:
ファイルから読み込んだデータは、そのままPL/Iの文字列変数として扱える。したがって、`VERIFY` 関数を適用するのに特別な工夫は必要ない。重要なのは、どのフィールドにどのような文字が許可されるのかを正確に把握し、`ALLOWED_CHARS` を適切に定義することだ。

まとめ:VERIFY関数で堅牢なバッチ処理を!

どうだったかな? `VERIFY` 関数は、一見地味だが、PL/Iでバッチ処理を書く上で非常に強力なツールだ。

  • コードをシンプルに保ちたい。
  • 処理速度を少しでも向上させたい。
  • 入力データの堅牢性を高めたい。

そんな時は、迷わず `VERIFY` 関数を使ってみてほしい。

今回紹介したパターン以外にも、要件に応じて様々な応用が考えられるだろう。重要なのは、「このフィールドには、この文字セットしか入ってこないはずだ」 という確信を持ち、その確信をコードで保証することだ。`VERIFY` 関数は、そのための強力な味方になってくれる。

君たちの書くプログラムが、この `VERIFY` 関数によって、より一層安定し、保守しやすくなることを願っている。何か分からないことがあれば、いつでも聞きに来い。

タイトルとURLをコピーしました