はじめに:基幹システムにおける「ゼロとの遭遇」とPL/Iの美学
メインフレームの現場で長くシステムを支えているアーキテクトなら、深夜のバッチ処理で突如発生する `S806` や `SOC7`、そして最も厄介な算術例外によるアベンドの苦い記憶が幾つかあるはずだ。特に、何百万件と流れる金融系の金利計算や歩掛り算出の最中、外部から渡ってきたマスタデータの不備によって引き起こされる「ゼロ除算(ZERODIVIDE)」。
現代のモダン言語、例えばJavaやC#であれば、`java.lang.ArithmeticException` や `DivideByZeroException` がスローされ、キャッチし損ねれば容赦なくスレッドがダウンする。では、IBMメインフレームの重厚長大なPL/I環境ではどうだろうか。PL/Iには、言語仕様の根幹として「ON条件(条件処理)」という、異常系を優雅かつ強烈に制御するメカニズムが備わっている。
今回は、単なるマニュアルの解説を超え、コンパイラ最適化の裏側、DB2やCICSが絡むエッジケース、そして将来的なJava等へのマイグレーション(レガシー移行)を見据えたアーキテクチャの観点から、`ON ZERODIVIDE` の極意を徹底的に紐解いていこう。
—
1. PL/Iの「ON条件」が持つ真のポテンシャル
PL/Iの例外処理は、C++の try-catch や Java の exception handling とは一味違う。OSやハードウェア(IBM Zのプロセッサ)の割り込みをランタイム環境がシームレスに捕捉し、ユーザー定義のハンドラへと制御を渡す。
特に `ZERODIVIDE` は、単にプログラムを異常終了(ABEND)させるのではなく、「計算結果をどう取り繕うか」というビジネス継続の判断をプログラマに委ねるための強力な武器だ。
基本的な実装パターン:優雅なリカバリと処理続行
まずは、ゼロ除算が発生した際に、デフォルト値(例えばゼロまたは安全な定数)に置換してバッチを止めることなく完走させるための実践的なコードを見てほしい。
—————————————————————-
- ゼロ除算(ZERODIVIDE)捕捉およびリカバリ処理のサンプル
—————————————————————-
ZERODIVIDE_SAMPLE: PROC OPTIONS(MAIN);
DCL TOTAL_AMT FIXED DECIMAL(11,2) INIT(0);
DCL ITEM_COUNT FIXED DECIMAL(5,0) INIT(0);
DCL UNIT_PRICE FIXED DECIMAL(9,2) INIT(0);
DCL SAFE_PRICE FIXED DECIMAL(9,2) INIT(0);
— ゼロ除算発生時のONユニット(例外ハンドラ)の定義 —
ON ZERODIVIDE BEGIN;
DISPLAY(‘【警告】ゼロ除算を検知しました。デフォルト値を適用します。’);
- ゼロ除算を引き起こした除算の代わりに安全な値を代入する、
- あるいは計算結果にフォールバック値を設定する。
UNIT_PRICE = 0.00;
- GOTOにより、例外発生箇所の直後へ制御を戻す(LOCAL GOTO)
GOTO RESUME_POINT;
END;
— テストデータのモック —
TOTAL_AMT = 15000.50;
ITEM_COUNT = 0; <-- これが原因でゼロ除算が発生する
DISPLAY('割算を実行します: TOTAL_AMT / ITEM_COUNT');
--- 算術演算の実行 ---
- 故意にゼロ除算を引き起こすポイント
UNIT_PRICE = TOTAL_AMT / ITEM_COUNT;
RESene_POINT:
- ラベル:ONユニットからのGOTO着地点
DISPLAY(‘計算完了後の単価: ‘ || CHAR(UNIT_PRICE));
— 通常終了 —
RETURN;
END ZERODIVIDE_SAMPLE;
このコードの肝は、`ON ZERODIVIDE` ブロック内からの `GOTO` による制御の復帰だ。PL/Iでは、ONユニット内で適切な処置を行い、`GOTO` でアクティブなブロック内のラベルへジャンプすることで、プログラムをアベンドさせずに続行させることができる。
—
2. アーキテクチャの深層:コンパイラ最適化とハードウェアの罠
しかし、ここからがシニア・アーキテクトの腕の見せ所だ。IBM Enterprise PL/Iコンパイラは非常に強力な最適化エンジン(`OPTIMIZE` オプション)を持っている。これを有効にした途端、上記の `ON` 条件の挙動が開発者の意図と異なる動きをすることがある。
最適化(OPTIMIZE)とレジスタ・キャッシュ
コンパイラが最適化を行うと、変数の値がメインメモリではなくCPUレジスタ上にキャッシュされる。ゼロ除算が発生した瞬間、ハードウェア(IBM Z)は浮動小数点や十進演算の例外割り込み(Program Check)を発生させるが、最適化の度合い(`OPT(2)` や `OPT(3)`)によっては、どの命令で例外が起きたかの特定が曖昧になることがある。
特に、パックデシマル(`FIXED DECIMAL`)演算において、コンパイラが複数の演算をインライン展開・パイプライン化した際、`ON ZERODIVIDE` 内で値を書き換えても、レジスタ内の古いキャッシュが再書き込みされてしまい、意図した `UNIT_PRICE` が保持されないケースに直面したアーキテクトも多いはずだ。
対策:
信頼性が極めて求められる金融勘定系等のモジュールで `ON ZERODIVIDE` を用いる場合は、対象の変数に `volatile` 属性(PL/Iではそれに相当する厳密なストレージ制御、あるいはコンパイラオプションの調整)を意識し、不必要な最適化によるレジスタ・アロケーションの弊害を排除する必要がある。また、計算の直前で明細なバリデーション(`IF ITEM_COUNT = 0 THEN …`)を入れる事前チェックとのハイブリッド設計が、実務では最も堅牢とされる。
—
3. エッジケース:DB2埋め込みSQLとCICSオンラインにおける挙動
基幹システムのPL/Iは、単体で動くことは少ない。大抵は DB2(埋め込みSQL) や CICS(オンライン・トランザクション処理) と密結合している。ここで `ON ZERODIVIDE` を安易に使うと、予期せぬ障害を引き起こす。
① CICS環境でのスレッド異常とタスクアベンド
CICS配下で動作するPL/Iプログラムで `ON ZERODIVIDE` が発動し、もしハンドラ内で適切な後始末(例えば、CICSのリソース解放やアボート処理)を行わずに暴走した場合、CICSタスク全体が `ASRA` などのシステムアベンドを引き起こし、最悪の場合はCICS領域全体のパフォーマンス劣化やトランザクションの不整合を招く。
オンライン処理では、`ON` 条件に頼るのではなく、データベースから取得した数値や画面からの入力値は、必ず演算前に `IF` 文でゼロチェックを行うべきである。
② DB2ホスト変数とパックデシマルの内部符号反転バグ
SQLの `SELECT SUM(…)` の結果がヌル(NULL)あるいはゼロとなり、それをPL/Iの `FIXED DECIMAL` に受けてそのまま割算に利用した場合、DB2のインジケータ変数(Indicator Variable)のハンドリングを誤っていると、パックデシマルの内部表現において予期せぬパディングや符号(Sign)の不正が発生することがある。
この状態でゼロ除算やそれに近い微小値との除算を行うと、ハードウェアが `DECIMAL DIVIDE` 割り込みを検知し、`ON ZERODIVIDE` ではなく `ON CONDITIONS` 全般のパニック、あるいは `SOC7`(Data Exception)へ直行するケースがある。
—
4. マイグレーション(レガシーモダナイゼーション)への視点
現在、多くの企業がメインフレーム上のPL/I資産を、Java(Spring Boot)やC#(.NET Core)へとマイグレーション(あるいはリライト)するプロジェクトを推進している。ここで大きな障壁になるのが、まさにこの 「PL/Iの暗黙的な例外処理(ON条件)」 の移植性である。
Java/C#への移行設計のポイント
JavaにはPL/Iの `ON ZERODIVIDE` のような、スコープベースの動的例外トラップ(プログラムのどこで割っても一律で同じONユニットが拾う仕組み)は存在しない。Javaでこれを模倣しようとすると、すべての算術演算をラップするユーティリティクラス(あるいはAOP:アスペクト指向プログラミング)を設計し、そこで `ArithmeticException` をキャッチしてデフォルト値にフォールバックさせる仕組みが必要になる。
// Java移行時のラッパーメソッドの概念例
public static BigDecimal safeDivide(BigDecimal numerator, BigDecimal denominator, BigDecimal defaultVal) {
if (denominator == null || denominator.compareTo(BigDecimal.ZERO) == 0) {
// PL/Iの ON ZERODIVIDE の振る舞いを模倣
log.warn(“ZERODIVIDE detected. Falling back to default value.”);
return defaultVal;
}
return numerator.divide(denominator, 2, RoundingMode.HALF_UP);
}
レガシー移行の現場において、「元のPL/Iプログラムがこのエラーをどう握りつぶしていたか(あるいはどうリカバリしていたか)」の仕様書が残っていないことが多々ある。そのため、コンパイル済みのロードモジュールやプリプロセッサの挙動、さらには今回解説した `ON ZERODIVIDE` のような言語仕様の深い理解が、移行先のバグを防ぐ唯一の防衛線となるのだ。
—
おわりに
PL/Iは、1960年代に生まれた古き良き言語でありながら、ハードウェアの挙動と密接に結びついた極めて高度な制御構文を持っている。`ON ZERODIVIDE` は、単なるエラー処理の構文ではなく、「システムを絶対に止めるな」という当時のアーキテクトたちの執念が生んだ遺産とも言える。
その挙動の裏にあるコンパイラの最適化、OSの割り込み、そして周辺ミドルウェア(DB2/CICS)との関係性を完全に理解した上でコードを書くこと。それこそが、真のメインフレーム・システムアーキテクトの矜持である。
