2017年6月7日 Theolizer®最新版v1.1.0へ対応するために修正しました。
最新版のソース一式をGistに置いてます。

こんにちは。田原です。

プログラムの開発中に構造体やクラス、enum型の定義変更は日常茶飯事ですね。そして、古いプログラムで保存したファイルを新しいプログラムで読む必要があります。Theolizer®ではこれらに2種類の方法を組み合わせて対処します。前回、定義変更方法について解説しました。今回はバージョン・アップ/ダウン処理について解説します。

1.どんな特に使うのか

前回の方法でクラスや構造体のメンバ変数定義を変更できます。そして、それでは足りずバージョン・アップ処理を書く必要がでてきたら、今回の方法で対応します。また、旧バージョンのデータを保存したいプログラムもあると思います。その時、バージョン・ダウン処理を追加します。

この方法は他に2つの場面でも使えます。

  1. クラスや構造体のメンバ変数名を変更する時
  2. enum型のシンボル名を重複して変更する時
    一度使ったシンボル名を再度使いたい場合は、バージョン・アップすることで使えるようになります。

1-1.バージョン番号について

バージョン・アップ/ダウン処理を書く時、どのバージョンに対するアップ/ダウン処理なのか指定する必要がありますので、バージョン番号を定義します。1から初めて1組のバージョン・アップ/ダウン処理を書く度に1つ上げます。

そして、例えば、最新版がバージョン5でバージョン3のファイルから回復したいような時、Theolizer®は自動的にバージョン3→バージョン4→バージョン5とカスケードにバージョン・アップ処理を行います。
バージョン・ダウンの時も同様です。最新版のバージョンが5のプログラムがバージョン3のファイルを保存する時、Theolizer®は自動的にバージョン5→バージョン4→バージョン3とカスケードにバージョン・ダウン処理を行い、目標バージョンのファイルを保存します。

従来のシリアライザはこのような時には、過去の全てのバージョンから最新版への変換処理を記述する必要がありましたので、バージョンが上がってくるとメンテナンスが大変でした。
Theolizer®はバージョンを1つ上げる時にバージョン・アップ処理を1つ追加するだけでOKです。古いバージョン・アップ処理に手を入れる必要がありません。

1-2.グローバル・バージョン番号テーブルについて

1つのアプリケーションは複数のクラスを持ちますし、全てのクラスが一斉にバージョン・アップすることはありません。従って、各クラスのバージョンはまちまちとなります。
ですので、旧バージョンで保存する際、各クラスのバージョンを1つ1つ指定するコードを書くのは現実的ではありません。

そこで、グローバルなバージョン番号を定義し、その番号と各クラスのバージョンの対応表を自動生成するようにしました。これをグローバル・バージョン番号テーブルと呼んでます。

また、このグローバル・バージョン番号テーブルを回復処理でも用いることで、シリアライズ・データ内に各クラスや構造体のバーション番号を1つ1つ記録しないで済むようにしています。(今までのサンプルが出力した各jsonファイルの先頭付近の“GlobalVersionNo”:1がこのグローバル・バージョン番号です。)

2.バージョン番号管理を行う時の手順

今回はいつもの家計簿サンプル・ソースを変更しますが、その前にバージョン管理の手順を説明します。

2-1.手順1(バージョン管理開始時に一度だけ行う。)

3回目の技術ブログで下記を仕込みました。バージョン番号による管理を始める時、まず最初にこれら2つを定義します。

  1. #define THEOLIZER_GLOBAL_VERSION_TABLE
    グローバル・バージョン番号テーブルの実体はアプリケーション全体で1つです。その実体を生成するコンパイル単位(cppファイル)にてTHEOLIZER_GLOBAL_VERSION_TABLEを定義します。

  2. THEOLIZER_DEFINE_GLOBAL_VERSION_TABLE(GlobalVersionTable, 1);
    全てのバージョン指定を行うクラス・構造体・enum型の定義より前でTHEOLIZER_DEFINE_GLOBAL_VERSION_TABLE()マクロを設置し、現在のグローバル・バージョン番号を指定します。
    グローバル・バージョン番号は必ず1から始めて下さい。そして、この時の各クラス・構造体・enum型のバーションがグローバル・バージョン番号1に対応するバージョンとなります。なお、バージョン番号を指定していないもののバージョンは自動的に1となります。

次に、プロジェクトをビルドし、Theolizer®でソース自動生成を行います。
そして、生成されたソースをgitやsvn等お使いのバージョン管理システムへ登録します。
自動生成ソースにはその時までの各バージョンのクラス・構造体・enum型の定義情報、および、グローバル・バージョン番号テーブルが含まれています。そのため、バージョン管理システムの管理対象としておかないと、情報に矛盾が発生する可能性があるのです。

2-2.手順2(バージョン・アップ/ダウン処理を追加する度に行う。)

グローバル・バージョン番号と、バージョン番号を変更するクラス・構造体・enum型、および、それらを含むクラス・構造体(*1)のバージョンを1つ増やします。

(*1)クラス・構造体・enum型を直接含むクラスや構造体とは
基底クラスやメンバ変数としてクラス・構造体・enum型を持つ場合が該当します。
それらへのポインタの場合は「直接」ではないので該当しません。

そして、バージョン・アップ/ダウン関数を該当のクラス内に記述し、ビルドします。

バージョン・アップ/ダウン関数を記述するには、下記のような雛形を対象のクラス・構造体定義の直後にコピーし、雛形の「対象クラス」を対象のクラス・構造体名を変更、雛形の「バージョン番号」はバージョンアップ直前のバージョン番号を記載します。例えば、バージョン3を作った時はバージョン番号を2とします。

template<class tTheolizerVersion, class tNextVersion>
struct 対象クラス::TheolizerUserDefine<tTheolizerVersion, tNextVersion, バージョン番号>
{
    // Members version down.
    static void downVersion(tNextVersion const& iNextVersion, tTheolizerVersion& oNowVersion)
    {
        // バージョン・ダウン処理を記述する。
    }
    // Members version up.
    static void upVersion(tTheolizerVersion const& iNowVersion, tNextVersion& oNextVersion)
    {
        // バージョン・アップ処理を記述する。
    }
};

3.家計簿サンプルのバージョン・アップ

今回は一部の科目について在庫数量を管理できるようにします。

  1. Item構造体の変更
    バージョン1ではbool mDoManage;で残高管理するかどうかだけ設定していました。それをManageType mManageType;とし、管理しない(emtNone)、残高管理する(emtMoney)、在庫管理する(emtCount)へ変更します。型も異なり値の割り当て方も変わるため、バージョン・アップ処理が必要になります。また、ついでに数え方(単位mUnit)も追加しました。

  2. Trade構造体の変更
    バージョン1では取引高としてmAmount(円)だけでした。それを借方(mDebitAmount)、貸方(mCreditAmount)の2つに拡張しました。
    在庫管理する時は増減する数を記載します。例えばパンを6枚158円で購入した時は、mDebitAmount=6、mCreditAmount=158となります。Itemと同じくバージョン・アップ処理が必要になります。

ついでにバージョン・ダウンもできるようにしてみました。

4.今回のソース・コード

前節で記載した変更点は全てここで記述します。バージョン・アップ/ダウン関数は、下記構造体にdownVersion()関数、upVersion()関数として定義します。

4-1.バージョン2構造体定義

common.h
バージョン1形式での保存とバージョン1形式からの回復をデモできるようにするため、コマンド・ライン引き数で1を指定するとバージョン1を保存し、Lを指定すると保存を行わずにSettings.jsonとData.jsonから回復すようにしています。

4-2.データの保存と回復、および、簡単な集計と表示

main.cpp
新しく追加した機能に対応しています。また、在庫が減ることもデモに含めるため、科目定義に「消費」を追加してます。

4-3.家計簿アプリケーションのメインに当たる部分

sub.cpp

4-4.自動生成されたソース

main.cpp.theolizer.hpp

4-5.CMakeLists.txt

CMakeLists.txt

5.実行結果

5-1.新プログラムにてバージョン2で保存/回復

パラメータ無しで起動した場合の結果です。バージョン2のデータが保存され回復されました。

5-1-1.保存された設定ファイル
Settings2.json
5-1-2.保存されたデータファイル
Data2.json
5-1-3.実行結果
result2.log

5-2.旧プログラムのファイルを回復する(新プログラムにてバージョン1形式を回復)

第3回の技術解説で作成した旧プログラムにて保存したSettings.jsonとData.Jsonを新プログラムで回復します。
新プログラムをコマンドライン・パラメータにLを指定して起動した結果です。

5-2-1.回復した設定ファイル
Settings.json
5-2-2.回復したデータファイル
Data.json
5-2-3.実行結果
result2L.log

5-3.旧プログラムで回復できるファイルを保存する(新プログラムにてバージョン1を保存し、旧プログラムで回復)

新プログラムをコマンドライン・パラメータに1を指定して起動することでバージョン1のデータが保存されます。
そして、旧プログラムを起動し、このデータを読ませた結果です。

5-3-1.保存された設定ファイル
Settings1.json
5-3-2.保存されたデータファイル
Data1.json
5-3-3.実行結果
result21.log

6.まとめ

Theolizer®の開発に当たって、クラス・構造体・enum型の仕様変更を容易にできることに注力しました。
バージョン・アップ/ダウン処理をカスケード処理すること、および、グローバル・バージョン番号テーブルの実装により、変更工数を大きく改善できると思います。