【実務・中級編】ON ZERODIVIDEによる例外トラップ – PL/Iの基本構文とデータ制御実践ガイド

こんにちは、皆さん。日々のメインフレーム保守や、気の遠くなるようなレガシー移行プロジェクト、本当にお疲れ様です。

私たちが日々向き合っているPL/I(Programming Language One)という言語は、1960年代に生まれた非常に懐の深い汎用言語です。C言語やJavaのように「厳しい予約語(Keyword)」の縛りが少ないのが特徴で、例えば `IF` や `READ` さえも、文脈によっては変数名として定義できてしまう(もちろん、そんな怖いコードを書く人はいませんが)ほどの柔軟性を持っています。

しかし、その柔軟性ゆえに、ひとたび本番バッチで異常終了(ABEND)が発生すると、原因究明が難航することがあります。中でも最も古典的でありながら、実務で最も頭を悩ませるのが「ゼロ除算(ZERODIVIDE)」です。

今回は、基幹システムの現場で若手エンジニアが必ず直面する「`ON ZERODIVIDE` による例外トラップとリカバリ手法」について、私が現場で叩き込んできたノウハウを余すところなく伝授しましょう。

—

なぜPL/Iのゼロ除算トラップが現場で重要なのか?

夜間バッチ処理の最中、突如としてシスログに「ASRA(IBMメインフレームにおけるプログラム例外)」や、PL/I特有のメッセージが出力され、数百万件を処理する巨大なジョブが異常終了する――。

原因を辿っていくと、マスターデータの欠損や、マスタメンテ漏れによって単価や数量といった除算の分母(DIVISOR)に「ゼロ(あるいはスペース等の不当文字からの暗黙的変換によるゼロ)」が入ってしまった、というケースは後を絶ちません。

ここで、何も対策をしていないと、プログラムは問答無用で強制終了します。しかし、PL/IのONユニット(例外処理機構)を正しく使いこなしていれば、システムをクラッシュさせることなく、エラーログを丁寧に出力した上で「該当レコードをスキップして次の処理へ進む」といった、極めて優雅なリカバリが可能になります。

—

予約語を持たないPL/Iの優しさと「罠」

冒頭で触れた通り、PL/Iには厳密な意味での「予約語」がほぼ存在しません。コンパイラは文脈(Context)からそれがキーワードなのかユーザー定義の変数名なのかを判断します。

この特性は `ON` 条件の記述においても現れます。`ON ZERODIVIDE` は、ハードウェアレベルの例外、あるいは演算上の例外を捕捉するための強力な構文ですが、これをどこに、どのように配置するかで、コードの挙動が劇的に変わります。

現場で使えるON ZERODIVIDEの基本制御フロー

1. 有効範囲の設定: `ON ZERODIVIDE BEGIN; … END;` を宣言することで、以降の処理ブロックで発生したゼロ除算を捕捉可能にします。
2. 発生箇所の特定: エラーがどこで起きたのかを特定するためには、PL/Iのビルトイン関数(`ONSOURCE`, `ONCHAR` などは主に変換エラー用ですが、位置特定にはスタック情報やキー項目を保持するロジックが不可欠です)を活用します。
3. 制御の戻り先: エラー処理が終わった後、そのまま次の文へ進ませるのか、あるいは処理をアベンドさせるのかを明確に制御します。

—

実践:VSAMファイルを読み込みながらゼロ除算を回避するサンプルコード

百聞は一見に如かず。実際のメインフレームのバッチプログラムを想定した、実用的なPL/Iソースコードを見てみましょう。KSDS(Key-Sequenced Data Set)のVSAMファイルを読み込み、単価割当計算を行うというよくあるシチュエーションです。

1
DIV_CALC: PROC OPTIONS(MAIN);

/————————————————-/
/ 変数宣言ブロック /
/————————————————-/
DCL VSAM_IN FILE RECORD INPUT;
DCL SYSPRINT FILE STREAM OUTPUT;

/ レコード定義(入力:売上トランザクション) /
DCL 1 IN_REC,
5 IN_KEY CHAR(8), / 顧客コード /
5 IN_NAME CHAR(20), / 顧客名 /
5 IN_AMOUNT FIXED DEC(9,2), / 売上金額 /
5 IN_QTY FIXED DEC(5,2); / 数量(分母になる) /

/ 計算結果用変数 /
DCL W_UNIT_PRICE FIXED DEC(9,4);
DCL W_ERR_COUNT FIXED BIN(31) INIT(0);
DCL EOF_FLG CHAR(1) INIT(‘OFF’);

/ ファイル終了(ENDFILE)の条件定義 /
ON ENDFILE(VSAM_IN) EOF_FLG = ‘ON’;

/————————————————-/
/ ZERODIVIDE例外トラップの定義 /
/————————————————-/
ON ZERODIVIDE BEGIN;
W_ERR_COUNT = W_ERR_COUNT + 1;
PUT EDIT (‘【警告】ゼロ除算発生 – 顧客コード: ‘, IN_KEY,
‘ 数量がゼロまたは不正です。このレコードをスキップします。’)
(A, A, A) SKIP;

/ 単価にゼロをセットして処理を続行(異常終了を回避) /
W_UNIT_PRICE = 0;

/ GOTOを使用してエラー処理ブロックから安全に抜け出す /
GOTO NEXT_RECORD;
END;

/ 処理開始ログ /
PUT EDIT (‘ 顧客別単価計算バッチ 開始 ‘) (A) SKIP;

/ VSAMファイルのオープン /
OPEN FILE(VSAM_IN);

/ 初回リード /
READ FILE(VSAM_IN) INTO(IN_REC);

/ メインループ /
DO WHILE (EOF_FLG = ‘OFF’);

/ ここでゼロ除算が発生する可能性がある /
/ 数量(IN_QTY)が0の場合、上記のON ZERODIVIDEが即座にトラップする /
W_UNIT_PRICE = IN_AMOUNT / IN_QTY;

/ 正常系の出力処理 /
PUT EDIT (‘顧客:’, IN_KEY, ‘ 金額:’, IN_AMOUNT, ‘ 単価:’, W_UNIT_PRICE)
(A, A, A, F(9,2), A, F(9,4)) SKIP;

NEXT_RECORD:
/ 次レコードの読み込み /
READ FILE(VSAM_IN) INTO(IN_REC);

END;

/ 終了処理 /
CLOSE FILE(VSAM_IN);

PUT EDIT (‘ 処理終了. エラー件数 = ‘, W_ERR_COUNT) (A, F(5)) SKIP;

END DIV_CALC;

—

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