【入門編】ON条件におけるZERODIVIDEの捕捉とリカバリ処理 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のプログラミング言語の経験がある方にとって、レガシーの代表格である「PL/I(ピーエルワン)」は、最初ちょっと近寄りがたい雰囲気がするかもしれませんね。「なんだか難解そう」「古い仕様でガチガチなんじゃ…」なんて不安を感じていませんか?

でも、安心してください。基本のキを押さえてしまえば、PL/Iは非常に表現力豊かで、プログラマの意図に素直に従ってくれる頼もしい相棒です。

今回は、そんなPL/Iの懐の深さを実感できるテーマ「ON条件によるZERODIVIDE(ゼロ除算)の捕捉とリカバリ処理」について、一緒に紐解いていきましょう。

1. 他言語とはひと味違う? PL/Iの「エラーとの向き合い方」

Javaで開発をしていると、ゼロで割り算をしようものなら `ArithmeticException` がバシッと飛んできて、しっかり `try-catch` で囲まないとプログラムが強制終了してしまいますよね。COBOLでも例外処理の仕組みはありますが、記述がやや冗長になりがちです。

一方、PL/Iには「ONユニット(ON-unit)」という非常にユニークで強力な例外処理の仕組みがあります。

イメージとしては、プログラムのどこかで計算エラー(今回はゼロ除算)が発生した瞬間、「おっと危ない!」と自動的に割り込んで、あらかじめ用意しておいた非常用ボタン(ONユニット)をポチッと押してくれる自動制御システムのようなものです。

しかも、PL/Iの素晴らしい(そして少しレガシーらしい)ところは、エラーをただキャッチして終わるだけでなく、「何事もなかったかのように、デフォルト値にすり替えて処理を続行させることができる」という点です。夜間のバッチ処理などで、「データが一部おかしいからといって、全体の巨大な処理を今すぐアボート(異常終了)させたくない!」という現場の切実な要望に、見事に応えてくれる機能なんです。

2. 予約語の罠がない? 自由な識別子と今回の主役たち

PL/Iのユニークな特徴の一つに、「実は決定的な予約語(Keyword)がほとんどない」という仕様があります。
どういうことかと言うと、`IF` や `READ` といった命令(キーワード)すら、コンテキスト(文脈)で判断されるため、変数名としてうっかり使えてしまうのです。(※もちろん、ソースコードがカオスになるのでそんな命名は絶対にしませんが!)

今回のサンプルコードでは、初心者の方にも馴染みやすいように、計算対象の数値を扱うためのデータ属性として `FIXED DEC`(固定小数点10進数:COBOLのCOMP-3に近いイメージです)を登場させます。メインフレームの基幹系では王道のデータ型ですね。

それでは、実際にゼロ除算を優しくいなす、実用的なPL/Iのコードを見てみましょう。

3. 実装パターン:ゼロ除算をスマートに乗りこなすPL/Iコード

以下のサンプルプログラムは、マスタファイルから読み込んだ数値を計算するバッチ処理の一コマを想定しています。分母が「0」になっている不健全なデータが混ざっていても、システムが止まらないように仕掛けを施してあります。

1
DIVIDE_SAMPLE: PROC OPTIONS(MAIN);

/ 変数の宣言 /
DCL W_TOTAL_AMT FIXED DEC(9,2) INIT(0); / 集計金額 /
DCL W_COUNT FIXED DEC(5,0) INIT(0); / 件数 /
DCL W_AVERAGE FIXED DEC(7,2); > / 平均値(計算結果) /

/ 異常値対策のフラグ /
DCL W_ERR_FLG CHAR(1) INIT(‘0’);

PUT SKIP LIST(‘— 割り算処理を開始します —‘);

/ ========================================================== /
/ [ここがポイント] ZERODIVIDE 発生時のトラップ(ONユニット)の設定 /
/ ========================================================== /
ON ZERODIVIDE
BEGIN;
W_ERR_FLG = ‘1’; / エラーが発生したことを記録 /
W_AVERAGE = 0; / デフォルト値(0)で救済する /
PUT SKIP LIST(‘【警告】ゼロ除算を検知しました。平均値を 0 に補正して継続します。’);
END;

/ ———————————————————- /
/ テストデータを用いた割り算のシミュレーション /
/ ———————————————————- /

/ パターン1: 正常系(1000を5で割る) /
W_TOTAL_AMT = 1000.00;
W_COUNT = 5;

W_AVERAGE = W_TOTAL_AMT / W_COUNT;

PUT SKIP EDIT (‘パターン1結果: ‘, W_AVERAGE) (A, F(7,2));

/ パターン2: 異常系(1000を0で割る:ここでONユニットが発動!) /
W_TOTAL_AMT = 1000.00;
W_COUNT = 0; / ← 分母がゼロ! /

W_AVERAGE = W_TOTAL_AMT / W_COUNT; / 実行瞬間、ON ZERODIVIDEへジャンプ /

PUT SKIP EDIT (‘パターン2結果: ‘, W_AVERAGE) (A, F(7,2));

PUT SKIP LIST(‘— 処理は正常に終了しました(アボートなし) —‘);

END DIVIDE_SAMPLE;

4. コードの解説:裏側で何が起きているのか?

初めてこのコードを見た方は、「あれ、`ON ZERODIVIDE` のブロックって、どこから呼ばれているの?」と不思議に思うかもしれません。

1. 常駐の警備員のようなONユニット
プログラムの途中で宣言された `ON ZERODIVIDE BEGIN; … END;` は、いわばプログラム内の「警備員」です。この宣言以降、CPUが割り算(`/`)を実行した瞬間にゼロ除算エラー(ハードウェア割り込みやコンパイラ検知)が発生すると、警備員が瞬時に飛んできます。
2. 制御の奪取と復帰
エラーが発生すると、通常の処理フローが一時中断され、`BEGIN` から `END` までの処理が実行されます。今回のコードでは、平均値 `W_AVERAGE` に安全な `0` を代入し、フラグを立てています。
3. スマートな処理継続
ONユニットの処理が終わると、PL/Iは「エラーが発生した演算の、次の行(あるいは文の直後)」へ自動的に制御を戻し、処理を続行させます。そのため、プログラムが異常終了(U0000やS0C7など)することなく、最後まで走り切ることができるのです。

5. レガシー移行・保守の現場からアドバイス

このONユニットを使ったエラー処理、非常に便利でエレガントなのですが、実務の現場ではちょっとした注意が必要です。

  • どこで起きたエラーかを見失わないこと

あまりに多くの計算を1つのONユニットで大雑把にカバーしてしまうと、「どのデータのどの割り算でゼロ除算が起きたのか」がデバッグ時に分からなくなることがあります。大規模なプログラムでは、必要なスコープ(範囲)に限定してONユニットを配置したり、エラーログに詳細なキー情報を出力させたりする工夫が大切です。

  • 「隠れたバグ」の温床にならないように

エラー値を「0」に置き換えて処理を継続できるのはメリットですが、逆に「本来あってはならない致命的なデータ異常」までサイレントにスルーしてしまう危険性もあります。上記のコードのように、エラーフラグを立てておき、後続の処理やバッチの最後に「警告件数:〇件」としてレポート出力するような、丁寧な監査証跡を残す設計を心がけましょう。

いかがでしたでしょうか?
「PL/Iの例外処理」と言われると難しく聞こえますが、要は「エラーが起きたときのセーフティネットを自分で自由にカスタマイズできる仕組み」です。

レガシーシステムのソースコードを読むとき、この `ON` 文を見つけても、もう怖がる必要はありません。「あ、ここでデータ異常からシステムを守る優しい気配りをしてるんだな」と、先人たちの設計意図を優しく読み解いてみてくださいね。

それでは、快適なメインフレーム・ライフを!

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