【テクニカル・上級編】ON CONVERSIONによるデータ変換エラーの捕捉 – PL/Iの基本構文とデータ制御実践ガイド

基幹システムの夜間バッチを揺るがす「CONVERSION」の悪夢

夜間バッチが突如として「S0C7」や「IBM0553S」といった見慣れぬシステム異常終了コードを吐き出し、無慈悲にジョブが異常終了する——。メインフレームの運用保守を担うエンジニアであれば、誰もが一度は冷や汗をかいた経験があるはずだ。

PL/I(Programming Language One)は、COBOLとは一線を画す高い表現力と厳密なデータ型制御を持つ言語だが、その柔軟性の裏側で最も開発者を悩ませるのが、文字データから数値データへの暗黙的(あるいは明示的)な変換失敗時に発生する CONVERSION条件 である。

今回は、PL/IにおけるCONVERSION条件のメカニズムを深掘りし、実務の現場で遭遇するエッジケース(パックデシマルの符号反転バグ、DB2やCICSでの罠)、そしてJavaやC#へのマイグレーション(レガシー移行)を見据えた堅牢なエラーハンドリング設計について、システムアーキテクトの視点から徹底的に解説しよう。

—

1. PL/Iの予約語レス構造とCONVERSION条件の基本

PL/Iの最大の特徴の一つに、「予約語を持たない(あるいは文脈依存である)」という言語仕様上の設計がある。例えば、`IF`, `THEN`, `READ` といったキーワードであっても、プログラマが変数名として使用することが理論上可能である(※コンパイラの解釈やコーディング規約の観点から推奨はしないが)。この仕様は、識別子の命名規則において圧倒的な自由度をもたらす一方で、コンパイラやランタイムがデータ型の整合性を厳密に監視する必要性を高めている。

特に、外部ファイル(QSAMなど)から読み込んだ文字(`CHAR`)データを、計算用の数値(`FIXED DEC` や `FLOAT`)に代入する際、全角スペース、数値以外の文字(英字や特殊文字)、あるいは空白(ゾーン数字の不整合)が混入していると、PL/Iランタイムは直ちに `CONVERSION` 条件を発生させる。

ここで、基本的なONユニット(例外処理)の記述例を見てみよう。

CONV_TEST: PROC OPTIONS(MAIN);

DCL IN_DATA CHAR(5);
DCL NUM_VAL FIXED DEC(5,0);
DCL ERR_FLG BIT(1) INIT(‘0’B);

/ CONVERSION条件のトラップを設定 /
ON CONVERSION
BEGIN;
DISPLAY(‘【警告】データ変換エラーを検出しました。’);
ERR_FLG = ‘1’B;
/ エラー値を安全なデフォルト値(ゼロ等)に補正して続行する場合 /
NUM_VAL = 0;
END;

/ テストデータ(正常系) /
IN_DATA = ‘12345’;
NUM_VAL = IN_DATA; / 暗黙の文字→数値変換 /

IF ^ERR_FLG THEN
DISPLAY(‘正常変換値: || NUM_VAL);

/ テストデータ(異常系:数値以外が混入) /
IN_DATA = ’12A45’;
ERR_FLG = ‘0’B;

NUM_VAL = IN_DATA; / ここでCONVERSION条件が発生し、ONユニットが起動する /

IF ERR_FLG THEN
DISPLAY(‘異常データを検知したため、ゼロ補正して処理を継続します。’);

END CONV_TEST;

このコードのように、`ON CONVERSION` を適切に配置していなければ、プログラムはダンプを出力してアベンド(ABEND)する。しかし、夜間バッチの海原で無秩序にONユニットを配置すると、かえってデータの破損を見逃す温床になりかねない。

—

2. 実務の現場を襲うエッジケースとハードウェアの罠

パックデシマルの内部符号反転バグ

メインフレームの根幹を支える `FIXED DECIMAL`(PL/Iにおけるパック10進数)は、ストレージ上で各バイトの下位4ビットにゾーン部、上位4ビットに数値部、そして最下位バイトの右側4ビットに符号(`C`, `D`, `F` など)を保持している。

外部システムや古い磁気テープから流入したデータにおいて、この符号部分が文字化けや文字コード変換ミスによって破壊されると、算術演算を行った瞬間にハードウェア例外(S0C7など)が発生する。特に、CICSオンラインやDB2の埋め込みSQL(宿敵:ホスト変数)との間でやり取りされる際、CHAR型からDECIMAL型へのキャストが暗黙裏に行われ、CONVERSION条件が即座にトリガーされるケースが後を絶たない。

ベース変数とポインタを用いた動的メモリ操作における盲点

ストレージの直接操作や、不特定長のレコードをハンドリングするために `BASED` 変数と `POINTER` を駆使する高度なアーキテクチャでは、データ領域の誤ったキャストが致命的なCONVERSIONを引き起こす。

Dcl 1 REC_MAP Based(P_REC),
2 HEADER Char(4),
2 BODY_NUM Fixed Dec(7,2);

Dcl P_REC Pointer;

上記のような構造体において、`P_REC` が指すアドレスの `BODY_NUM` の領域に、意図せずパディングやバイナリデータが混入している状態で参照・演算を行うと、ランタイムは有効なパック10進数として解釈できず、異常終了に至る。ポインタを扱うコードでは、文字データを数値としてマップする前に、必ず `VERIFY` ビルトイン関数などで妥当性検証を行うのが、ベテランアーキテクトの鉄則である。

—

3. コンパイラオプションとダンプ解析(SYSUDUMP / CEEDUMP)

アベンドが発生した際、頼りになるのはコンパイラによる最適化情報と、吐き出されたダンプリストだ。

IBM Enterprise PL/Iコンパイラを使用する場合、以下のオプションがデバッグの生死を分ける。

  • `TEST(ALL,SYM)` / `NOTEST`:本番稼働時はパフォーマンス考慮で `NOTEST` にしがちだが、例外発生時のステートメント番号を正確に特定するためには、適切なシンボル情報を残す設計が求められる。
  • `STGOWNT` / `INIT`:未初期化変数が原因で意図せぬバイナリが数値領域に入り込むことを防ぐため、明示的な初期化を強制するコーディング規約が不可欠である。

ダンプ解析の手順

1. CEEDUMP / SYSUDUMPの取得:エラー発生時の PSW(Program Status Word)とブレークポイントを確認し、どのアセンブリ命令(大抵は `PACK` や `CVB` などの十進演算・変換命令)で例外が起きたかを特定する。
2. OFFSETの逆引き:リストティング(Listing)のマップを参照し、問題のPL/Iステートメント番号を割り出す。
3. 変数のスナップショット:ダンプ内のストレージ領域をウォークし、該当変数がどのような16進数(HEX)を保持していたかを暴く。例えば、`40404040F1`(スペースと1)が期待される場所に、EBCDICの無効なビットパターンが入っていないかを確認する。

—

4. マイグレーション(Java / C#への移行)における設計思想の断絶

現在、多くの企業がメインフレームからオープン系(Java, C#など)へのマイグレーションを進めているが、ここで最も大きな壁となるのが、PL/Iの「寛容(あるいは厳格)な例外処理モデル」と、モダン言語の「厳密な型安全性(Type Safety)」のギャップである。

  • PL/Iの世界:CONVERSION条件をキャッチして「とりあえず0に置き換えて処理を継続する」という動的なリカバリが言語仕様レベルでサポートされている。
  • Java / C#の世界:`Integer.parseInt()` や `Convert.ToInt32()` でフォーマット不正があれば、容赦なく `NumberFormatException` や `FormatException` がスローされ、トランザクションはロールバックされる。

アーキテクチャ移行の要諦

レガシーなPL/IバッチをJava(Spring Batch等)へリライトする際、「エラーデータが含まれていても、特定の条件でスキップまたはデフォルト値化して最後まで走り抜けたい」という業務要件が隠れていることが多い。

移行設計においては、単に構文を置き換えるだけでなく、以下のようなレイヤードなバリデーション機構を必ず組み込む必要がある。

1. インジェスト層(Reader):ファイル読み込み時に全フィールドを文字列(String)として安全に受け取る。
2. パース層(Processor):PL/Iの `ON CONVERSION` の挙動を模倣する「許容型パース・ユーティリティ」を実装し、不正値検知時の挙動(ログ出力・エラーテーブルへの退避・デフォルト値置換)を業務仕様通りに再現する。
3. ドメイン層:型安全なオブジェクトとしてビジネスロジックを安全に実行する。

—

結びにかえて

PL/Iの `ON CONVERSION` は、単なるエラートラップの構文ではない。それは、ハードウェアのデータ表現形式(ゾーン 10進数、パック 10進数、浮動小数点)とアプリケーションの論理データ型の間にある深い溝を橋渡しするための、極めて洗練された(そして時に諸刃の剣となる)仕組みである。

基幹システムのアーキテクトとして、私たちはこの言語仕様の裏側にあるバイナリの挙動までを熟知し、日々のバッチ運用や次世代へのモダナイゼーションを成功に導かなければならない。コードの1行、データ定義の1バイトに宿る意味を見極めること——それこそが、レガシーの海を泳ぎ切るプロフェッショナルの矜持である。

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