【実務・中級編】ON OVERFLOW条件と算術例外のハンドリング – PL/Iの基本構文とデータ制御実践ガイド

メインフレームの現場で生き抜くためのPL/I「ON OVERFLOW」暗黙のルール

おい、最近入った若手が「夜間バッチでS0C7(データ例外)が出たんですけど、どこで溢れたのかさっぱり分からないんです」って青い顔して駆け込んできたんだよ。
ソースを見せてもらったら、ご丁寧に `ON ERROR` で一律トラップしているだけで、肝心の算術例外に対する個別ケアがすっぽり抜け落ちてやがる。

PL/Iという言語は、C言語やJavaあたりと違って、「予約語を持たない(厳密には文脈依存のキーワード)」という変態的かつ懐の深い仕様を持っている。変数名に `IF` や `GOTO` を使おうと思えば使えてしまう(おすすめはしないがね)ような自由度がある反面、コンパイラが「どこまでをプログラマの意図として汲み取るか」の境界線が独特なんだ。

特に、金融や流通の基幹システムを支える大規模バッチで一番怖いのは、計算結果が変数の桁あふれを起こしたときに発生する `OVERFLOW` 条件 だ。今回は、この `OVERFLOW` の検知と、ダンプ解析で涙を流さないための実践的なハンドリング技術を、実際のコードを交えて徹底的に叩き込んでやる。

—

1. 算術例外と `OVERFLOW` 条件の正体

PL/Iにおける算術例外(Exception)は、単なるプログラム異常終了ではない。ハードウェア(IBM System zの演算機構)レベルでの浮動小数点や固定小数点のオーバーフロー、アンダーフロー、ゼロ割(`ZERODIVIDE`)などを、言語仕様としてプログラマがハンドリングできるように統合されているのが特徴だ。

ここで勘違いしやすいのが、「オーバーフロー=桁あふれ」の定義だ。
COBOL感覚で `PIC S9(5)` の領域に `99999` を超える数値をブチ込んだときのエラーとは少し毛色が違う。PL/Iの `OVERFLOW`(短縮形 `OFL`)は、主に固定小数点や浮動小数点の演算結果が、その変数の宣言された精度(ビッツ数や桁数)の限界を超えたときに発生する。

厄介なのは、コンパイラオプションやコーディングの仕方次第で、この例外が発生したときに即座に異常終了するのか、それとも最高位の桁を切り捨てて(あるいは最大値を保持して)シレッと処理を続行してしまうのかが分かれる点だ。夜間バッチが「しれっとゴミデータを後続のVSAMファイルに書き込んでいた」なんて事故を防ぐためには、明示的な `ON` ユニットの配置が不可欠になる。

—

2. 制御フローを支配する `ON OVERFLOW` ユニット

PL/Iの例外処理は、`ON 条件` 文によって「例外が発生したときにどう動くか」をあらかじめ宣言しておく動的(ダイナミック)なスコープ制御をとる。

ここでメインフレーム開発者が必ず知っておくべき制御フローの鉄則がある。

1. スコープの継承: `ON OVERFLOW` を宣言したブロック(あるいはそれより内側のブロック)の実行中に例外が起きると、制御は自動的にその `ON` ユニット(文)へジャンプする。
2. 復帰の挙動: `ON` ユニットの処理が正常に終了した場合、制御は原則として例外を発生させた演算文の次の文へ進む。つまり、「エラーだからリトライする」のではなく、「エラーを検知してログを出し、別の安全値(デフォルト値)を代入して処理をバイパスする」といったリカバリに向いている。
3. `SNAFU` を防ぐ `STMT` と `GO TO`: ダンプ解析を見据えるなら、`ON` ユニット内でどのステートメントで起きたかを特定できるよう、組み込み関数(BUILTIN)を使いこなす必要がある。

—

3. 実践:VSAM入出力と算術例外を網羅したPL/Iプログラム

百聞は一見に如かずだ。実際の現場でそのまま使える、大文字ベースの構造化されたPL/Iソースコードを見てほしい。
これは、売上マスタ(VSAM KSDS)からレコードを読み込み、複雑な単価計算で `OVERFLOW` が起きた際に、安全にトラップしてログ(SYSOUT)に落とし、処理を継続するバッチ処理の骨組みだ。

1
/ /
/ プログラム名: CALC01PL /
/ 概要: VSAM入力とオーバーフロー・ハンドリングの実践サンプル /
/ /
CALC01PL: PROC OPTIONS(MAIN);

/ — 変数宣言 — /
DCL W_EOF CHAR(1) INIT(‘0’);
DCL W_RC FIXED BIN(31) INIT(0);

/ 入力用VSAMレコード構造体 /
DCL 1 IN_REC,
5 IN_EMP_ID CHAR(5),
5 IN_HOURS FIXED DEC(5,2), 年間稼働時間
5 IN_RATE FIXED DEC(7,2); 単価

/ 計算用変数(あえて少しタイトな精度にしてOFLを誘発しやすくする) /
DCL W_BASE_AMT FIXED DEC(7,2);
DCL W_TOTAL_AMT FIXED DEC(9,2);

/ — ファイル定義 — /
DCL EMPFILE FILE RECORD INPUT
ENVIRONMENT(VSAM);

/ — 組み込み関数の宣言 — /
DCL ONCODE BUILTIN;
DCL ONSOURCE BUILTIN;

/ ============================================================= /
/ ON ユニットの定義(算術例外のトラップ) /
/ ============================================================= /
ON OVERFLOW
BEGIN;
PUT SKIP LIST(‘ 警告: 算術オーバーフローを検知しました ‘);
PUT SKIP LIST(‘ 発生時のソースデータ (ONSOURCE): ‘, ONSOURCE);
PUT SKIP LIST(‘ システム復帰コード (ONCODE) : ‘, ONCODE);

/ 桁あふれ時は安全な最大値あるいはゼロを補正してバッチ断を回避 /
W_TOTAL_AMT = 9999999.99;

/ 注意: ここでGOTOを使って正常ループに復帰させることも可能だが /
/ 無闇なGOTOはスパゲッティの元なので、代入による救済を推奨する /
END;

/ — ファイルオープン — /
OPEN FILE(EMPFILE) INPUT;

/ — メイン処理ループ — /
DO WHILE (W_EOF = ‘0’);

READ FILE(EMPFILE) INTO(IN_REC);

IF W_RC = 8 / 簡易的なEOF判定のイメージ /
THEN W_EOF = ‘1’;
ELSE DO;
/ 演算処理(ここでOVERFLOW条件が発生しうる) /
W_BASE_AMT = IN_HOURS IN_RATE;
W_TOTAL_AMT = W_BASE_AMT 1.105; / 消費税・諸経費込み計算 /

/ 結果の出力(実際はここで出力用VSAMや順編成へWRITEする) /
PUT SKIP EDIT (IN_EMP_ID, ‘ 支給額: ‘, W_TOTAL_AMT)
(A(5), A(9), F(12,2));
END;

END;

/ — クローズ処理 — /
CLOSE FILE(EMPFILE);

PUT SKIP LIST(‘ 正常終了: CALC01PL ‘);
RETURN;

END CALC01PL;

—

4. デバッグとダンプ解析における注意点

さて、もし運悪く `ON OVERFLOW` をすり抜けて、あるいは `ON` ユニットが組まれていない状態で異常終了(S0C7やU4038など)に至ったとき、私たちが向き合うのは膨大なCEEDUMPやSYSUDUMPだ。

ここで、メインフレーム・エンジニアとしての腕の見せ所がある。

1. `ONSOURCE` と `ONCHAR` を活用せよ

ダンプやログ上で、「どの文字データが原因で計算が狂ったか」を特定するためには、PL/Iの診断機能である `ONSOURCE`(例外を引き起こした元の文字データ)や `ONCHAR` をデバッグ時にいかに引っぱるかが勝負の分かれ目になる。特に外部から入ってくる電文や古いフラットファイルに混入したスペースや不正なゾーン十進(Zoned Decimal)が、内部のパック十進や固定小数点演算に巻き込まれて爆発するケースが多い。

2. コンパイラオプションの確認

マイグレーション直後のソースでよくあるのが、コンパイラオプションの差異による挙動の変化だ。
昔のOS/VS PL/I時代と、現在のEnterprise PL/I for z/OSでは、デフォルトの算術例外の扱い(`TRAP(ON)` か `TRAP(OFF)` か)が異なる場合がある。
「テスト環境ではエラーになって止まるのに、本番環境では値が丸められてシレッと通ってしまう」という、最もゾッとする現象を防ぐためにも、コンパイル時 JCL の PARM には明示的に `TRAP(ON),AGGREGATE` などの必要なオプションを記述しておくのが、夜眠れるエンジニアの共通認識だ。

—

5. まとめ

PL/Iの `OVERFLOW` 条件ハンドリングは、単なる「エラー処理のコードブロック」ではない。巨大なデータプールを扱うメインフレームシステムにおいて、想定外の数値の暴走からシステム全体の整合性を守るための最後の防壁だ。

「予約語がない」という自由度の高い言語仕様の裏で、コンパイラとランタイムが裏で何を支えているのか。そのメカニズムを理解し、今回紹介したような `ON` ユニットと組み込み関数を適切に配置できるようになれば、どんな古いレガシーシステムの改修であっても、ビクともしない堅牢なコードを書けるようになるはずだ。

さあ、コーヒーでも飲んで、次のモダナイゼーション案件のソースレビューに取り掛かろうか。

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