PL/IからJavaへ:基幹システムマイグレーションにおける「データ型マッピング」の深淵と罠
メインフレームの心臓部で何十年も動き続けてきたPL/Iアプリケーションを、オープン系のJavaへリライトする――。昨今のレガシーモダンナイゼーションにおいて、最も美しく、そして最もエンジニアの血を流すポイントが「データ型と精度のマッピング」である。
「たかが数値をJavaの `BigDecimal` にし、画面項目のピクチャ型を `String` に変えるだけだろう」などと高を括っているプロジェクトは、大抵、本番稼働直前の結合テストや、夜間バッチの金額不一致アラートで地獄を見る。PL/Iという言語の柔軟性、そしてIBM Enterprise PL/Iコンパイラが裏側で担保してきた厳密なデータ表現の歴史を舐めてもらっては困る。
今回は、PL/IからJavaへのマイグレーションにおいて、システムアーキテクトが直面する致命的な罠と、それを如何にして突破すべきかについて、コンパイラ挙動や内部符号のレベルから徹底的に紐解いていこう。
—
1. `FIXED DECIMAL` と `BigDecimal`:スケールと丸め誤差の幻想
PL/Iの真骨頂は、何と言っても `FIXED DECIMAL`(いわゆるパック10進数 / COMP-3)による誤差のない十進演算にある。金融計算において浮動小数点数の丸め誤差が許されないため、メインフレームではこれが標準的に使われてきた。
これをJavaの `java.math.BigDecimal` にマッピングする際、多くのアーキテクトが犯す最初のミスが、コンストラクタの選択ミスやスケール(小数点以下の桁数)の暗黙的な切り捨てだ。
PL/I側の定義とJavaへの直訳の罠
例えば、PL/Iで以下のように定義された金額項目があるとする。
1
DCL WK-KINGAKU FIXED DECIMAL(15, 2); / 整数11桁、小数2桁の固定小数点 /
これをJava側で何も考えずに `double` や `float` で受けるのは、金融システムにおいては「万死に値する」設計ミスである。当然、`BigDecimal` を選ぶことになるが、ここで問題になるのが 「スケール不一致による自動丸め」 と 「ゾーン/パックの符号反転バグ」 だ。
// 良くある危険なJava側のマッピング例
BigDecimal kingaku = new BigDecimal(inputString);
// スケールや丸めモード(RoundingMode)を明示していない場合、
// マイグレーション元のCOBOL/PL/Iの挙動と微妙に一致しないリスクが生じる。
PL/Iの演算では、中間結果の精度はコンパイラの規則(最大31桁)に従って自動拡張されるが、代入時にターゲットのピクチャや宣言に合わせて切り捨て(Truncation)や丸め(Round)が行われる。Javaへ移行する際は、`MathContext` および `RoundingMode`(メインフレームの伝統的な四捨五入、あるいは切り捨て)を完全に一致させなければ、月次決算で「1円の合わない夜」を過ごす羽目になる。
—
2. ピクチャ型(PICTURE)の呪縛:編集文字とNULLのハンドリング
PL/Iの `PICTURE` 句は強力だ。データ保持と画面・帳票用の編集(エディット)を兼務させることができる。
1
DCL PRN-AMT PICTURE ‘ZZZ,ZZ9.99CR’;
この `PRN-AMT` は、数値でありながら文字(Character)として扱われ、符号位置にはスペースやマイナス、あるいは「CR」が付与される。これをJavaの `String` や `BigDecimal` にマッピングする際の最大の罠は、「空白(パディング)の解釈」 と 「DB2等からのフェッチ時のNULL(あるいはハイフン)混入」 である。
メインフレームの世界では、数値項目のブランクパディングは「ゼロ」とみなされる文脈が多いが、Javaの `Integer.parseInt()` や `BigDecimal` のコンストラクタに空白文字を渡せば、即座に `NumberFormatException` が飛んでくる。
エッジケース:パックデシマルの内部符号反転バグ
さらに恐ろしいのは、外部から渡されたデータや、古い磁気テープ(タスク)からのレガシーファイル入力において、パックデシマルの最下位ニブル(4ビット)にある符号ビットが破損しているケースだ。
- 正しい正数: `C` または `F`
- 正しい負数: `D`
- 壊れた符号: `A`, `B`, `E` など
PL/Iの厳密な型チェックやコンパイラオプション(例えば `TRAP(ON)` や `CHECK`)下では、こうした不正データは即座にS0C7などのデータ例外(ABEND)を引き起こし、シスプレックス全体を止めることなく安全にジョブを異常終了させてくれた。
しかし、これを適当にJavaへ移行してしまうと、「例外としても検知されず、符号が反転したまま、あるいはゴミデータが混入したままDBにスルーインし、数ヶ月後に大規模なデータ汚染として発覚する」 という、最悪の隠れバグに変貌する。
—
3. 埋め込みSQL(DB2)とCICSオンラインにおけるエッジケース
基幹システムのPL/Iプログラムの多くは、DB2の宿すプレコンパイラ(SQL/PL-I)や、CICS(Customer Information Control System)の通信領域(DFHCOMMAREA)と密結合している。
インジケータ変数の欠落
PL/Iの構造体(Structure)をそのままJavaのDTO(Data Transfer Object)に変換する際、DB2のNULL値を表現する「インジケータ変数(IND-VAR)」の存在を忘れてはならない。
1
/ PL/I側でのDB2ホスト変数構造体 /
01 EMP-RECORD,
05 EMP-ID FIXED BIN(31),
05 EMP-NAME CHAR(30);
01 EMP-IND.
05 EMP-ID-IND FIXED BIN(15);
05 EMP-NAME-IND FIXED BIN(15);
Java(MyBatisやJPA、あるいはJDBC直叩き)へ移行する際、このインジケータ変数をJava側の `Optional
特に金融系の残高やフラグ項目において、`NULL`(未設定)と `0`(ゼロ円)の混同は、監査上の致命的な指摘事項(コンプライアンス違反)に直結する。
—
4. マイグレーション設計:システムアーキテクトが取るべき防衛策
では、我々アーキテクトはこのデータ型マッピングの罠からシステムをどう守るべきか。具体的な防衛策を提示しよう。
1. 自動変換ツールの過信を捨てよ
市販のレガシーマイグレーションツール(PL/I to Javaコンバーター)は、構文の表面的な置き換えしか行わない。型の意味論(Semantics)までは理解していないため、生成されたJavaコードに対しては、型マッピングの厳格なレビューゲートを必ず設けること。
2. 共通ライブラリによるデータ型のエミュレーション
PL/Iの `FIXED DECIMAL` の挙動(特に桁あふれ時の振る舞いや丸めモード)を完全に再現するカスタム `Decimal` クラスをJavaの共通基盤層として作成し、プロジェクト全体でそれを強制する。プリミティブな `BigDecimal` をそのまま野放しにしてはならない。
3. 二重稼働(デュアルラン)とバイナリ比較テストの徹底
移行期には、メインフレーム側で出力されたバイナリファイル(あるいはDBスナップショット)と、Java側で処理した結果のバイナリを突き合わせる「ビット単位の回帰テスト(Bit-to-Bit Comparison)」を自動化する。ピクチャ型のスペースパディングや符号の1ビットのズレも見逃さない仕組みを作ることが、プロジェクト成功の唯一の道である。
—
結びにかえて
PL/IからJavaへの移行は、単なる言語の書き換えではない。それは、メインフレームが何十年もかけて培ってきた「データの信頼性と厳密性」という要塞を、現代のオープン系アーキテクチャの上に再構築する壮大な建築工事である。
「動けばいい」という妥協は、基幹システムの文脈においては最大の悪である。コンパイラの挙動を知り尽くし、ビットのレベルまでデータを愛でる気概を持ったエンジニアだけが、この難敵を制圧することができる。さあ、今日も堅牢なコードを書こう。
