COBOLをJavaへ移行するための様々なアプローチとメリット・デメリット
前回は、COBOL言語の特性について解説をしました。
今回は、このような特性を持ったCOBOL言語により実装されたアプリケーションを、Javaに移行するために、どのような方法があるか?について解説していきます。
まずは、変数宣言の移行から説明を始めます。
変数宣言
01 NUM1 PIC 9(6).
というCOBOLにおける宣言は、正の整数の変数であり、長さが6桁(6バイト)をあらわしています。内部コードをSJISとし、123456という数字がセットされていたとすると、6バイトの領域には、[ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 ] というバイト値が設定されます。
Javaではこれと類似の型はint型(32ビット)に当たります。int型は、正負の値を表現することができ、表現可能な数値の範囲は、-2,147,483,648~2,147,483,647となります。ただしint型は2進数であり、前回説明しました通り、演算方法も10進と2進とで異なります。またCOBOLでこのNUM1に1234という値をセットし、その値をファイルに出力した場合は、値は右詰となり、頭の2桁にはゼロが出力されるというJavaには無い仕様もあります。
それでは、変数宣言の部分をJava言語へ移行する方法について検討します。
移行方法 1:Javaの組み込み型にマップする。
int NUM1;
この方法は変数の仕様の違いを受け入れ、違いを補正するための補助コードを元プログラムに追加することについても受け入れる方針です。
移行方法 2:Javaに存在しているBigDecimalのようなクラスを新たに定義する。
CInt NUM1 = new CInt(6);
この方法は元言語の仕様を再現する変数クラスを新たに定義し、その変数クラスへ移行する方針です。
それぞれ、長所・短所はありますが、この後説明します。なお、MOVEステートメントの仕様を効率よく再現するためには、<方法2>を採用した方がいいですが、その理由も後半で説明します。
Javaでは通常の変数名は小文字が一般的ですが、ここでは変換元言語との比較が容易なように大文字のままとしています。変数名の変換のメリット・デメリットについては、この後のコラム「変数名のネーミング」で説明いたします。
では、次にこの2つの方法に沿った形で、集団項目の移行方法について検討します。
まずは、移行元となるCOBOLの集団項目の宣言です。
01 GRP1.
03 NUM1 PIC 9(6).
03 STR1 PIC X(6).
移行方法 1の場合:
public class CGRP1 { // 説明に無関係なコードは省略、以下同様
public int NUM1;
public String STR1;
};
CGRP1 GRP1 = new CGRP1();
Javaの場合、GRP1の定義については型定義とインスタンス宣言とに分ける必要があります。
クラスに対してint getNUM1(), setNUM1(int i)というメソッド定義し、変数をprivateにする方法もありますが、今回は話を簡略化するために、変数をpublicにすることで対応しています。
また、クラス名については、変数名と異なるものにするために、クラス名に何等かの接頭語もしくは接尾語が必要になります。例えば、’C’を付ける等。(クラスのC)
移行方法 2の場合:
public class CGRP1 {
public CInt NUM1 = new CInt(6);
public CXStr STR1 = new CXStr(6);
};
CGRP1 GRP1 = new CGRP1();
GRP1の定義については、型定義(public class CGRP1 {…})とインスタンス宣言(CGRP1 GRP1 = new CGRP1();)とに分割されます。(方法1と同じ)
フィールド宣言には、基本型ではなくクラス(CInt,CXStr)を使用しています。
その他の移行方法として、過去に以下のような変換を行っていたプロジェクトもあるようですが、とてもメンテナンスできるとは思えません。
海外事例の場合:
Var GRP1 = declare.level(1).var(); // 01 GRP1.
Var NUM1 = declare.level(3).pic9(6).var(); // 03 NUM1 PIC 9(6).
Var STR1 = declare.level(3).picX(6).var(); // 03 STR1 PIC X(6).
このサンプルコードに記載のVarは、最近のJavaに追加されたvarとは別ものでユーザ定義のクラスになります。
変数名のネーミング【コラム】
変数名のネーミングについては悩ましいところで、既にJavaで開発を行っておられるお客様の場合、会社で決めたコーディングルールがあったりします。JavaやCでは、大文字は定数的な変数とルール化されているケースも多いので、COBOLの全部大文字の変数をそのままの形で移行するのには抵抗を感じられるかも知れません。ただし、設計書を再作成しないのであれば、大文字の方が親和性は高いとも言えます。と言いますのも、自動変換の場合、ロジックが変更されるという事がありませんので、元の設計書でも十分機能する事ができます。(ただし元の設計書がメンテナンスされていれば、、、ですが)
この辺りは、本当に好みの問題の様な気もいたします。
自動変換を行う観点からだと、変換の際に変数名を変換するロジックを通すだけなので大きな問題にはなりません。プログラムで記述可能な変数名の変換ルールさえ決めることができれば。
やはり、今後、どういう人が保守していくのか?が重要ですし、設計書との整合性をどこまで求めるのか?という事に帰結すると思います。
データの転記(転送)- MOVE文
データを転記(転送)するMOVE ステートメントは、様々なケースで利用されます。転送元と転送先の型の組み合わせによって、挙動が変わります。このような挙動の変化は、ある意味オブジェクト指向的かもしれません(笑)。ただ、挙動は実行時に決まる訳ではなく、コンパイル時に決まります。以下に挙動の違いを列挙します。変数の型は以下の通りです。
(STR1,STR2は文字列、NUM11は数値型(COMP3)、GRP1, GRP2は集団項目)
MOVE STR1 TO STR2. *型が同じなので、バイト列のコピー。
MOVE NUM11 TO STR1. *型が違うので、型変換後に、バイト列をコピー。
01 GRP2.
03 NUM2 PIC 9(6).
03 STR2 PIC X(6).
-------- 省略 --------
MOVE GRP1 TO GRP2. *集団項目同士のコピーなので、バイト列のコピー。
前述の変数宣言において、集団項目をクラスにマップするという説明をしました。その前提で話を進めますと、「MOVE GRP1 TO GRP2」は、クラス間でのデータの転送になります。しかしJavaでは、クラスのメンバー(例えば、方法1のケースにおけるNUM1,STR1)を、1回の命令でコピーするという機能はありません。よって、COBOLにおいて集団項目の転送が1行で事足りているのですが、それをJava化しようとする場合、以下の方法が考えられます。
- 1)1行のMOVE文を、各メンバー変数の代入コードに展開する。(n文に変換)
- 2)コピーメソッドを用意し、用意したコピーメソッドの呼び出しに変換する。(1文に変換)
- 3)Javaのリフレクション機能を利用してコピーを行うメソッドの呼出に変換する。(1文に変換)
以下に、1)のケースの例を示します。この例では項目数が少ないのでシンプルですが、集団項目配下の項目数が多かったり、階層が深かったりすれば、このような展開は、数十行になることもあります。
変換元COBOLの例:
MOVE GRP1 TO GRP2.
Javaへの変換例:
GRP2.NUM1 = GRP1.NUM1;
GRP2.STR1 = GRP1.STR1;
2)のケースの場合は、コピー先のクラスが同じ型とは限らないために、コピーが行われるクラス(コピー先)の洗い出しを実施し、その後対象数分のメソッドを用意するという手間な作業となってしまいます。
3)のケースの場合で、よく利用されるのが、CommonsやSpringのBeanUtils.copyPropertiesメソッドになりますが、これは変数名が同じものの場合にしか値のコピーが行われないことになります。
更にもうひとつ全く別の方法としては、集団項目毎にデータを管理するバッファ領域を保持し、各変数は、その場所をポインティングするという手法があります。この手法の場合、集団項目が管理しているバッファの中身のみを転送することで、MOVE文と同様の動作を実現することができます。加えて、変換時のコードも1文で済むと同時に、処理自体も高速に動作することが可能です。
そして、同じような課題を抱えているのが、INITIALIZE文です。INITIALIZE文は、変数を初期化するために使用するステートメントです。初期化対象は、通常の変数および集団項目です。集団項目の場合、配下の変数の型によって初期化の方法が変わるので、MOVE文に類似した対応が必要になります。つまり、n文の初期値設定文に展開するか、変数の型を動的に判断して初期化を行うか?の対応が、MOVE文と同じく必要になるということです。
再定義 - REDEFINES
更に厄介なのが、REDEFINESと言う機能です。この機能は、同一のメモリ空間を異なる変数でアクセスできるというものです。COBOLプロラムにおいて、よく見る例としては、日付の定義があります。
01 DATE PIC X(8).
01 DATE-GRP REDEFINE DATE.
03 YYYY PIC 9(4).
03 MM PIC 9(2).
03 DD PIC 9(2).
COBOLでは、DATE と[YYYY,MM,DD]は、メモリ領域を共有しています。このようなメモリ空間を共有する機能は、Javaにはありません。よって、メモリ空間共有の仕組みを、自ら実装する必要があります。それを実現するためには、int型のような組み込み型を使用することは不可能です。以下の例は、CDateGrpのコンストラクタの引数にdate変数を渡すことで、メモリ空間を共有しようとしている例です。
CXStr date = new CXStr(8);
<グループの定義を省略>
CDateGrp dateGrp = new CDateGrp(date);
ここまで見てきたように、COBOLからJavaへの移行(変換)と言っても様々なアプローチ方法があることを理解していただけたと思います。では、述べてきたアプローチのどれかを選択する際に、どのようなポリシーを適用するか?ですが、その点において、我々が考える重要なポリシーは「可能な限り100%の自動変換を行う」になります。自動変換率が高ければ、不具合が入り込む可能性を限りなくゼロに近づけることができます。しかし手修正による移行作業を行ってしまうと、実装誤りが発生する可能性がありますし、テストにより多角的に検証することで内在する不具合をあぶり出す必要があり時間を要します。マイグレーションプロジェクトに携わっていますと、この辺りの考え方(何を優先するか?)は、お客様によって千差万別であると感じます。そして、その判断は、お客様が当該プロジェクトにどれだけのコストと時間をかけることができるか?という事により自然と決まっていくとも言えます。手法によるコストと期間の違いは以下の通りです。
フルスクラッチ > 50%開発+50%自動変換 > 100%自動変換
COPY文の対応
マイグレーションツールの中には、COPY文の移行を諦めているケースも多いです。つまり、COPY文による挿入ファイルのマージ後の状態で、Javaに変換するというものです。これは、「ホストから脱却できればいいのですよね!」というアプローチに私には思えます。その後の保守性の事をあまり考えていないか、割り切っていると考えられます。
これに対し、ここではCOPY文の対象ファイルの内容を、外部クラス化することを考えてみましょう。COPY文の対象ファイルを外部クラス化することで、保守性を維持できるメリットがあります。
以下のCOPYファイルがあるとします。
03 GRP1.
05 MEM1 PIC X(8).
05 MEM2 PIC X(8).
05 MEM3 PIC X(8).
このケースは、COPYファイルの中に最上位のグループが一つしかありません。
この場合は、1つのCOPYファイルを、1つの外部クラスに対応させる事ができます。
そして、もしCOPYファイルに含まれる最上位グループが複数の場合は、1つのCOPYファイルを、複数のクラスに分割するか、複数の上位グループの上に、更にグループを挿入することで対応が可能です。このグループを挿入するという方法は、COPYファイルの中身が全て基本項目で構成されている場合にも適用可能な方法です。
このようにCOPY文一つを例にしても、いくつかの課題があります。変換ツールの中には、このようなCOPY文の保守性に対して考慮されていないものも多いです。
癖のあるCOPY文の使い方
GRP1-COPY
01 GRP1.
03 NUM1 PIC 9(6).
03 STR1 PIC X(6).
というCOPYファイルがあったとします。
これを利用する際に、以下のようなコードを書くことも可能です。
COPY GRP1-COPY.
03 STR2 PIC X(6).
これは、GRP1の構造を、ある意味変更したことになってしまいます。
このような悪しき利用方法についても適切に外部クラス化する変換方法を我々は考案し、特許を取得しています。(※【特許番号】6199278)
ご興味ある方は特許内容を参照して頂ければと思いますが、オブジェクト指向の継承関係をうまく利用することにより上記の問題を解決しています。
(ちょっと休憩)
ここで、どうしてCOBOLのプログラムがメンテナンスし辛いか?という話にも少し触れてみたいと思います。一番の理由は、COBOLのプログラムにおける変数定義が、そのプログラム内でグローバルな変数となっているからです。加えて大量の変数定義が行われていることが多いため、初めてプログラムを見た人にとっては、理解するまでに時間を要することになると言えます。
COBOLにおいても、1つのプログラムの中で、サブルーチンを定義して保守性を向上させる戦略が採られます。この時に利用されるのがSECTIONです。しかし、このSECTIONには、引数の概念が無いのです。よって、SECTIONを構成しているロジックの内容を、IN,OUTで理解しようとしても、最上位の変数定義に戻って確認しないと解らないことがある訳です。サブルーチンを呼び出す時には、別のプログラムをCALL命令で呼び出すようにすれば、CALL命令には引数があるので少しは明確になりますが、性能的にはコストが高くなってしまいます。
演算
演算の違いについても触れていきます。
まずは、COBOLの例になります。
01 A PIC 9(1)V9(3) VALUE 0.31.
01 B PIC 9(1)V9(3) VALUE 0.3.
01 C PIC 9(1)V9(3).
COMPUTE C = A / B .
これを、Javaで書き直そうとした場合、基本型で小数点以下の数値を表現するために、double型を利用することが考えられます。このサンプルプログラムの出力結果は以下の通りです。
double a = 0.31;
double b = 0.3;
double c = a / b;
System.out.println(c);
[出力結果]
1.0333333333333334
しかし、元のCOBOLプログラムでは、結果を格納する変数は、小数点以下の桁数が3桁なので、値は、1.033となり、doubleによる計算結果と合致しません。
では、COBOLと同じ10進数による演算を行いたい場合は、BigDecimalクラスを使用します。
BigDecimal a = new BigDecimal("0.31");
BigDecimal b = new BigDecimal("0.3");
BigDecimal c = a.divide(b, 3, RoundingMode.DOWN);
この際に、BigDecimal c = a.divide(b); のみで演算を行った場合は、以下の例外が発生してしまい計算ができません。
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
前述しましたラッパークラス(ここではCDec)を用いることで、各変数に精度情報を付加することが可能となり、COBOLの演算と同様の動きを再現することが可能になります。
CDec a = new CDec(4, V3).VALUE("0.31");
CDec b = new CDec(4, V3).VALUE("0.3");
CDec c = new CDec(4, V3);
compute(c, a.divide(b));
この計算結果は1.033となり、COBOLでの計算結果に合致します。このようにCOBOLの計算仕様を、CDecと呼ばれるクラスで再現することにより正確性を担保し、マイグレーションプロジェクトを成功へと導きます。
今回は、Javaの移行の方法について、様々なアプローチがあり、メリット・デメリットについて解説しました。次回は、我々がマイグレーションにおいて、正確性、保守性にプラスして特に留意している性能対策について解説いたします。
「Xenlon~神龍 モダナイゼーションサービス」製品ラインナップ
「Xenlon~神龍 モダナイゼーションサービス」シリーズはTIS独自のリライト手法によるマイグレーションを中心としたモダナイゼーションサービスで企業のDX推進をご支援します。
アセスメントサービス | リライトによるモダナイゼーションを検討中の企業様に対して「Xenlon~神龍 モダナイゼーションサービス」を活用したプロジェクト推進の『実現性』と『実効性』を検討します。 さらに、ご希望のお客様に対しては情報システム化戦略やエンハンス革新戦略・DX戦略の評価・診断をご支援します。 |
---|---|
マイグレーションサービス | TIS独自のリライト技術「Xenlon~神龍 Migrator」を活用して、レガシーな言語(COBOL、PL/Ⅰなど)からJavaへのリライトを実現し、オープン環境へ移行します。業務ロジックの100%を自動変換するとともに、メインフレームと同等以上の処理性能を実現します。 |
エンハンス革新・DX推進サービス | マイグレーション後の早期エンハンス革新やDXの実現に向け、各種戦略やマイグレーションプロジェクトからの情報をインプットにしてエンハンス革新計画・DX計画を立案。 PoCを実現しながら実現性を検証するとともに、必要に応じて、マイグレーションプロジェクトにフィードバックすることで早期にエンハンス革新・DX実現に向け推進します。 |
エンハンス革新・DX実践サービス | システムの正常稼働を保つためのメンテナンスをはじめ、オープンシステムの手法を有効活用した安全なリファクタリングやエンハンスメント革新の実践によるシステム効率を支援します。またマイグレーション後のシステムをベースとして、データ利活用や先端技術活用などDX実践を支援します。 |