序章:夜間バッチの静寂を破る「S0C7」とCONVERSIONの悪夢
メインフレームの基幹システムを長年支えてきたPL/I。その美しくも泥臭い世界において、最も恐れられている例外の一つが CONVERSION条件(コンバージョン・エラー) です。
JavaやC#といった現代の言語に慣れ親しんだ若いエンジニアが最初に直面し、そして絶望するのがこのエラーです。「なぜ、ただの代入文でプログラムが異常終了(アベンド)するのか?」と。
コンソールに突如として吐き出される `IBM0221S ONCODE=8091 CONVERSION condition was raised` というメッセージ。そしてこれに続くシステムABEND(多くの場合は `S0C7` やオペレーティングシステム起因の例外)。夜間バッチの真っ最中にこれが起きたときの冷や汗は、レガシーシステムのアーキテクトなら誰もが一度は経験しているはずです。
今回は、PL/IにおけるCONVERSION条件の本質的な発生メカニズムから、パックデシマル(COMP-3)の内部符号反転というマニアックなエッジケース、そしてJava/C#へのマイグレーション(リライト)時に絶対に踏み抜いてはならない設計上の地雷まで、システムアーキテクトの視点から徹底的に紐解いていきます。
—
1. PL/Iの「予約語レス」な構文哲学とCONVERSIONの発生原因
PL/Iの言語仕様における最大の変態的かつ強力な特徴は、「予約語(Reserved Words)を持たない」 という点にあります。`IF` や `THEN` でさえも、文脈によってはただの変数名として定義できてしまいます。
この柔軟性はプログラミングの自由度を高める一方で、コンパイラによる厳密な事前型チェックのハードルを上げました。数値型(FIXED DECIMAL、FLOATなど)の変数に対して、外部ファイルや画面から読み込んだ文字(PICTURE文字列やCHARACTER型)を代入する際、PL/Iコンパイラは実行時(Runtime)に暗黙のデータ変換(Implicit Conversion)を試みます。
ここで、入力データにスペースや数値以外のゴミ(ゾーン文字の化け、低位ニブルの不正など)が混入していると、コンパイラは変換を諦め、CONVERSION条件 を送出します。
1
DCL WK_SALARY FIXED DECIMAL(9,2); / 演算用の数値型変数 /
DCL IN_RECORD CHARACTER(80); / 外部から読み込んだ生データ /
/ 外部ファイルの文字データを数値変数に代入する瞬間、暗黙の変換が発生 /
WK_SALARY = SUBSTR(IN_RECORD, 1, 9);
/
- もし IN_RECORD の先頭9バイトに “1234567 A” のような
- 意図しない文字(この場合はスペース)が含まれていると、
- この瞬間、CONVERSION条件がレイズされる。
/
もし、このCONVERSION条件に対して `ON CONVERSION` による捕捉(Trap)が定義されていない場合、プログラムは即座にデフォルトのアクション、すなわち プログラムの異常終了(ABEND) へと直行します。
—
2. 実践:ON UNITによる防御的プログラミングと不正データの特定手法
基幹システムのバッチプログラムにおいて、不正なデータが1件混入したからといって、システム全体を即座にアベンドさせるのはナンセンスです。アーキテクトとしては、エラーログに正確なレコードキーや生データをダンプし、該当レコードをスキップ(あるいはエラー退避)する「防御的プログラミング」が求められます。
以下に、実務で使える `ON CONVERSION` を用いた安全なデータ捕捉のパターンを示します。
1
/ ================================================================= /
/ プログラム名: CONVDEMO – コンバージョンエラー捕捉のサンプル /
/ ================================================================= /
CONVDEMO: PROC OPTIONS(MAIN);
Dcl IN_EMP_ID CHAR(5);
Dcl IN_WAGE_STR CHAR(7);
Dcl NUM_WAGE FIXED DECIMAL(7,2);
Dcl ERR_FLAG BIT(1) INIT(‘0’B);
/ CONVERSION条件発生時の割り込み処理(ON UNIT)を定義 /
ON CONVERSION BEGIN;
ERR_FLAG = ‘1’B;
/ 発生源の変数名や、問題の文字列をロギングする処理をここに記述 /
DISPLAY(‘【警告】数値変換エラーを検知しました。データ: ‘ || IN_WAGE_STR);
END;
/ テストデータの模擬入力(本来はファイルやDBから読み込む) /
IN_EMP_ID = ‘99999’;
IN_WAGE_STR = ‘1234.5X’; 制約違反の文字 ‘X’ が含まれている
/ エラーフラグを初期化して代入を実行 /
ERR_FLAG = ‘0’B;
NUM_WAGE = IN_WAGE_STR; / ← ここでCONVERSION条件がレイズされる /
IF ERR_FLAG THEN DO;
DISPLAY(‘-> 異常データをスキップし、代替処理を行います。’);
NUM_WAGE = 0; / 規定のデフォルト値を設定 /
END;
ELSE DO;
DISPLAY(‘-> 正常処理: 給与 = ‘ || CHAR(NUM_WAGE));
END;
END CONVDEMO;
このアプローチにより、バッチジョブの耐障害性は飛躍的に向上します。しかし、根本的な解決には「なぜその不正データが入り込んだのか」の特定が不可欠です。
—
3. 深層:パックデシマル(COMP-3)の内部符号反転とS0C7ダンプの読み方
PL/IやCOBOLで最も頻繁に遭遇する数値型が、IBMメインフレームのハードウェアがネイティブでサポートする パックデシマル(Packed Decimal / `COMP-3`) です。
1バイトに2つの十進数字(ニブル)を詰め込み、最後の右側4ビット(低位ニブル)に符号(`C` = 正、`D` = 負、`F` = 符号なし/初期値など)を格納します。
パックデシマルの基本構造(例:`+123.45` の場合)
- 内部表現(16進数): `01 23 45 C1` (最後の `C` が正符号)
ここで、ファイル転送のミスや、C言語等から書き出されたバイナリデータの破損によって、この符号部分や数値部分が破壊されるとどうなるでしょうか。
例えば、最後のニブルが `E` や `A` など、IBM体系で定義されていない不正なゾーン・符号ニブルになると、算術命令(ZAPやAPなど)が実行された瞬間にハードウェア例外が発生し、お馴染みの S0C7(Data Exception) が発生します。
ダンプ解析の現場から
アーキテクトとしてSYSUDUMPやCEEDUMPを調査する際、以下のポイントを即座に確認するスキルが必要です。
1. PSW(Program Status Word)の確認: アベンドを引き起こした機械語命令のアドレスを特定し、リスト(Listing)のどのPL/Iステートメントに対応するか突き止めます。
2. レジスタ(Registers)の調査: ベース रजिस्टर が指すエリアのストレージダンプを取得し、問題の変数アドレス周辺の16進数パターンを目視します。
3. 符号ニブルの検証: 末尾の1バイトが `C`, `D`, `F` 以外の値(例: `00` や `FF` のゴミ)になっていないかを確認します。これこそが、外部インターフェース設計の不備や、マイグレーション時の文字コード変換(EBCDIC ⇄ ASCII)のバグの動かぬ証拠となります。
—
4. 埋め込みSQL(DB2)とCICSオンラインのエッジケース
オンライン処理(CICS)やリレーショナルデータベース(DB2)との連携においては、CONVERSIONの性質がさらに複雑化します。
DB2(SQL)とのデータ型ミスマッチ
DB2の `DECIMAL` 型カラムからPL/Iの `FIXED DECIMAL` へデータをフェッチ(FETCH)する際、DB2プリコンパイラは適切な型変換コードを生成します。しかし、DB/2側のデータが文字型(`VARCHAR`等)で定義されており、中身が数値に変換できないゴミデータであった場合、SQLCA(SQL通信エリア)にエラーコードが返されるか、あるいはPL/Iランタイム側でCONVERSIONがレイズされます。
CICS環境下では、CONVERSION未捕捉によるアベンドは ASRA(またはAPCT)アベンド を引き起こし、タスクが異常終了するだけでなく、最悪の場合はCICSトランザクションの整合性を損ない、リソースマネージャとの間で中途半端なコミット/ロールバックのジレンマを生む原因となります。オンライン画面からユーザーが全角スペースを入力し、それがバックエンドの数値項目にダイレクトにバインドされた瞬間、トランザクション全体がクラッシュする――これはレガシー移行前のシステムでよく見られる悲劇です。
—
5. レガシーマイグレーション(Java/C#化)における設計の罠
現在、多くの企業がPL/Iで書かれた基幹システムをJava(Spring Boot)やC#(.NET Core)へマイグレーションしています。この時、最も多くのプロジェクトがハマるのが 「例外処理の思想のギャップ」 です。
| 項目 | PL/I (メインフレーム) | Java / C# (モダン言語) |
| :— | :— | :— |
| 数値型への不正値代入 | ハードウェア/ランタイムレベルで即座にCONVERSION例外(捕捉可能) | パース時(`Integer.parseInt` や `BigDecimal` 生成時)に `NumberFormatException` が発生 |
| 空白(スペース)の扱い | FIXED DECIMALへの空白代入はCONVERSION、またはゼロサプレス領域として暗黙処理される場合あり | `Integer.parseInt(” “)` は容赦なく例外をスローする |
| オーバーフロー/切り捨て | コンパイラオプション(FIXED(DEC)等)や環境変数で挙動が変わる | 明示的な例外、またはビルダーの設定に依存 |
マイグレーション設計の鉄則
JavaやC#へリライトする際、元のPL/Iプログラムが「不正データをどのようにハンドリングしていたか(黙って0にしていたのか、ログを出してスキップしていたのか)」を完全に見極める必要があります。
単に `Integer.parseInt()` や `new BigDecimal()` に置き換えるだけでは、元のPL/Iが持っていた「泥臭い耐障害性(あるいは潜在的なバグ)」の挙動が変わってしまい、移行後に「本来処理されるべきデータがエラーで弾かれる」「逆に不正データがすり抜ける」といった致命的な不具合を誘発します。
マイグレーションアーキテクトとしては、移行先の言語において 「堅牢なカスタムバリデーション・パーサー層」 を必ず一枚挟み込み、PL/IのCONVERSION条件やON UNITの挙動をエミュレートする設計が不可欠です。
—
結語:メインフレームの魂を継承するアーキテクトへ
PL/IのCONVERSION条件やデータ例外は、単なる「エラーコード」ではありません。それは、ハードウェアとソフトウェアが密結合していた時代に、システムが発していた「データのほころびを教えてくれ」という切実なサインです。
言語がJavaやC#に変わろうとも、データが持つ「型」の厳格さと、現実世界のデータの「汚さ」が衝突するという構造的矛盾は永遠になくなりません。
PL/Iのコンパイラ挙動と内部構造の隅々までを理解した私たちアーキテクトこそが、レガシーの知見を武器に、モダンな世界でも揺るぎない高信頼性システムを構築し続けなければならないのです。夜間のバッチログに現れる小さなエラーサインを見逃さない深い洞察力を、今日も私たちは持ち続けましょう。
