遺産の再構築:PL/IからJavaへ、精度と符号の深淵を渡る設計指針
メインフレームの心臓部で数十年もの間、何十億ものトランザクションをさばき続けてきたPL/Iプログラム。その堅牢性は、コンパイラが生成する機械語の「最適化された冷徹さ」と、ハードウェアレベルで保証されたパックデシマル演算の上に成り立っています。
しかし、今、我々はそれをJavaの抽象化された世界へ移植しようとしている。ここで多くのプロジェクトが、「データ型マッピング」という名の底なし沼に足を取られます。本稿では、レガシー移行の現場で最も頻発するトラブル、その深層心理と対策を紐解きます。
—
1. FIXED DECIMALからBigDecimalへの「誤差」の正体
PL/Iの `DCL AMOUNT FIXED DECIMAL(15, 2)` は、内部的にはパックデシマル(Packed Decimal)形式で格納されます。これは10進数演算をハードウェア(IBM Z)が直接サポートしているため、浮動小数点の丸め誤差とは無縁です。
Javaへ移行する際、安易に `double` を使えばその時点でプロジェクトは失敗です。`java.math.BigDecimal` を選択するのは当然ですが、ここで陥るのが「スケールと丸めモードの不一致」です。
1
/ PL/I側: 15桁、小数点以下2桁のパックデシマル /
DCL AMT FIXED DEC(15, 2) INIT(0);
/ Java側へのマッピング例 /
BigDecimal javaAmt = new BigDecimal(“1234567890123.45”);
javaAmt = javaAmt.setScale(2, RoundingMode.HALF_UP);
現場の教訓:
PL/Iの算術演算では、コンパイラオプション(`FIXEDOVERFLOW`など)によって、桁あふれ時に即座にABEND(S0C7等)を発生させて防御可能です。しかしJavaでは例外を明示的にハンドリングしなければ、計算結果がサイレントに汚染されます。移行設計では、PL/Iの算術属性をすべて棚卸しし、演算の途中で精度が落ちないよう、`MathContext` を固定した設計を強制すべきです。
—
2. EBCDICからUnicodeへの「見えない罠」
文字コードの移行は、単なる `Charset` の変換ではありません。PL/Iプログラムがポインタ(`PTR`)を使ってメモリのオフセットを直接操作している場合、特に注意が必要です。
例えば、`DCL BUF CHAR(100) BASED(P);` のように定義されたバッファを、別の構造体にキャストして読み取るようなトリッキーなコード。EBCDIC上の `X’0E'(Shift-Out)` と `X’0F'(Shift-In)` を含むマルチバイト文字が、Unicode変換時に「化ける」だけでなく、バイト長の変化によりデータ構造そのものが破壊されます。
- 対策: Unicode移行時には、全ての `CHAR` 変数の長さを再定義せよ。特にCICSオンライン処理において、画面転送用のDFHCOMMAREAに格納されたデータは、バイト単位のレイアウトが狂うと、受信側のCOBOLや別のPL/IモジュールがS0C4(メモリ保護違反)で即死します。
—
3. パックデシマルの「符号反転」という悪夢
PL/I特有の罠に、内部表現の符号(Sign)があります。パックデシマルの最終バイトの下位4ビットが符号を表しますが、メインフレーム側で異常なデータパッチが当たった場合、`X’C’`(正)や `X’D’`(負)以外が混入することがあります。
1
/ 意図せぬデータ化けを防ぐためのチェックルーチン例 /
IF (SUBSTR(UNSPEC(AMT), 15, 1) < 'A'B) THEN
SIGNAL CONDITION(INVALID_DATA); / データ妥当性検証 /
Javaへ移行した際、`BigDecimal` のコンストラクタは、この「不正な符号」を許容せず、容赦なく `NumberFormatException` を投げます。レガシー側の「汚れたデータ」をそのまま引き継ぐのか、移行時にクレンジングするのか。この設計判断を怠ると、移行後の本番稼働で原因不明のジョブ異常終了に夜中まで悩まされることになります。
---
4. ABENDダンプから読み解く「真実」
移行設計のフェーズで、最も信頼できるのは仕様書ではなく、「過去のシステムダンプ」です。
PL/Iのダンプリストを眺めれば、変数の配置、ポインタが指すアドレス、そしてコンパイラが生成した命令コードが見えてきます。もし移行先のJavaで不可解な数値のズレが発生したら、ダンプを採取してください。パックデシマルの内部形式(Packed Decimal)と、Javaのバイナリ表現の差異を、16進数で突き合わせる。これこそが、アーキテクトが最後に頼るべき「唯一の真実」です。
最後に:移行は「翻訳」ではなく「再構築」である
PL/IからJavaへの移行は、単なる構文変換ではありません。それは、「ハードウェアの制約に守られていた安全な世界」から「抽象化のレイヤーを自力で制御しなければならない世界」への移住です。
- `OPTIONS(MAIN)` のエントリポイントがどのように初期化されているか。
- `STATIC` 変数がCICSのタスク間でどのように共有(あるいは隔離)されているか。
- 埋め込みSQLのカーソル制御が、接続プーリング環境でどう振る舞うか。
これらを一つずつ紐解き、Javaの設計に落とし込むこと。それが、基幹システムを次の30年へと繋ぐ、我々アーキテクトの矜持です。
次回の記事では、CICSの `EXEC CICS LINK` をJavaのマイクロサービス間でいかに再現するか、そのトランザクション整合性について掘り下げていきましょう。
