ユニットテストで振り返るプログラマとしての自分史

ここ 1, 2 年、主に キューブ・ソフト で配布している Windows ソフトウェアを中心に、ユニットテスト周りの整備を精力的に行ってきました。今回は、テストと言う視点で、自らのプログラミングの軌跡を振り返るような記事を執筆してみようかと思います。

テスト以前

私のプログラマとしての経歴は 2001 年 4 月に大学の情報科学科に入学した事によって始まります。入学するまではプログラミングの経験もなく、本当にゼロからのスタートでした。入学からしばらくの間は、講義で出される課題を解いていく日々と言う感じで、触れたプログラミング言語こそ、Pascal、C、JavaCASL II、PerlStandard MLVHDL と多岐に渡りますが、実際には書き捨てのプログラムが大多数を占めていました。

この記事を書くにあたり、何か当時の成果が残っていないかバックアップ HDD を漁ったところ、ROVND と言うプロジェクトを発見しました。私は RAGNAROK Online と言う MMORPG の最初期に「ゲーム内(Loki サーバ)で取引されるアイテムの相場を調査して公開する」と言う事を行っていましたが、時が経ち、ゲーム内で流通するアイテムの種類が増えていくにつれて、手動による更新に限界を感じ始めていました。そこで、プログラムによる半自動化を試みたのが最初のプロジェクトとなります。内容的には、価格データ (price.txt) 等の内容を基に、静的な HTML を生成するものです*1。日付を見ると、2002 年~ 2003 年の終わり頃まで続けていたようです。

ROVND はテキスト処理を C のみで行うと言う、今考えると茨の道を選択していますが、困ってから根性で何とかすると言うスタンスで、最初の数年は過ぎていきました。この頃は、テストどころか保守と言う概念すらありません。バージョン管理と言う概念を知るのもまだまだ先の事です。

Edit and Pray からの脱却

プログラマとして最初の転換期となったのが 2004 年です。この年、初めて講義とは関係なく、自らの意思で C++ の学習を始めました。始めた理由は、何となくカッコイイからと言う程度のものだったのですが、学習し始めると同時に何故か「汎用的な C++ ライブラリ」を作り始めます。これが後に CLX C++ Libraries と言う名前となり、公開している中で最初の成果物となりました。記録を見ると初公開は 2006 年 2 月 22 日だそうなので、プログラマになって 6 年目と言う事になります。

CLX C++ Libraries は、私にとって思い出深いものとなりました*2。2008 年に OSDN(旧 SourceForge.JP)より 今月のプロジェクト として取り上げて頂いた時には、初めて自分のプログラムが注目されて嬉しかった事を覚えています。また、このプロジェクトのおかげか @cpp_akira さんに Boost.勉強会等に誘って頂き、日本における C++ のコミュニティについても知る事ができました。

プログラミングに関する基本的なスタンスや バージョン管理 への理解なども、このプロジェクトを通じて徐々に固まっていきました。しかし、自分以外の誰かが自分のプログラムを利用する と言う状況になり、ついに保守に関する問題が露呈し始めます。CLX C++ Libraries は、自分の思い付きでライブラリを追加・修正していましたが、少しずつ「公開されているサンプルファイルすら動かない」と言う報告を頂くようになりました。

ソフトウェア開発の失敗談

修正した部分に関しては手動による確認を行っていたのですが、「まさかそんな所に影響があるとは……」と思うようなファイルに対して、エラーが見つかる事が多々ありました。自分が完全に把握していると思っているプロジェクトですらこの有様なのだと、プログラミングに対する恐ろしさを教えてくれたプロジェクトでもあります。

この時点では、まだ「ユニットテスト」と言う概念は知りませんでしたが、何とかしなければと言う危機感はありました。しばらくの間、この問題に対して悩んだ結果、ライブラリと一緒に作成していたサンプルファイルを利用する事を思いつきます。CLX プロジェクトでは、新たなライブラリを追加すると、そのライブラリを利用するための簡単なサンプルコードも併せて作成する形で更新を続けており、この時点で 100 ファイル程度のサンプルファイルが手元に存在しました。そこで、これらのサンプルファイルを下記のようなシェルスクリプトで全て実行し、コンパイルエラーが存在しないかどうかを確認する事でテストとする方針にしました。

for file in `ls example/*.cpp`; do
    command="$CC $CFLAGS $file $LDFLAGS"
    $command 2>$TMPLOG >/dev/null
    if [ -e $TMPLOG -a -s $TMPLOG ]; then
        cat $TMPLOG >>$ERRLOG
    fi
done

対象とするコンパイラGCC、Visual C++、および Borland C++ でしたが、全てのコンパイラに対して毎回コンパイルを実行すると数 10 分程度の時間を要していたため、実際には、通常時は GCC のみでリリース前のみ全てのコンパイラで検査する形を採用していました。これにより、完全なテストとは言えないまでも単純なコンパイルエラーは検知できるようになり、何もしないよりはマシと言うレベルには改善する事ができました。

はじめてのユニットテスト

2009 年頃から、利用者数的には恐らく最大の成果物となる CubePDF の開発がスタートしました。私自身は、ちょうどこの前後でユニットテストと言う概念を知ったため、C# による開発では比較的早い段階から NUnit を利用したユニットテストを用意する事に成功しています。しかし、この時点では少なくとも 2 つの問題が存在しました。1 点目は見た目と処理の分離 (PDS: Presentation Domain Separation) と言う原則が理解できていなかったため View に近い部分のユニットテストが書けずにいた事、そして 2 点目はテスト時間です。

[Test]
public void TestConvertFileType() {
    foreach (Parameter.FileTypes type in Enum.GetValues(...)) {
        UserSetting setting = new UserSetting();
        setting.FileType = type;
        setting.PostProcess = Parameter.PostProcesses.None;
        ExecConvert(setting, "-type");
    }
}

private void ExecConvert(UserSetting setting, string suffix) {
    string output = System.Environment.CurrentDirectory + @"\\results";
    foreach (string file in Directory.GetFiles("examples", "*.ps")) {
        /* ... */
        bool status = File.Exists(setting.OutputPath);
        Assert.IsTrue(status, "File.Exists: " + file);
    }
}

上記は CubePDF における変換処理のユニットテストの一部です。これらのユニットテストを記述した当初は、事前に用意したいくつかの PostScript ファイルに対して、CubePDF で取り得る全ての設定でテストする事 を目的としていたのですが、1 つのテストケースに対して 1 分以上の時間を要すると言うとんでもないテストコードとなりました。

テストを実行して成功した後、小さな変更をしてテストが失敗したら、問題の原因がどこにあるかを正確に知ることができる。それは、今しがた実施した小さな変更のどれかなので、変更を元に戻してやり直すことができる。しかしテストが大きければ、実行時間は非常に長くなってしまう。このため、エラー箇所を特定できるまで頻繁にテストを実行するのを、つい避けてしまいがちになる。

Michael C. Feathers, “レガシーコード改善ガイド” (p.15)

CubePDF は、どちらかと言えばかなりテスト規模の小さな部類に入ると予想されますが、上記のようなテストケースが大量に存在していたため、毎回のテスト実行が非常に億劫になる現象を引き起こしました。この失敗で得られた教訓は、最悪でも選択的なテスト実行においては余計な時間を取られないよう、少なくとも foreach 内の各テストが単一のテストケースとなるように記述を工夫すべきと言うものでした。

Ruby を用いた Web サービスと RSpec

CubePDF のリリース後、2011 年には CubeICE、2013 年には CubePDF Utility と、利用者数的には現在でもキューブ・ソフトの主力となるソフトウェアをリリースしていますが、後から振り返ってみると、個人的な開発スタイルとしては停滞期を迎えた時期でした(もちろん、その時、その時では、頑張っているつもりでしたが)。

この頃、キューブ・ソフトでの開発と並行して個人的なプロジェクトである Web サービスの開発・運営にも取り組んでおり、この時の成果物が SoGap となります。SoGap は、複数のソーシャルメディア(ここでは、はてなブックマークTwitter 、および Facebook)のいずれでも話題になっている記事を除外する事によって、各ソーシャルメディアでのみ話題になっているニッチなものを探そうと言うコンセプトで、概要および SoGap で取得したデータを用いた解析に関しては Proposal of a New Social Signal for Excluding Common Web Pages in Multiple Social Networking Services と言う論文にまとまっています。ただ、残念ながら諸々の都合で、2015 年 11 月 20 日をもって SoGap の更新は終了しました。

この Web サービスの開発時に利用したのが RSpec でした。私自身は Spec と言う概念を正確には把握しておらず、単にユニットテストフレームワークの一つとして利用していたのですが、上手く記述すると実行した時に英語として読める形で結果が出力されていく様は、書いていて楽しいかもと思わせる魅力でもありました。

RSpec に関する記事で学んだ事は、単一のテストケースに含まれる Assert の数はできるだけ少なくすべきと言うものでした。特に、for 文中の各要素に対して Assert が実行されるような状況でテストが失敗すると、どの部分が失敗したのか判別するのが困難になるので良くないと言う指摘は、なるほど確かにと納得した事を覚えています。RSpec に関する記事では 1 テスト 1 Assert と言う主張も見られ、さすがにそこまでは厳しいですが、失敗した時に判別できるようにメッセージ等を工夫するなど、それまであまり気にしていなかった点に気付けた事は、テストと言う観点から見ても良かったと思います。

PDS とアプリケーションのユニットテスト

再び C# の開発に目を戻します。C# による開発では長年、アプリケーションに近い部分のユニットテストを何とかしたいと言う思いがあり、2014 年頃にようやく Presentation Domain Separation (PDS) と言う概念にたどり着きます。最初は非公開としているプロジェクトのリファクタリング時に適用したのですが、何度か失敗した後、公開しているプロジェクトにも適用する事に成功しました。これらの詳細に関しては、下記の記事を参照下さい。

適用したパターンは、それぞれのソフトウェア毎に微妙に異なりますが、CubeICE のリファクタリング以降は概ね View 以外はユニットテストでカバーできる形になりました。View 部分のユニットテストは相変わらずの懸念事項です。Friendly と言うフレームワークを用いた View テストの発表・実演を何度か見て、非常に良さそうと言う印象を抱いているので、折を見て試してみようとは思っています。

開発をサポートする Web サービスの拡充

2018 年現在までたどり着きました。ここ 1, 2 年、開発面で大きく前進したと実感できるのは AppVeyorCodecov と言った開発をサポートする Web サービスの存在が非常に大きいと感じます。現在では、キューブ・ソフトで開発しているソフトウェアおよびライブラリのかなりの部分まで Continuous Integration (CI) の実現に成功し、ユニットテストカバレッジ等も分かりやすく閲覧する事ができます。かつてであれば、対応するパッケージを自らのサーバにインストールする所から始めなければならなかったのが、ボタン一つで多くの作業が終了してしまうのは、利用者から見れば非常に大きなメリットです。

Codecov によるカバレッジ

Web サービスを利用する事によってカバレッジと言う指標に対する見方も変わりました。カバレッジと言う指標は必ずしもソフトウェアの品質を保証するものではありませんが、後々テストを追加していく時のための枠組みとして、取り合えずこの辺りまではやっておくと言う指標程度には利用できると感じています。また、行単位でテスト状況が確認できると言うのは非常に便利で、カバレッジと言う数値自体は置いておくとしても、この機能だけでも利用する価値はあるように思います。これらの詳細な感想については、下記も参照下さい。

以上、これまでの自分の経験を主にテストと言う観点でまとめてみました。PDS と言う概念にたどり着くまで、プログラミングを始めて 15 年程度、GUI プログラミングを開始してからでも 4, 5 年かかっている等、改めて振り返ると恥ずかしい部分も多々ありました。また、現在でも、よく分かっていないと感じる事の方が多い状態ですが、これからも少しずつ解決していければと思います。

*1:プログラムが足りないような気がするのですが、残念ながら、これ以外のプログラムをサルベージする事はできませんでした。

*2:メインとするプログラミング言語C# になった等の都合で、残念ながら、現在は保守をしていません。

7-Zip ライブラリとしての CubeICE

圧縮・解凍ソフト CubeICE をゼロから改修 に記載した通り、昨年、CubeICE に対して大幅な修正を実施しましたが、この際にライブラリとしても利用できるようにインターフェース(クラスやメソッド)を整理する事をテーマの一つに設定していました。そこで、この記事では CubeICE (正確には Cube.FileSystem.SevenZip.dll)を 7-Zip ライブラリとして利用するための方法について記載します。

事前準備

Cube.FileSystem.SevenZip は NuGet 経由で取得する事ができますが、この中には 7z.dll が含まれないので別途ダウンロードして実行ディレクトリに配置して下さい。7z.dll は www.7-zip.org から取得できる他、オリジナルに対して Unicode ビルドする等いくつかの修正を加えたものを GitHub releases に公開しています(また、修正版のリポジトリcube-soft/7z で公開しています)。尚、C# 等から Unmanaged な dll を利用する場合 x86/x64 の設定が問題になるので、特に Any CPU でビルドする場合、どちらの dll を必要とするか注意して下さい。

圧縮ファイルの解凍・展開

圧縮ファイルを解凍・展開するサンプルコードは下記になります。尚、全てのコードで "using Cube.FileSystem.SeventZip;" が記述されているものとします。

// Set password directly or using Query<string>
var password = new Cube.Query<string>(e =>
{
    e.Result = "password";
    e.Cancel = false;
});

using (var reader = new ArchiveReader(@"path\to\archive", password))
{
    var progress = new Progress<Report>(e => DoSomething(e));
    reader.Filters = new[] { ".DS_Store", "Thumbs.db", "__MACOSX", "desktop.ini" };
    reader.Extract(@"path\to\directory", progress);
}

ArchiveReader クラスは、オブジェクト生成時の第 1 引数に指定されたファイルに対して、解凍・展開するために必要な 7-Zip モジュールを自動的にロードします。また、第 2 引数はパスワードを表し、文字列を直接指定できる他、Cube.IQuery<string> を実装したオブジェクトを指定する事も可能です。このオブジェクトは、「パスワード・ダイアログ」などを通じてユーザにパスワードを尋ねるようなインターフェースを実現する際に利用します。

Filters プロパティは CubeICE のフィルタリング機能をライブラリとして実装したもので、解凍・展開時に除外するファイルまたはフォルダの一覧を指定します。最後に Extract メソッドに解凍・展開後のフォルダのパスを指定して、処理を開始します。この時、第 2 引数に System.IProgress<Report> を実装したオブジェクトを指定すると、このオブジェクトを通じて処理状況が通知されます。

using (var reader = new ArchiveReader(@"path\to\archive", "password"))
{
    // Save as "path\to\directory\{item.FullName}"
    reader.Items[0].Extract(@"path\to\directory");
}

ArchiveReader クラスの Items プロパティでは圧縮ファイルに含まれる各項目の情報 (ArchiveItem) を取得できますが、ArchiveItem に定義されている Extract メソッドを実行する事で個別に解凍・展開する事も可能です。ただし、開発環境で検証した限りですが、解凍・展開するフォーマットによっては処理時間が増大する場合があるようです。そのため、このメソッドは、あくまでも特定ファイルを解凍・展開したい時に使用する程度に留めておいた方が良いかもしれません。

圧縮ファイルの生成

圧縮ファイルを生成するサンプルコードは下記になります。

using (var writer = new ArchiveWriter(Format.Zip))
{
    writer.Add(@"path\to\file");
    writer.Add(@"path\to\directory_including_files");
    writer.Option  = new ZipOption { CompressionLevel = CompressionLevel.Ultra };
    writer.Filters = new[] { ".DS_Store", "Thumbs.db", "__MACOSX", "desktop.ini" };
    
    var progress = new Progress<Report>(e => DoSomething(e));
    writer.Save(@"path\to\save.zip", "password", progress);
}

ArchiveWriter クラスは、まず、圧縮形式を指定してオブジェクトを生成します。Format は、圧縮と解凍・展開で共用しているため大量の定義が存在しますが、ArchiveWriter として対応しているものは Zip, SevenZip, Tar, GZip, BZip2, XZ の 6 種類となります。そして、Add メソッドを通じて、生成されたオブジェクトに対してファイルまたはフォルダを追加していきます。尚、フォルダが指定された場合、フォルダに含まれるファイルおよびフォルダが再帰的に追加されていきます。最後に、Save メソッドに保存パスを指定して圧縮処理を開始します。

Option プロパティには圧縮時の各種オプションを指定する事ができます。指定可能な内容については、ArchiveOption.cs を参照下さい。まだ、基本的なオプションにしか対応できていませんが、追々、その他のものにも対応していく予定です。また、Filters プロパティおよび、Save メソッドの System.IProgress<Report> 引数の役割は解凍・展開の時と同様です。

using (var writer = new ArchiveWriter(Format.Tar))
{
    writer.Option = new TarOption
    {
        CompressionMethod = CompressionMethod.BZip2, // GZip, BZip2, XZ or Copy
        CompressionLevel  = CompressionLevel.Ultra,
    };

    writer.Add(@"path\to\file");
    writer.Add(@"path\to\directory_including_files");
    writer.Save(@"path\to\save.tar.gz");
}

*.tar.gz のように Tar 系の圧縮ファイルを生成する場合、ArchiveWriter のコンストラクタには Format.Tar を指定してオブジェクトを生成します。その上で、Option プロパティに対して、TarOption クラスを利用して圧縮方式を指定します。

進捗状況のレポート機能

System.IProgress<Report> を通じて通知される進捗内容は下記になります。

public class Report
{
    public ReportStatus Status { get; }
    public public Information Current { get; }
    public long Count { get; }
    public long TotalCount { get; }
    public long Bytes { get; }
    public long TotalBytes { get; }
    public double Ratio => TotalBytes > 0 ? Bytes / (double)TotalBytes : 0.0;
}

TotalCount は圧縮または解凍・展開対象となるファイルおよびフォルダの総数、Count は Bytes は処理の終了した数を示し、TobalBytes および Bytes はバイト数に関する内容を表します。Current は現在処理中のファイルまたはフォルダの情報を保持し、Status は Current の処理状況 を Begin(開始直前)、End(終了)、Progress(処理中)で示します。

尚、Status および Current に関しては現時点では解凍・展開用と言う性質が強く、圧縮時にはあまり信頼できません(一応 Begin は通知されますが、End の通知は未実装)。これは、圧縮処理は 7-Zip 自体がマルチスレッド対応している事もあり、正確に通知する事が難しいと言う課題が残っているためです。この辺りをどうするかは、今後、検討していきます。

CubePDF シリーズの大改修

先日 CubePDF Utility 0.5.0β のリリースが完了し、数ヶ月にわたる CubePDF シリーズの改修、さらに言えば、去年からスタートさせていた CubeICE を含むキューブ・ソフト初期のソフトウェア大改修プロジェクトがようやくひと段落しました。そこで、この記事では CubePDF シリーズ、特に CubePDF Utility の改修後書を記載していきます。

はじめに

CubePDF シリーズには全部で 6 個のソフトウェアが存在しますが、今回改修した CubePDFCubePDF Utility、それに簡易版 CubePDF Utility として位置付けている CubePDF Page を始めとして、多くのユーザにご利用頂いているようです。特に、CubePDF は単純な累計ダウンロード数であれば 1,000 万を超えるような数字となっており、約 8 年前に最初のバージョンをリリースした時には、ここまで育つとは思いもよらなかったと言うのが正直な感想です。

CubePDF Utility

そんな CubePDF シリーズですが、10 年近い年月が経過する内に、ソフトウェアの保守がどんどん大変になっていくと言う状態が続いていました。また、CubePDF シリーズ内でのソースコードの共有が上手くいっていなかった事や、CubePDF Utility に存在する諸問題を解決するために利用ライブラリを変更したい等、様々な課題を解決するに際して、一度ソースコードを整理しておきたいと思ったのが今回の大改修を実施した理由です。

余談として、本当であれば CubePDF の話を主役に据えたかったのですが、CubePDF に関しては思ったほど書く事がないと言う理由で、今回の主役は CubePDF Utility に譲っています。CubePDF は、私が人生の中で初めてゼロから作成した GUI アプリケーションですが、ソースコードの保守と言う観点で見ると、他のものに比べれば意外と上手くいってる事は嬉しい誤算でした。これは、View 自体はさほど複雑ではない等の理由によるものだろうと思いますが、そう言った点も含めて、いろいろと予想外だったソフトウェアです。

PDFium の採用

CubePDF Utility の改修における大きな変更点の一つとして PDFium の採用が挙げられます。CubePDF Utility は、これまでレンダリングエンジンとして MuPDF .NET Framework 用にラップした PDFLibNet と言うライブラリを使用していましたが、Unicode 版としてビルドされていないためにファイルパスの扱いで問題が発生する事があり、サムネイル画像が表示されない等の原因にもなっていました。

一方 PDFium では、下記のように必要なタイミングで必要なバイトデータを返す関数ポインタを指定するためのインターフェースが公開されています。このインターフェースを利用してファイルの操作自体は C#/.NET 側で行う事により、これまでの懸念事項であったパスの問題を解決する事ができました (PdfiumReader.cs)。

_core = PdfiumApi.FPDF_LoadCustomDocument(
    new FileAccess
    {
        Length    = (uint)_stream.Length,
        GetBlock  = Marshal.GetFunctionPointerForDelegate(_delegate),
        Parameter = IntPtr.Zero,
    },
    password
);

WPF の再入門

CubePDF Utility は、約 6 年前に WPF を用いて作成した初めての GUI アプリケーションでしたが、WPF での代表的な開発パターンである Model-View-ViewModel (MVVM) への理解が足りなかった事が、その後の保守性の悪さに繋がりました(所謂 Fat View、Fat ViewModel 問題)。また、完成したアプリケーションを実際に試してみるとコールドスタートが非常に遅いと言う結果となったため、これ以降の何年もの間、新規プロジェクトにおける WPF の採用を見送る原因にもなりました。キューブ・ソフトで公開している Windows ソフトウェアの中で CubePDF Utility のみスプラッシュ画面を表示しているのも、このためです。

しかし、ここ数年、システムドライブに採用されるストレージが HDD から SSD に変化するのにつれて、コールドスタートの問題は相対的に小さなものとなりました。また、リリース後に何度か実施した調査によると、どうも 利用している Ribbon ライブラリ の影響も大きいと言う結果が出ていたため、今回、利用ライブラリを Fluent.Ribbon に変更する事で改善を試みています*1。こう言った状況の変化もあって WPF 自体を忌避する理由も徐々に薄れているため、2018 年の個人的なテーマの一つとして WPF の再入門を設定し、これまでに CubeRSS Reader の新規作成と CubePDF Utility の改修を行ってきました。

その成果の一つが下図になります。これは CubePDF Utility のメイン画面を表す MainWindow.xaml.cs の改修前後における記述行数の変化を示したものですが、2,120 行 から 0 行 に削減できた事が分かります(Visual Studio による自動生成部分を除く)。もちろん、実際に 1 行も記述せずに実現できている訳ではないのですが、インパクトの大きさを示すのには良い例かなと思って紹介します。

MainWindow.xaml.cs コード行数の変化

今回、個人的にもっとも感動したのは Expression Blend SDK によって提供される Behavior と言う概念でした。WinForms を始めとして、EventHandler ベースの GUI プログラミングはややもすると View がゴチャゴチャする (Fat View) と言う問題点が指摘されますが、この原因の一つに「EventHandler をどこに記述すれば良いのか判断できない」と言うものがあるように思います。自分自身を振り返っても、EventHandler の記述内容を見ると public なプロパティおよびメソッドのみで完結しているので必ずしも MainForm.cs のような場所に記述する必要はないが、それ以外の適当な場所も思いつかないため、結局そこに書いてしまう(そして、どんどんと膨れ上がる)と言う経験が多々ありました。

Behavior と言う概念は、この問題に対して「単一、あるいは一纏まりの View の動作に対して名前を付け、クラス化する」と言う解決策を示してくれました。さらに、これによって「View の動作単位での汎用化」と言う可能性も視野に入るようになり、特に MVVM における Messenger (EventAggregator) パターンと併用する事で、かなり柔軟な記述が可能となってきました。例えば、CubePDF Utility では「メッセージとしてサブ画面用の ViewModel オブジェクトを送信すると、View は ViewModel の型に紐づけられた SubView を生成して DataContext にその ViewModel を設定し、ShowDialog メソッドを実行する」と言う「動作」を予め作成しておく事で、View の個別の生成処理をかなり削減するのに成功しています。

<i:Interaction.Behaviors>
    <xb:DialogBehavior />
    <xb:OpenFileDialogBehavior />
    <xb:SaveFileDialogBehavior />
    <xb:UriBehavior />
    <xb:CloseBehavior />
    <xb:ClosingBehavior Command="{Binding Ribbon.Close.Command}" />
    <my:PasswordWindowBehavior />
    <my:PreviewWindowBehavior />
    <my:InsertWindowBehavior />
    <my:RemoveWindowBehavior />
    <my:MetadataWindowBehavior />
    <my:EncryptionWindowBehavior />
    <my:SettingsWindowBehavior />
    <my:DragFileBehavior Command="{Binding Drop}" />
</i:Interaction.Behaviors>

Behavior と言うライブラリ自体は 6 年前から使用していたのですが、今回、改めて見直す事によって View との連携方法を大きく前進できたのは非常に良かったように思います。

正式リリースに向けて

キューブ・ソフトで公開しているソフトウェアの多くは β や RC を名乗っています。「いつまで β なんだよ」と言う指摘を見かける事もありますが、これは私自身が長年、正式リリースを名乗って良いのかどうか判断できないし自信もない、と感じていたためと言う面がありました。

この問題を克服するために、2017 年から「アプリケーション部分を含めたユニットテストの拡充と CI 環境による継続的なテスト」と言う課題に取り組んできました(参考:圧縮・解凍ソフト CubeICE をゼロから改修)。そして、現時点で振り返ってみると、まだ完璧とは言い切れませんが、ここ 1 年のものが実を結びつつあると言う実感がでてきました。もう少し時間がかかりそうですが、順次、正式リリースと言えるように今後も環境を整えていく予定です。

また、Cube.Pdf プロジェクトに関しては、ライブラリ部分が CubePDF シリーズで必要な範囲しか実装できておらず不十分なため、もう少し頑張って実装したいと言う思いがあります。こちらに関しては、どうしても優先度が下がりがちになってしまうのですが、全体のスケジュールを見ながら上手く進めていきたいと思います。

*1:ただし、Fluent.Ribbon .NET Framework 4 以降でないと利用できないため、.NET 3.5 版では従来の Ribbon ライブラリを使用しています。