【実務・中級編】ON条件におけるSUBSCRIPTRANGEのデバッグ活用 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近夜間バッチの終了時間がじわじわ延びて、運用監視チームから「またかよ」って溜息をつかれてないか?
原因を調べてみたら、決まって昔のプログラマが適当に書いた配列の添字オーバーランによるメモリ破壊、いわゆる「ストレージ・オバーレイ(Storage Overlay)」だったりする。しかも厄介なことに、これが即座にABEND(異常終了)せず、関係ない制御ブロックを書き換えて数時間後にワケのわからないコードで落ちるもんだから、デバッグの神様にお祈りする羽目になる。

PL/Iという言語は、COBOLなんかと違って非常に強力で自由度が高い反面、プログラマが境界チェックのガードをサボると、容赦なくメモリの深淵に突き落とされる。

今回は、そんな地獄のトラブルを防ぐための最強の切り札、`SUBSCRIPTRANGE` 条件と、その実務でのデバッグ活用術について、俺の現場経験を総動員して伝授しよう。

1. PL/Iの「予約語を持たない」懐の深さと、その裏返し

まず、PL/Iの基本思想に少し触れておこう。
PL/Iの最大の特徴の一つは、「言語仕様上の予約語(Reserved Words)が存在しない」ということだ。つまり、`IF` や `GO TO`、さらには今回解説する `SUBSCRIPTRANGE` のようなキーワードでさえも、コンテキストによっては「ただの変数名」として定義できてしまう。

1
/ こんな変態的なコードも(コンパイルエラーにはならないが)書けてしまう /
DECLARE SUBSCRIPTRANGE FIXED BIN(31);

コンパイラは、文脈(Context)からそれがキーワードなのか変数名なのかを判断する。この仕様は言語設計としては美しいが、ひとたびタイポやコーディング規約の不徹底があると、コードの可読性を劇的に悪化させる原因になる。

だからこそ、基幹システムの現場では、コーディング標準(コーディング規約)でこれらのキーワード周辺の命名規則を厳格に縛っているわけだ。

2. SUBSCRIPTRANGE条件とは何か?

`SUBSCRIPTRANGE`(省略形: `SUBRG`)は、配列(Array)を参照または定義する際、その添字(Subscript)が定義された次元の境界値(Bounds)の中に収まっているかを実行時に監視するための条件(Condition)だ。

デフォルトの状態では、このチェックはオフになっている。なぜなら、すべての配列アクセスに対して境界チェックを行うと、当然ながらCPU命令が増え、パフォーマンス(特に大規模なループ処理や多次元配列の演算)に悪影響を及ぼすからだ。

しかし、デバッグ時や品質検証(SIT)フェーズにおいては、この隠れた時限爆弾を早期に発見するために不可欠な機能となる。

有効化の構文

PL/Iでこの監視を有効にするには、コンパイルオプションで指定する方法と、ソースコード内で `ON` ユニットやプレフィックス(Prefix)として記述する方法がある。

1
/ プレフィックスによる個別有効化の例 /
(SUBSCRIPTRANGE):
DCL WORK_TABLE(100) FIXED BIN(31);

3. ONユニットによる制御フローと実践的デバッグコード

もし `SUBSCRIPTRANGE` が有効な状態で、定義された範囲を超えた添字(例えば `WORK_TABLE(101)` や `WORK_TABLE(0)`)にアクセスしようとすると、PL/Iのランタイム環境は `SUBSCRIPTRANGE` 条件を発生(Raise)させる。

ここで `ON SUBSCRIPTRANGE` ユニットを仕込んでおけば、プログラムが即座にクラッシュしてダンプを取るだけでなく、「どの変数の、何番目の添字で、どのソース行でエラーが起きたか」をログに出力して安全に制御を捉えることができる。

実際のバッチプログラムを想定したサンプルコードを見てみよう。

1
——————————————————-;

  • 顧客別月別売上集計プログラム

——————————————————-;
SM0100: PROC OPTIONS(MAIN);

DCL RET_CODE FIXED BIN(31) INIT(0);
DCL I FIXED BIN(31);
DCL J FIXED BIN(31);

/ 12ヶ月分の売上テーブル(1~12の範囲で定義) /
DCL SALES_AMT(12) FIXED DECIMAL(11,2) INIT((12)0);
DCL ERROR_LOG CHARACTER(100);

/ SUBSCRIPTRANGE条件の有効化とONユニットの定義 /
(SUBSCRIPTRANGE): BEGIN;

ON SUBSCRIPTRANGE
BEGIN;
/ 境界外アクセスを検知した際の割り込み処理 /
ERROR_LOG = ‘ ERROR: 添字範囲外アクセスを検知しました。’;
DISPLAY(ERROR_LOG);

/ ビルトイン関数を利用して詳細情報を取得する /
/ 注: 実際の処理系依存の部分もありますが、デバッグ時はダンプ採取や異常終了へ誘導します /
DISPLAY(‘発生箇所付近のインデックスを確認してください。’);

/ 意図的な異常終了(U4038など)へ持ち込む /
SIGNAL ERROR;
END;

/ わざと境界を超えるループ(1から13まで回してしまうバグ) /
DO I = 1 TO 13;
/ ここでI=13の時にSUBSCRIPTRANGE条件が発生する /
SALES_AMT(I) = SALES_AMT(I) + 1000.00;
END;

END; / プレフィックスの有効範囲ここまで /

RETURN;

END SM0100;

このコードでは、`DO I = 1 TO 13;` という古典的な「オフ・バイ・ワン(Off-by-one)エラー」を仕込んでいる。`SALES_AMT` は12要素なのに13回目にアクセスしようとした瞬間、`ON SUBSCRIPTRANGE` がフックし、野良のストレージ破壊を引き起こす前にプログラムを綺麗に(あるいは制御された形で)止めてくれる。

4. 本番環境でのパフォーマンス影響と使い分けの鉄則

さて、ここで後輩によく聞かれる質問がある。
「先輩、こんなに便利な機能なら、本番環境でも常に有効(`ON`)にしておけば安全じゃないですか?」

答えは「NO」だ。ここにメインフレーム・アーキテクトとしての腕の見せ所がある。

パフォーマンスへのペナルティ

`SUBSCRIPTRANGE` を有効にすると、コンパイラは配列要素にアクセスするすべての機械語命令の直前に、「現在の添字が下限以上か?上限以下か?」を判定する比較分岐命令(Compare and Branch)を挿入する。
これが、夜間バッチで何千万回、何億回と回る巨大な多次元配列のループ内で行われたらどうなるか? CPU使用率(特にスーパバイザ状態のオーバーヘッド)が跳ね上がり、バッチウィンドウ(夜間処理の制限時間)を確実にオーバーすることになる。

現場の鉄則(プラクティス)

1. 開発・単体テスト(UT)・統合テスト(IT)環境:
コンパイルオプション(`TEST` や `SUBRG`)を必ず有効にしてビルドする。これにより、テスト段階で潜在的なバグを根絶する。
2. 本番環境(Production):
基本的には無効(`NOSUBRG`)でコンパイルし、最大限の実行パフォーマンスを確保する。
3. 例外的な本番切り分け:
本番環境で「原因不明のデータ破損」や「S0C4等のストレージ保護例外(Protection Exception)」が間欠的に発生し、どうしても原因が特定できない場合に限り、該当モジュールだけ一時的に `SUBRG` を有効にしたバージョンを適用してトレースする。

おわりに

PL/Iは古い言語だと言われることもあるが、メモリ管理の自由度と堅牢性を両立させるための仕組みが、言語仕様の奥深くにしっかりと用意されている。

今回解説した `SUBSCRIPTRANGE` は、まさにその代表格だ。
「動けばいいや」でコードを書くのではなく、こうした言語のメカニズムを正しく理解し、テスト時は厳格にガードをかけ、本番ではパフォーマンスを最適化する。このバランス感覚こそが、現代のメインフレーム現場を支えるプロフェッショナルの技術だと言える。

さあ、今日のデバッグ作業に戻るとしようか。エラーログに泣かされる前に、コンパイラにしっかり守りを固めさせておきなさい。

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