【実務・中級編】ON ZERODIVIDE条件の捕捉と制御 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近夜間バッチのABEND解析で冷や汗をかいていないか?
「またシステム0C8だ……一体どこでゼロ除算を踏んだんだ?」と、コアダンプ(SYSUDUMP)の広大なメモリアドレスを夜通し睨めっこするのは、もうそろそろ終わりにしよう。

メインフレームの現場において、商用バッチプログラムの突然の異常終了ほど胃が痛くなる瞬間はない。特に、外部から流れてくるマスタデータやトランザクションの数量・金額項目に、前触れもなく「ゼロ」が混入していたり、あるいはデータ破損でスペース(ハイ・ロー)が入り込んでいたりして発生するゼロ除算(ZERODIVIDE)。これを防ぐ手立てを知らないと、深夜の緊急コールで叩き起こされる羽目になる。

今回は、PL/Iが誇る強力な例外処理機構である `ON ZERODIVIDE`条件 を取り上げる。システムデフォルトの無慈悲なABENDを華麗に回避し、ログに適切なメッセージを残して安全にリターン、あるいは後続処理を継続するための実践的なコーディング作法を叩き込んでやろう。

—

1. 予約語を持たないPL/Iと「ON条件」の哲学

PL/Iという言語の最大の特徴の一つは、COBOLのように「何が何でも予約語でガチガチに固められているわけではない」という点だ(厳密にはキーワードはあるが、文脈によって変数名にも使える柔軟性がある)。この柔軟かつ泥臭いまでに実用的な言語仕様において、例外処理は `ON` ステートメントによって制御される。

ゼロ除算が発生した瞬間、OSやハードウェアが検知した例外をPL/Iのランタイム環境(Language Environment:LE)がキャッチし、ユーザーが定義した `ON ZERODIVIDE` ユニットへ制御を移してくれる。

もしこれを怠るとどうなるか?
硬件割り込みコード `0C8`(Fixed-point divide exception)または `0C9`(Floating-point divide exception)が発生し、容赦なくバッチジョブはU0000やS0CBで異常終了する。夜間運用担当者から「ジョブが落ちました」と泣きが入る原因の第一位だ。

—

2. 実践コード:ON ZERODIVIDE による華麗な防衛術

百聞は一見に如かず。実際にVSAMファイルから読み込んだマスタデータの単価計算を行う、よくある月次売上計算バッチの骨組みを見てほしい。
現場のコーディング標準に則り、すべて大文字、かつ適切なインデントとBUILTIN関数(組込み関数)をフル活用した実用コードだ。

ZERODIVIDE_SAMPLE: PROC OPTIONS(MAIN);

/————————————————————–/
/ 変数宣言領域 /
/————————————————————–/
DCL WK_SALES FIXED DEC(11,2) INIT(0); / 売上金額 /
DCL WK_QTY FIXED DEC(7,0) INIT(0); / 販売数量 (ゼロ想定)/
DCL WK_PRICE FIXED DEC(9,2) INIT(0); / 単価(計算結果) /
DCL WK_ERR_CNT FIXED BIN(31) INIT(0); / エラー発生件数 /

/ VSAMファイル定義(簡易) /
DCL SALES_FILE FILE RECORD SEQUENTIAL INPUT;
DCL 1 SALES_REC,
5 REC_KEY CHAR(5),
5 REC_SALES FIXED DEC(11,2),
5 REC_QTY FIXED DEC(7,0);

DCL EOF_FLAG CHAR(1) INIT(‘OFF’);

ON ENDFILE(SALES_FILE) EOF_FLAG = ‘ON’;

/————————————————————–/
/ 【重要】ON ZERODIVIDEユニットの定義 /
/ ゼロ除算が発生した瞬間に制御を奪い、システムABENDを回避する /
/————————————————————–/
ON ZERODIVIDE
BEGIN;
WK_ERR_CNT = WK_ERR_CNT + 1;
DISPLAY(‘【警告】ゼロ除算を検知しました。KEY=’ || REC_KEY);

/ ゼロ除算時のフォールバック値(単価をゼロとして強制設定) /
WK_PRICE = 0;

/
注意:
ここでGOTOを使って正常ループの処理継続ポイントへジャンプすることも可能だが、
構造化プログラミングの観点からは、フラグを立てて演算をスキップさせるか、
安全な値で代用して処理を続行させるのがモダンなメインフレームの作法だ。
/
END;

/ ファイルオープン /
OPEN FILE(SALES_FILE);

DISPLAY(‘— 月次売上単価計算バッチ 開始 —‘);

/ メイン処理ループ /
READ FILE(SALES_FILE) INTO(SALES_REC);
DO WHILE (EOF_FLAG = ‘OFF’);

/ 数量項目へのマクロ的防衛(明示的なチェック)も有効だが、 /
/ 想定外のデータ破壊によるゼロ除算はONユニットが受け止める /
WK_SALES = REC_SALES;
WK_QTY = REC_QTY;

/ === ここでゼロ除算が発生する可能性のある割り算 === /
/ WK_QTY が 0 の場合、ハードウェア割り込みが発生し、 /
/ 上記の ON ZERODIVIDE ユニットが即座に起動する /
WK_PRICE = WK_SALES / WK_QTY;

/ 計算結果の出力や後続処理(省略) /
/ WRITE … /

READ FILE(SALES_FILE) INTO(SALES_REC);
END;

CLOSE FILE(SALES_FILE);

DISPLAY(‘— 処理終了 —‘);
DISPLAY(‘ゼロ除算検知回数: ‘ || TRIM(WK_ERR_CNT));

END ZERODIVIDE_SAMPLE;

—

3. アーキテクトが教える現場のノウハウと「罠」

このコードを見て、「なんだ、簡単じゃないか。じゃあバッチ中いつでも `ON ZERODIVIDE` さえ書いておけば安心だな」と思ったなら、まだまだ修行が足りない。実務の現場でこの構文を扱う際には、以下の「先輩たちの血と汗が染み込んだ教訓」を必ず頭に叩き込んでおいてほしい。

① ONユニットのスコープ(有効範囲)を意識しろ

`ON` ステートメントは、宣言されたブロックおよびその内側の静的・動的ブロックに影響を与える。しかし、サブルーチン(PROCやBEGINブロック)を細かく分けている場合、どこでONユニットが有効になっているかを見失うと、「意図したところで捕捉されない」という現象が起きる。基本的にはメイン処理のなるべく上位、あるいは例外を確実にキャッチしたいブロックの直前で定義するのが鉄則だ。

② 無限ループの罠に気をつけろ

ゼロ除算が発生したあと、`ON` ユニット内でエラーとなった演算式をそのまま再実行するような制御(例えば不適切な `GOTO` の使い方)を書いてしまうと、エラー発生 ➔ ONユニット ➔ 同じ演算へ戻る ➔ 再びゼロ除算発生 ➔ 無限ループ という最悪のシナリオを引く。
上記のサンプルコードのように、安全な値(`WK_PRICE = 0`)を代用してそのまま次のステップへ進むか、あるいは当該レコードをエラーファイルにスプールして `LEAVE`(COBOLでいうEXIT/GO TOでループ抜け)させる設計にしなければならない。

③ ログ出力には `DISPLAY` と `TRIM` を使いこなせ

メインフレームのシスログやJESMSGLGにメッセージを残す際、PL/Iの `DISPLAY` ステートメントは非常に強力だ。ただし、固定長文字列のスペースパディングに悩まされることが多いので、`TRIM` などのBUILTIN関数を組み合わせて、ログを視認性高く出力するのがプロの技量というものだ。

—

まとめ

レガシーシステムの寿命を延ばし、安定稼働を支え続けるのは、派手な新しいフレームワークではなく、こうした地道で確実な例外処理の積み重ねだ。

「データがおかしいなら落ちて当然」という昭和の思想は、24時間365止まらない現代の基幹系システムでは通用しない。異常なデータが来ようとも、システム全体を落とさずに検知し、該当レコードを弾いて処理を完遂させる――これこそが、我々メインフレームエンジニアが守るべきプライドであり、スキルだ。

次のバッチ改修の際には、ぜひこの `ON ZERODIVIDE` の防壁を組み込み、運用チームをあっと言わせてやるといい。

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