ViewModel をテスト可能にするためにファイルストリームを活用するパターンを考察

一般的に(?)MVVM パターンを使うと xUnit が作りやすい。主に Model やロジックに対して xUnit を適用すると TDD 的にあとあと楽なのですが、もう一歩進んで ViewModel にも xUnit を適用してみましょう、という話の続きです。
まぁ、普通にオブジェクト指向のクラスを作っている中で、ある程度隠蔽化されていれば xUnit を使うのもそう難しくはないんですけどね。ただ、実務的にやってみると GoF の組み合わせパターンを使ったときに xUnit の適用どころが難しかったり、最初の簡単なロジックは xUnit を使っていたけど、だんだん拡張していくうちに xUnit が使えなくなってしまったという場合もよくあります。そうそう。プロジェクトが進む中で結合テストをしていて、あまりにも設定がややこしくなってしまった xUnit のコードはばっさり捨ててしまっても構いません。本来ならば、xUnit がきちんと動く中でクラスを拡張するのがベストなのですが、時間的な制約や他社的な制約などがあってなかなか理想通りにはいきません。ならば、ある程度、xUnit で品質が保たれているであろう基本的なクラスであると「信じて」、複雑怪奇になってメンテできなくなってしまった(主に設定関係で動かなくなってしまった)テストクラスを捨ててしまうのも作業的な効率化です。あえて言えば、そのときにいくつか正しく動くパターンを残しておくのがベターです。チェックしておきたい基本的なテストコードだけ残して、あとはばっさりとコメントアウトしてしまいます。

ファイルを扱うときの ViewModel は?

MVVM パターンを使うときには、View と Model との分離が目的であり、ロジックを View に置いたり ViewModel に置いたりしてコード量を調節します。でもって、Model は単なる POJO なデータクラスであったり、Entitiy Framework のようにデータベースに接続したりする訳ですが、永続的なストレージとしては「ファイル」もあたったりします。ファイルアクセスは設定を保存したり画像ファイルを読み込んだりします。時には XML をアクセスすることもありますが、これはデータベースと同じように扱ってもよいでしょう。どちらにせよ、何らかの形で「データ」が、Model と ViewModel の間でやり取りをすることになります。

このとき、ファイルオープンをする機能は ViewModel にあったほうがいいのか否か、ということを ViewModel に xUnit を適用するという観点から考察してみます。と言いますか、すでに考察した後なので、結論から言うと、ViewModel に xUnit を適用するためには「オープン済みのファイルストリーム」を使うのが良いようです。

OpenCCPM を作っていく中で、各タスクをファイルに保存して復元するという機能を実装してみました。データ自体は XML でシリアライズすればよいので、非常に簡単です。プロトタイプは WPF で作っているので一発で保存/復元ができるので便利ですよね。と思っていたのです。が、これを Xamarin.Forms に移植しようとしたときに考え込んでしまいました。そもそものファイルの保存先は、iOS/Android で異なるし、Surface で使えるようにするため UWP アプリにするときは Storage を使わないといけません。ってことは、ファイルアクセス自体は、実行環境依存になるのです。WPF の場合は System.IO でファイルオープンができるけど、UWP は Storage を通さないと駄目というアレですね。となると、環境依存部分を含んだまま ViewModel を作ると xUnit が結構面倒なことになります。そう環境ごとに異なった xUnit を用意しないといけないからです。

実は、データベースを扱う EF のときにも同じことが言えて、WPF 等を使って SQL Server を扱うときは EF として統一して使えるのですが、ASP.NET Core を使って .NET Core 上で作ろうとすると途端におかしなことになります。.NET Core でも SQL Server を使えば EF が作れますが、じゃあ MySQL の場合はどうなのか、SQLite の場合は?そもそも、独自の軽いDBを作ったときにはどうなのか、という問題が残ります。

となれば、ViewModel や Model から実行環境依存な部分を取り除いてしまうのがベターですよね。特にファイルオープンやクローズやファイル検索などの環境依存なところを ViewModel から取り除いてしまったほうが、統一的な xUnit が作れそうです。

System.IO.Stream を使って環境依存部分を外へ追いやる

一例を示します。ViewModel が Model を抱えるようにして View にアクセスするパターンです。Model 用のデータが二重化してしまっているのが面倒な感じなのですが、永続化するときに Model を一気にシリアライズ化できるのでこれはこれで便利なのです。

public class TaskCanvasViewModel : BindableBase
{
    CcpmModel _model;
    public TaskCanvasViewModel(CcpmModel model)
    {
        _model = model;
    }

    public ObservableCollection<CcpmTask> Items
    {
        get { return _model.Tasks; }
    }

    TaskViewModel _cur;

    public TaskViewModel Current
    {
        get { return _cur; }
        set { this.SetProperty(ref _cur, value); }
    }

~省略~

    public bool Save(System.IO.Stream st)
    {
        return _model.Save(st);
    }
    public bool Load(System.IO.Stream st)
    {
        _model.Load(st);
        return true;
    }
}

永続化のメソッド(Save/Load)は、ファイルそのものを扱うのではなくてストリームを扱います。.NET Framework の場合は、System.IO.Stream がストリームの基底クラスになっています。Java とか他の言語でも同じパターンが使えます。
ストリームはオープン済みなので、ファイルがないとか権限でアクセスできないとかのエラーはありません。また、ファイルストリームだけではなくメモリストリームや文字列のストリームも使えるので汎用性が高いのです。

Model クラスではシリアライズ機能を使ってストリームに対して読み書きをします。このあたりは、WPF だけではなくて、Xamarin.iOS/Android でも共有できるところです。

public class CcpmModel
{
    private CcpmTaskCollection _tasks = new CcpmTaskCollection();

    public CcpmTaskCollection Tasks
    {
        get { return _tasks; }
    }

    // *************************************************
    // 永続化機能
    // *************************************************
    /// <summary>
    /// XML形式で保存する
    /// </summary>
    /// <returns></returns>
    public bool Save(Stream st)
    {
        // 先頭に戻す
        st.SetLength(0);
        // カレントフォルダに保存
        var se = new System.Xml.Serialization.XmlSerializer(typeof(CcpmModel));
        var sw = new System.IO.StreamWriter(st);
        se.Serialize(sw, this);
        return true;
    }
    /// <summary>
    /// XML形式から復元する
    /// </summary>
    /// <returns>読み込んだ CcpmModel オブジェクトを返す</returns>
    public CcpmModel Load(Stream st)
    {
        var se = new System.Xml.Serialization.XmlSerializer(typeof(CcpmModel));
        var sr = new System.IO.StreamReader(st);
        var obj = se.Deserialize(sr) as CcpmModel;
        CopyFrom(obj);
        return this;
    }
    public static CcpmModel LoadXml(Stream st)
    {
        var se = new System.Xml.Serialization.XmlSerializer(typeof(CcpmModel));
        var sr = new System.IO.StreamReader(st);
        var obj = se.Deserialize(sr) as CcpmModel;
        return obj;
    }

    /// <summary>
    /// 自モデルにコピーする
    /// </summary>
    /// <param name="src"></param>
    /// <returns></returns>
    public CcpmModel CopyFrom(CcpmModel src)
    {
        this.Tasks.Clear();
        src.Tasks.All(x => { this.Tasks.Add(x); return true; });
        return this;
    }

    // *************************************************
    // CURD 機能を実装する
    // *************************************************
~~省略~~
}

こうすると、ViewModel と Model が実行環境に依存しないので、PCL 化ができます。WPF/UWP/Xamarin.iOS/Android/.NET Core の5種類の環境で利用できるようになります。

これは永続化の機能部分をストリームとして抽象化していることで得られる利益です。

ファイルアクセスの環境依存部分は何処に作るのか?

さて、ひとつ大きな問題が残っています。具体的にファイルアクセスをする部分(ファイルオープンをしてストリームを作成する部分)は何処に持っていけばよいでしょうか?ViewModel と Model は PCL 化が目的でもあるので、ここにファイルアクセスのような環境依存部分を持ってくるとはできません。パターン的に、ViewModel, Model に置くことは可能です。その場合は、環境依存のための ViewModel, Model を別途作ることになります。

環境依存用の Model を作ってもよいのですが、ちょっと考えてみましょう。そもそも、View 自体は環境依存ではないでしょうか?WPFのXAMLや、Xamrin.Android の UI や、共通機能としての Xamarin.Forms の XAML などは、それぞれの実行環境に依存しています。ということは、View のほうに持って行っても良いですよね。

ということで、View のコードビハイドに持って行ってしまいます。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
~~略~~
    /// <summary>
    /// タスクカードを保存
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void clickSave(object sender, RoutedEventArgs e)
    {
        var st = System.IO.File.OpenWrite(OpenCcpm.Models.CcpmModel.SETTING_FILENAME);
        this.tc.Save(st);
        st.Close();

    }
    /// <summary>
    /// タスクカードを読込
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void clickLoad(object sender, RoutedEventArgs e)
    {
        var st = System.IO.File.OpenRead(OpenCcpm.Models.CcpmModel.SETTING_FILENAME);
        this.tc.Load(st);
        st.Close();

    }
}

これは、相当異論があると思うし MVVM パターンなのにコードビハイドを使わないと駄目なのはおかしい、と思うでしょうが、ViewModel を xUnit でテストができるようにするには、この方法がいいんですよね。

ちなみにテストコードはこんな感じになります。

/// <summary>
/// モデルを保存する
/// </summary>
[TestMethod]
public void TestSave()
{
    // 初期化
    var model = new CcpmModel();
    // 2つタスクを追加する
    model.Create("T001").Title = "最初のタスク";
    model.Create("T002").Title = "次のタスク";

    var st = System.IO.File.OpenWrite(CcpmModel.SETTING_FILENAME);
    var ret = model.Save(st);
    st.Close();
    Assert.AreEqual(true, ret);
    Assert.AreEqual(true, System.IO.File.Exists(CcpmModel.SETTING_FILENAME));
}

/// <summary>
/// モデルを読み込む
/// </summary>
[TestMethod]
public void TestLoad()
{
    // 初期化
    var model = new CcpmModel();
    // 2つタスクを追加する
    model.Create("T001").Title = "最初のタスク";
    model.Create("T002").Title = "次のタスク";
    var st = System.IO.File.OpenWrite(CcpmModel.SETTING_FILENAME);
    model.Save(st);
    st.Close();
    // XMLから復元
    st = System.IO.File.OpenRead(CcpmModel.SETTING_FILENAME);
    model.Load(st);
    st.Close();
    Assert.AreEqual(2, model.Tasks.Count);
    Assert.AreEqual("最初のタスク", model.Tasks[0].Title);
    Assert.AreEqual("次のタスク", model.Tasks[1].Title);
}

実際は、もうちょっと進めてから考えることになりますが(多人数で分担するとか、Viewの規模の問題とか)、いまのところ View に近いところが一番環境依存しているところなので、そこに Save/Load 機能を置いています。

まあ、環境依存のため(設定ファイル絡み)の Model をひとつ用意して、そこへ prototype パターン的にアクセスするのがよりベターかなと思いますが、ひとまず、これで。

カテゴリー: 開発, C#, TDD | ViewModel をテスト可能にするためにファイルストリームを活用するパターンを考察 はコメントを受け付けていません

UWPでファイルのドロップして画像表示させる

UWPでファイルのドロップをしたい – かずきのBlog@hatena
http://blog.okazuki.jp/entry/2016/02/21/162757

を見て、ああ「AllowDropプロパティをTrue」にするだけでよかったんだ、と思って画像の表示まで試してみました。

private void Grid_DragOver(object sender, DragEventArgs e)
{
    e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Link;
}

private async void Grid_Drop(object sender, DragEventArgs e)
{
    // ファイルの場合
    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var items = await e.DataView.GetStorageItemsAsync();
        if ( items.Count &gt; 0 )
        {
            // 画像ファイルを表示する
            var it = items[0];
            var sf = it as Windows.Storage.StorageFile;
            var fs = await sf.OpenReadAsync();
            var bi = new BitmapImage();
            bi.SetSource(fs);
            this.image1.Source = bi;
        }
    }
}

private async void button_Click(object sender, RoutedEventArgs e)
{
    //Storageファイルを取得
    var picker = new FileOpenPicker();
    picker.FileTypeFilter.Add(".png");
    var file = await picker.PickSingleFileAsync();
    var fs = await file.OpenReadAsync();
    var bi = new BitmapImage();
    bi.SetSource(fs);
    this.image1.Source = bi;
}

コツは、ドラッグしたファイルが IStorageItem なので StorageFile にキャストしてしまえば ok です。これでストアアプリの Storage として扱えるので定番処理で BitmapImage に突っ込みます。
と、さっくりと書いているように見えますが、最初は it.Path のようにパスを取ってきてメモリ上に変換してとあれこれやっていたんですよね。結構苦労しましたが、StorageFile にキャストができることが分かって、すっきりした状態になります。
ちなみに、ボタンのほうは試しに FileOpenPicker で開いたものです。

こんな風に UWP アプリにドロップできます。

ピクチャフォルダ以外もアクセスできる

8.1 のストアアプリの場合、パッケージマニフェストで「ピクチャライブラリ」を選択しないと、Windows.Storage.KnownFolders.PicturesLibrary がアクセスできなかったのですが、この方法だとどんなフォルダにもアクセスできます。UWP から PicturesLibrary を参照するときにアクセス制限がかかるんですが、これって意図した動きですよね? 8.1 のときがどうだったか覚えていないのですが、PicturesLibrary を使わない限り、ローカル PC のどのフォルダもアクセスできます。

おそらく、

– ピッカーやドラッグの場合は、ユーザーが「意図」して操作したので、どこでもアクセスできる。
– プログラムが自動で PicturesLibrary 配下を探ろうとするとアクセス制限がかかる。

という思想なのかもしれません。

カテゴリー: 開発 | UWPでファイルのドロップして画像表示させる はコメントを受け付けていません

パタンランゲージとアジャイルの関係を再考する

「パタン・ランゲージによる住宅の生産」の序文に「1. 建築と施工は分離されていてはならない。(アーキテクトビルダー)」とある。何処かで聞いたようなことがあると思う。アジャイルソフトウェア開発宣言 に「プロセスやツールよりも個人と対話を…」とあるのと非常に似ている。

引用として、生産プロセスの基本条件を抜き出すと、

  1. 建築家と施工は分離されてはならない。(アーキテクトビルダー)
  2. 生産システムは、地域の高度に分散化された職人群(拠点)を利用する。(ビルダーズヤード)
  3. 共有地はユーザーによって最も大切な場所であり、彼らの管理下にあるべきである。(共有地の共同設計)
  4. ユーザーは、自分自身の住まいの間取りについて、現代建築のような受け身の姿勢ではなく、核家族によってそれぞれ異なる間取りができるような積極的な姿勢で参加しなければならない。(個々の住宅のレイアウト)
  5. 施工のシステムや技術やディテールは、プロセスそのものが必要とする、連続的な巧妙かつ微妙な修正の利くものを選ばなくてはならない。(一歩一歩の建設)
  6. コストコントロールは、柔軟な設計思考のプロセスに適応しなければならない。(コストコントロール)
  7. 建物の細工は、手づくりの楽しいものではなくてはならない。(プロセスの人間的リズム)

とある。

GoF のパタンは、「パタンランゲージ」の真髄を取りこぼしてしまったのか?

その昔、アジャイル協議会があったころ(確か、2002年頃だったと思う)、MVC, GoF のパターンの説明会の中で、東野高校 の見学発表というのがあった。アレグザンダー建築に則った設計をしているので、アジャイル&パタンランゲージを学習する一環として発表していた方がいたのだが、果たして「パタンランゲージ」からヒントを得て「GoF パターン」を UML に加味して利用するというスタイルだったはずなのだが…真髄を取りこぼしてしまったのかどうかは定かではない。ちなみに、GoF の原著を読み直してみると、「6.3 The Pattern Community」あたりにアレグザンダーのあれとはあれこれが違うのだ、と書いてあるのでひょっとすると取りこぼしてしまっているのかもしれない。

ただし、建築のパタンランゲージから、プログラミングのパターンに移るときに重要だったのは、

  • パターンに名前を付けること。
  • 名前を共有することで、複雑な UML を共有できること。

がある。そこで「パタンランゲージ」の「ランゲージ(言語)」たる部分として、各種のパターンを組み合わせるベストプラクティスを作ることが可能、というのがある。GoF パターン以下、○○パターンというのを共有することができるようにはなったが、パターンの組み合わせで「相互の語る」ことができるようになったかというと疑問である。実は、Java カラー本や、ファウラーの「アナリシスパターン」のように、ある程度もまでは基礎パターンの組み合わせで構築物たるシステムを語ることができようになったとは思えるものの、まあ、浸透はしていない。まだ、データベースやネットワークやCPUやメモリなどの各部品は交差せずにそのままのような気がする。

ただし、建築業界がアレグザンダーの提唱する「パタンランゲージ」に則っているかというとそうでもなく、新国立競技場のごたごたしかり、別なところで変なことになってるし、脱構築ナントか的におかしなことになっている。少なくとも、建築と土建が別々になってしまい「アーキテクトビルダー」は不在である。

かつて「アーキテクト」ブームというものがあった

今思えば「フルスタックエンジニア(苦笑)」と同じぐらい「アーキテクト(苦笑)」という職種ブームがあった。SIer と下請けプログラミング会社のはざまだったか、全体設計をする人という意味で「建築家」に近い位置を想定したものだろうが、責任を負わないので建築家とも異なるし、国家試験があるわけでもない「自称アーキテクト」でしかない。様々なパターンを知っているという点では、一種の物知りではあったかもしれないが、そこには責任がない以上、職種とは呼べない。資格さえもない。最も本物の「建築家」のほうもずいぶん怪しい感じなので、それを真似した「アーキテクト」もかなり怪しい。今となっては、アーキテクチャという分野は存在するが、アーキテクトという人は存在しないような気がする。

というのも、先の「アーキテクトビルダー」に話を戻すと、「建築家と施工は分離されない」ので、いわば「設計」と「コーディング」は分離されない。「アーキテクト」を名乗る(あるいはアーキテクチャを構築する)のであれば、要件定義から全体への設計、そしてコーディングまで一貫するのが必須だろうし、そういう仕事の仕方をせざるを得ない。昨今のゼネコンのように、建築会社と施工会社(SIerと各種孫請け)を分けてしまっては、アーキテクトビルダーは成り立たない。成り立つことすらできない。

ゆえに、かつて「アーキテクト」ブームがあったと断言する。

それはさておき、アジャイルを再考する

一言で「アジャイル」と言っても、かつての「アジャイルソフトウェア開発宣言」から遠く離れてしまったアジャイル開発もあるので、元に戻って考えてみる。住宅を生産する、ということは一過性のプロセス(二度と同じ形では進まない)という点で、ソフトウェア開発と似ている。これは、製造業が主に「プロダクト」として同じ製品を効率よく作成することを目的としているのに対して、「プロジェクト」として同じ過程にはならないことを意味している。例えば、同じ IT 業界の中であっても、サーバーのセッティングやルーターの設定のようなものは限りなく「プロダクト」に近い。部品を使い同じレベルのものを大量に作るからだ。逆に何らかの業務アプリケーションを作る場合には「プロジェクト」になる。それぞれの会社では業務プロセスが異なるので、それぞれにカスタマイズされた「プロジェクト」が必要になる。当然、混在するパターンも多く、Wordpress でホームページを作るパターンは「プロダクト」に近いし、SAP などの製品を取り込むパターンも「プロダクト」に近い。また、研究開発は「プロジェクト」と言えるかもしれないが、終わり≒納期が判然としない場合が多い(とはいえ、永遠にやっている研究者はいないが)ので「プロジェクト」でも違ったスタイルになる。

プロジェクトというものは PMBOK で定義されているように、

  • とある予算(人員)のなかでやりくりをする。
  • 同じ生産プロセスにはならない。
  • 期限や納期が存在する。

ことがプロジェクトであり、これを統率するのが「プロジェクトマネージメント」になる。予算や人員は無限ではないし、納期が必ず存在する。そして経済的な側面から効率化を求められる。そういう点で「住宅を建てる」ことに似ている。特に、納期があることとコストマネージメントが必須になる(新国立にコストマネージメントが存在するかどうかは怪しい)。

ここで、セル生産のように分業式ではあるものの作り手の創造性が効率化に寄与する。かつ、住宅においては住む人自身が利用者であり、利用する期間が非常に長いゆえに下手を打つと快適さは長期に損なわれる。ソフトウェア開発の場合には、利用する期間が短期と長期にわかれる。ゲームや一過性のインターネットサービスのようなものは、リリースまでの短期性が重視されることがおおく(最近では、短期すぎるゆえに失敗するパターンも多いのだが)おもに利用期間は「短期」になる。ゆえに、それゆえにとある失敗を長く引きずらずに済むという利点もある(ただし、その失敗は短期的な収入に大きな影響を与えるかもしれない)。もう一つ、業務システムのように会社が続く限り長く使われる長期的なシステムもある。銀行のシステムや工場のFAもそうなのだが、十数年単位で成果物が使われる。

ソフトウェアは、バージョンアップすることによって後からの機能が追加することができるが、住宅においても有機的に住居を追加することも可能である。それは住宅地の進化なのかもしれない。最初に区画整理された計画に沿うだけではなく、ある程度の計画から想定しえない方向に進んでいくこともしばしばである。それは経済的な側面が多いだろうし、今からだと少子化とか過疎地化とかがある。それらを踏まえたうえで、完全な計画をせずに、順応させる。

住宅において、住んでいる家族の構成は年月とともに変わる。子が育ち家を出るかもしれないし、子が増えるかもしれない。それらは、硬直化された建築システムに縛られるのではなく、緩やかに変化できる「可能性」を残しておく必要がある。同じように、長期に利用する業務システムの場合にも、ある程度の変化を許容する「可能性」を残さねばならない。これらは、ソフトウェア開発から最初の納品(リリース)までではなく、その先にもある。昨今「継続的な」というスタイルで言及されることが多いが、これらの「リリース前」と「リリース後」は同列に扱う必要がある。逆に言えば、リリース後だけを重視してもダメだし、リリース前だけを重視してもダメだ。

そこには、建築家&施工と住宅(とそれに住んでいる人)と関係が続くことが分かっている。渋谷の拙い地下道のように造りっぱなしなのではなく、乗客は日々渋谷の地下と地上を行き来する。その往来の増減によっても、ある程度の「変化の許容」が必要になる。それは決して未来を見通すことはできないからだ。未来を見通すことはできないからこそ、ある程度の変化が許容できる状態を維持し、維持するために利用者と開発者との長期的な連携を維持する。ひょっとすると、本格的に維持するためには人の寿命は短いかもしれないが、創業100年と続く菓子屋のように、とある意思を次いでいくことは可能かもしれない。

そんな意思を継いでいくところで、「パタンランゲージ」があり「GoF パターン」があり「アジャイル開発技法」があるのではないかな、と思ったりするのだ、どうだろう。ちょっと、その道筋で肯定的に GoF のパターンをいくつか考え直してみたい。

[amazonjs asin=”4306052613″ locale=”JP” title=”パタン・ランゲージによる住宅の生産 (SD選書)”]

カテゴリー: プロジェクト管理 | パタンランゲージとアジャイルの関係を再考する はコメントを受け付けていません

後置型プログラム言語と記号論と生成文法の思考実験

ふと、ノーム・チョムスキーの生成文法とウンベルト・エーコの記号論を思い出したので、後置型プログラム言語を再考してみる。

たいていのプログラム言語は前置定義型だ

「全ての」と書きたいところなのだが、C# の拡張メソッドや Ruby の後付けメソッド、Javascript のオブジェクトへのメソッドの追加を考えると、後置型とも言えなくもないので「たいていの」にしておく。要は、あらかじめ定義したものを使う、というスタイルでプログラムコードを書いていくのが「前置定義型」だ。

class class A {
public:
  void method() { ... }
};
void main() {
 A a ;
 a.method();
}

な感じでクラスやメソッドを定義する。

記述場所は、main 関数の後からもしれないし、別ファイルかもしれない。ただし「A」として使う時点で、A クラスは定義してあるという前提になる。これは一見、当たり前のように見えるけど、あらかじめ定義されているクラス A がアプリオリであることが前提になる。コードとして記述する、あるいはプログラムが実行される時点で、A から生成される a は、何も A に既定されなければいけない、という法はない。法を作った上で、A を使って a を生成しているというルール的な意味になる。

ただし、コンパイラや補完機能の絡みからすれば、これは便利だ。A から a を生成した時点で、a の型は既定されているので、method というメソッドを持っていることが分かる。あるいは、別のクラス B を使って b を生成したときに、a != b が明確になる。事前に決まっているからこそ曖昧さがなくなり、型推論が可能だし厳密さを保ったところでコードを書けるというメリットを享受できる。

となれば、仮定として「プログラムコードとしての安全性を取り払ったどうなるのか?」という疑問を呈してみよう。厳密に動かすためのプログラムなのに、厳密さを取り除くという考え自体が矛盾しているような感じがするか、思考実験としては妥当だろう。

自然言語的に定義を補完する

例えば、A x としたときに、x の型は決まらないし、A というクラスの内容は定義しない。ただ、A というクラスができて、x というモノ(オブジェクト)ができるという意味だけを取り出してみる。

x is a A;

ってことだ。いわゆるオブジェクト指向の is a だから、A というクラスあるいはカテゴリというものがあることが分かる。かつ。A であるものと A でないものを分離するという役割を担う。しかし、ただそれだけの意味しか持たない。

これをいきなり、A というクラスは func というメソッドを持つということを考えてみよう。その方法は2つ考えられる。

  1. class A { func() { … } };
  2. x.func( … );

ひとつは、1 のようにクラス定義をして func メソッドがあることを知らせる。x は A クラスなので、x が A クラスであるという三段論法から x は func というメソッドを持つことが分かる。これは普通のプログラミングのスタイルだ。

もうひとつ、2 のようにいきなり、func というメソッドを使う方法がある。通常は A クラスは定義されていないのだから、func というメソッドを持つかどうかわからないのだが、いやいや、それ狭いコードの範囲しかみていなくて、コード制作者(コード自身の外の世界)の意図としては、すでに A には func というメソッドがあることを知っているので、コードだけの世界にいきなり func を持ってきても不思議ではない、というスタイルだ。つまりは、コードの外部にあるコード制作者が常に正しとしてコードを書くというスタイルをとる。

しかし、実行時には、func というメソッドがコードという世界で定義されていたら動きようがないので、それは「何事もなく動く」のでもよいし「実行時例外を発生する」方法でもよい。コードの厳密性を探るのであれば「実行時例外」が正しいように見えるが、コード制作者が常に正しいという視点に立ては「何事もなく動」いてもいいような気がする。ただし、コードを実行する世界では、func メソッドの動作は定義されていないので、「何事もなく動く」ということはイコール「何も動かない」ということに等しい。この何も動かないということは例外さえも動かないということだ。

A a ;
a.func();

そう、後置定義型を許せば、上の2行はそのまま動く≒何も動かない、ということになる。

コード外のアプリオリを追加する

もう少し具体的に考えみよう。なんとく変数 a に名前をつけておいて、プリントアウトすることを考える。

A a("masuda");
a.print();

これだけで十分だ。プリントアウトするという意味っぽい print というメソッドを使うだけ、名前を付けるという意味で “masuda” を引数として定義するだけで完成する。本当のとこは、名前付けがコンストラクタの指定なのか、(あるかもしれない)Name プロパティへの指定なのかはどうでもいいことだ。コードを書いた人は、コードの世界よりもすべてを知っているので、コードが動作する枠を超えていても構わない。プリントアウトするという print メソッドは習慣的にプリントアウトするという動作を定義するだけで、それが画面なのか紙なのか3Dディスプレイなのか、それとも他のプロセスへの通知をプリントアウトするのかはわからない。脳内にプリントアウトしたってかまわないはずだ。それらの動きを既定するのはコードの世界なので、それはコード内の世界の「アプリオリな」ライブラリに寄るという答えになる。

ここで、コードを書く人とコードを動かす人(実行環境)とのずれが発生する。一般的なコミュニケーションの伝達の問題であって、プログラムコードではそれらを「厳密さ」で補おうとするけど、人とのコミュニケーションであればそれらの厳密さあまりあてにならない。文化の違いもあれば育ちもあれば宗教的な違いや政治的な違いがある。勿論、学問としてひとつのルール(閉鎖的なルール)で議論することも可能なのだが、日常生活においてはあまり厳密に伝えることは少ない。例えば、居酒屋にて「とりあえずビール」と言ったときに、ビンなのか生なのかピッチャーなのかは問わない場合もあれば問う場合もあれば、盆に注がれたりどんぶりだったり氷入りだったりコーラを一緒に持って来たりといパターンも考えられるけど、まあ大体において「とりあえずビール」で通じることが多い。常連であればなおさらだ。

ならば、コード制作者がコードに対して、いちいち厳密に説明することはすくなくて「とりあえずビール」感覚で「とりあえずプリントアウト」でもいいですよね、って感覚でコードを書くこともできるのではないだろうか。実際、Outlook でメールを印刷しようとすると「とりあえずメモ形式で印刷しますよ」みたいな感じになる。

じゃあ、プリントアウトする用紙を A4横にしたいときは、こんな感じで補完する。

A a("masuda");
a.print()
print() is A4横.

いきなり自然文法になってしまうが、 こんな感じで後付け書く。「プリントアウトしておいて、ええと、A4横でね」みたいな感覚だ。英語文法/文章法でいうところの、結論を先に書いて後から詳細を足す感覚で書けばいい。普段は「A4縦」で印刷するのだから、普通のデフォルトは print だけでいいのだが、ちょっと変わったことをしたいときは後から追加する。

A a("masuda");
a.paper = "A4横"
a.print()

普通のプログラム言語であれば、こんな風に print の前に書く。なぜかというと、print する時点でプリントアウト自体が実行されてしまうので、後から「ちょっと待った」ができないからだ。 インタープリタだったら、逐一実行するためにこう書かないと駄目なのだ。あらかじめ「これは A4横 で印刷するんだぞ」と初心者的にオーダーするわけだ。だが、コンパイラ言語であって、コードを解析するときとコードを実行するときでは時間的なずれがあるのだから、ほどよく プリントアウトに対する設定変更が終わったころにおもむろにプリントアウトを実行する熟練者がいても不思議ではないはずだ。

また、print 自体の動作も、印刷する先は実行時に決まってもよい。実際、コードを動かして Windows で動かすのか、Linux で動かすのか、何かの組み込みデバイスで動かすのか(小さな液晶に出すのか)という違いが出てくる。それでも、print というコード制作者の意図が達成されれば、それでよいし、逆に実行する環境で print という実行メソッドがなければ「何もしない」ことを実行すればよい。そのあたりの、コード外の実行環境は、コード外のアプリオリとして存在するのであって、コード制作者は未来のコード実行環境を想像しながら作るしかない。逆に、想像するしかないから大ざっぱにプリントアウトするという動作だけ決めてしまう。

記号論と生成文法を加味する

厳密に生成文法を使うかどうかは別として、記号論/現象論的な、シニフィエ/シニフィアンの問題を取り扱ってみる。実際の print の動作は、実行環境のアプリオリなライブラリ(ひらたく言えば既存のライブラリ)により実行されるが、先のコードに、コード制作者の意図を加えてもよいだろう。

A a("masuda");
a.print()

print() is "A4横"
print() {
	name <- name.upper()
}

こんな風に、プリントアウトするときに name プロパティを大文字にすることを考えてみよう。print メソッドの中身は print メソッドを使う時点ではなく、実行時に決まるので、コードの記述上 print メソッドの中身はいつでも変えられる。この場合は、”masuda” が指定されたら “MASUDA” のように大文字に変換すること示している(当然、upper 自体が「大文字にする」という動作がになっていれば、という限定がある)。

つまり、print というメソッドは、記号としての意味しかなくて、それはコード制作時と実行時の意味をつなげるという意味しか持たない。解釈する順序は、現実的な時間制約として、

  1. アプリオリな print メソッドが存在する
  2. コード制作者が print メソッドを使う
  3. 場合によっては、コード制作者が print メソッドを補足する
  4. 実行時に print メソッドが実行される。

の順序しかない。場合によっては、1 のアプリオリな print メソッドのライブラリがない場合もある。コード制作者の頭にしかない print メソッドを、なんとなくコード制作者が 3 で補足して、コードを実行する段階において(コード制作者とは異なる) print メソッドが実行される。コード制作者の「print」と、コード実行時の「print」とは確実に異なる。異なるので「同じ動作をする」という保証はない。保証はないが「だいたい同じ動作をする」という現実的な解を持つという訳だ。

このコードには「厳密さ」はないし、厳密に書くことは不可能だ。コード制作者は、あくまで過去においてコードを制作するので、未来において実行される実行コードを制御はできない。大体、想像して作るだけだし、そこにはとある「約束」や「ルール」があるだけだ。

そういう緩い結合で「後置型プログラム言語」を実装できないかな、と思考実験してみる。文法的に英語の自然言語っぽい形で書ければ(しかし、決して自然言語ではない)なんとなく動くというスタイル。

カテゴリー: 雑談 | 後置型プログラム言語と記号論と生成文法の思考実験 はコメントを受け付けていません

ASP.NET MVC 6 でデータベースに接続(データベースファースト編)

前回の続きで、今度はデータベースから構築するパターン。基本的なところは、ASP.NET5 MVC6 Entity Framework 7 を使って Database First する と同じで、あらかじめ SQL Server で作ったテーブルをもとに ASP.NET MVC の Model クラスを作る。

ASP.NET MVC 6 の Web Application テンプレートを使う

こちらも、Web Application のテンプレートを使う。無駄なページができるのは仕方がないのだが、project.json などの設定がそのまま使えるので活用する。

SSMS(SQL Server Mangement Studio)でテーブルを作成する

ここではローカルPCに SQL Server を入れて testdb に「Movie」というテーブルを作っている。前回のとテーブル名を合わせると比較しやすい、ってのとどうやらテーブルに主キーとIDENTITYが設定していないと Model クラスの作成に失敗するので、元の Movie クラスから引っこ抜いている。

テーブル作成時にスクリプトはこんな感じ。

USE [testdb]
GO

/****** Object:  Table [dbo].[Movie]    Script Date: 2016/02/04 0:31:47 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Movie](
	[ID] [int] IDENTITY(1,1) NOT NULL,
	[Genre] [nvarchar](max) NULL,
	[Price] [decimal](18, 2) NOT NULL,
	[ReleaseDate] [datetime2](7) NOT NULL,
	[Title] [nvarchar](max) NULL,
 CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ちなみに、最近の SSMSは後から列名の変更をしたり、IDENTITYの設定をし直したりできない。昔はできたような気がするのだが、最近の SSMSを使うとエラーになる。SQL Server のスクリプトで直接「alter table …」を使うと変更ができるので SQL Server 側の制限ではないらしい。MySQL の Workbench では自由にできるのでかなり残念。

…と思い続けていたのだけど、SSMSのオプションで [デザイナー] 部の [テーブルの再作成を必要とする変更を保存できないようにする] とできるようになる。これはハッピー。

project.json に EntityFramework.MicrosoftSqlServer.Design を追加する

SQL Server から Model クラスを作るときは「EntityFramework.MicrosoftSqlServer.Design」が必要なので、NuGet から取得しておく。
取得しないまま、「dnx ef …」をすると、きちんとエラーを返してくれるので、それに従ってもよい。

Unable to find design-time provider assembly. Please install the EntityFramework.MicrosoftSqlServer.Design NuGet package and ensure that the package is referenced by the project.

dnx ef で Model クラスを作る

ちょっとややこしいけど、次のように書くと Models フォルダに対象データベース内にあるテーブルの EF を作ってくれる。

dnx ef dbcontext scaffold "Server=.;Database=testdb;Trusted_Connection=True" EntityFramework.MicrosoftSqlServer --outputDir Models

Azure とかにつなぐ場合は(たぶん)接続文字列を変えればよい。出力先は、–outputDir で指定して今回は Models フォルダ内にしてある。

これを動かすと、Visual Studio のほうに EF クラスと接続のための testdbContext クラスを作ってくれる。接続用のクラスは、「データベース名」+「Context」になるようだ。

スキャフォールディングで、Controller と View を作る

Model クラスができたので Controller と View を自動生成させる。
「追加」→「コントローラー」を選択して、「Entity Framework を使用したビューがある~」のほうを選ぶ。

モデルクラスで「Movie」を選んで、データコンテキストクラスでは「testdbContext」のほうを選ぶ。

ちなみに、testdbContext クラスは、元のテンプレートにある ApplicationDbContext と同等の機能を持つデータコンテキストクラス。接続先のデータベースがハードコーディングされているので、デフォルトでここに直結という感じになっている。きちんと手を加えれば、ApplicationDbContext と同じように applications.json から読み込めるようにできるだろう。

public partial class testdbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(@"Server=.;Database=testdb;Trusted_Connection=True");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Movie>(entity =>
        {
            entity.Property(e => e.Price).HasColumnType("decimal");
        });

        modelBuilder.Entity<person>(entity =>
        {
            entity.Property(e => e.name)
                .IsRequired()
                .HasMaxLength(50)
                .HasColumnType("varbinary");
        });
    }

    public virtual DbSet<Movie> Movie { get; set; }
}

データベース接続のために Setup.cs を書き換える

データベースコンテキストを使って接続するので、Setup.cs に AddDbContext なところを書き加える。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

	// ここを追加する
    services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<testdbContext>();

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

実行してみる

動かしてみた結果がこれ。コードファーストのときと同じように CRUD を動かすことができる。

SSMS を使って select してデータ確認もできる。

カテゴリー: ASP.NET | 2件のコメント

ASP.NET MVC 6 でデータベースに接続(コードファースト編)

コードファースト自体は結構前に話題になったハズなのですが、ASP.NET MVC 6 でデータベースに接続する場合にはなんだかよくわからなかったので再調査。いやいや、データベースから POCO を作ることはあまり重要ではなくて、ASP.NET MVC 6 自体なんだけど…まあ、EF(Entity Framework)が扱えたほうがいいので、ちょっときちんと手順を追ってみる。

チュートリアル的にはいろいろあって、

を使って手早く済ますのもよいし、Getting started with ASP.NET MVC 6 ? ASP.NET MVC documentation にチュートリアルがあるので、それに沿って進めてみる。急がば廻れというスタイルです。github のほうは、ちまちま作っている途中らしいので抜けもあるけど、Building your first MVC 6 application からの一連の操作はひと通りできているようです。英語だけど、画面キャプチャが貼ってあるので分かりやすいはずです。

本来やりたいことは

本来やりたいことは、テンプレートを使わないシンプルな ASP.NET MVC を作りたいのだけど、実はなにやら設定がややこしい。一度、Empty のテンプレートから作ってみたもののうまく DB に接続できなかったので、これは後で調べてみる。ポイントとしては、

  • project.json に必要な dependencies を追加する。
  • ApplicationDbContext クラスを用意する。
  • POJO な Model クラスから dnx ef を使ってデータベースを作成する。

するらしい。後で手順を追いなおしてみる。

ASP.NET MVC 6 の Web Application テンプレートを使う

project.json の設定と ApplicationDbContext クラスがややこしいので、Getting started with ASP.NET MVC 6 に従って、テンプレートをそのまま使う。

image

あれこれ無駄なページができるのだけど仕方がない。最終的には Empty から作れるようにする。

image

POJO な Model クラスを作る

Adding a controllerAdding a view はチュートリアル的に流せば ok。ここで必要なのは、Adding a model になる。

手順通り Models/Movie クラスを作る。このあとスキャフォールディングを使って、Movie クラスに対応するコントローラとビューを自動作成する。

「追加」→「コントローラ」を選択して「Entity Frameworkを利用したビューがある…」を選択する。

image

対象となるモデルクラス(Movieクラス)を選択して、データコンテキストでは「ApplicationDbContext」を選ぶ

image

ApplicationDbContext はローカルPCにデータベースを作る方式で、もともと ASP.NET MVC 6 の Web Application に入っているものでユーザー認証とかに使っている。だから、自前で Empty プロジェクトから作るときは、ApplicationDbContext クラスと ApplicationDbContext クラスあたりを自前で作る必要がある(と思う)。

データベースの設定自体は、Startup.cs の ConfigureServices メソッド内に書かれていて、

// Add framework services.
services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

なところで DI 化されている。配布を考えると project.json にあったほうがいいのだが、試験的に使うのであれば直書きしてもいいと思う。

dnx コマンドで Entity Framework を作る

EF を使ったほうが LINQ が使えるようになる(そのためにわざわざコードファーストしている)ので、EF に対応させるのだがデザインでやるのではなくコマンドラインから実行する。ここの部分、最終的に DB から各テーブルを読み込んでという形になるのかコマンドラインのままなのかはわからないけども、Linux 上で ASP.NET MVC + EF + MySQL という組み合わせを考えたときにはコマンドラインでできたほうがよい。この手順は Use data migrations to create the database あたりから書いてある。マイグレーション(migrations)というスタイルで、EF クラスと DB のテーブルとを相互に反映させる仕組みになる。あれこれ書いてあるけど、コマンドラインで project.json のあるフォルダを開けばよい。

dnu restore
dnx ef migrations add Initial
dnx ef database update

dnu や dnx と打ったときにコマンドが見つからないと言われたときには、パスなどを設定するために

dnvm upgrade

するのだが、そもそも dnvm 自体がないといわれるときには、c:¥users¥ユーザ名¥.dnx¥bin にある。このあたりの手順は Installing ASP.NET 5 On Windows にある。Mac 版、Linux 版もあるのでインストール時に一度やっておけばいいだろう。

image

dnvm use 1.0.0-rc1-update1 -p を実行していないのは、dnvm upgrade したときのバージョンをそのまま使うからだ。

そんな訳で、うまく DB が作成されるとデバッグ実行ができるようになる。Create New でデータ登録をしてリストを表示させることも可能。

image

ローカルデータベースは何処にあるのか

できあがった ASP.NET MVC のサンプルを動かすとデータが保持されているようなので、どこかにデータベースがあるはずだ。MVC 5 の場合は、App_Data の中にローカルデータベースがあってそれを参照していたのだが、MVC 6 の場合にはそれがない。

何処にあるのだろうかと調べると Setup.cs の

services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

から、設定の Data:DefaultConnection:ConnectionString から接続文字列を読み取っているらしいことが分かる。

そこで applications.json という設定ファイルらしくものを覗くと

"Data": {
  "DefaultConnection": {
    "ConnectionString": "Server=(localdb)mssqllocaldb;Database=aspnet5-CodeFirst2-848a6bc9-9651-4b68-a5db-8b56f15a8be0;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
},

と書いてある。なんか接続文字列っぽくて、スキーマ名が 「mssqllocaldb」になっている。Working with SQL Server LocalDB にも書いてある。試しに SSMS(Sql Server Management Studio)を使って (localdb)mssqllocaldb を開いてみると、うまく接続できた上にそれらしいデータベースができていることが分かる。

image

データベースのプロパティを見れば、保存場所のわかる。

image

どうやら、c:usersmasuda の直下に置かれている。ああ、確かに豪快に Cドライブに置かれていることが分かります。まあ、これだとユーザー領域とか Cドライブを圧迫しちゃうよねという感じなので、これは別途データベース接続文字列を書き換えて元の SQL Server などに移せばよいでしょう。

image

これがコードファーストなので、コードからデータベースを生成する方法。だが、普通は既存のデータベースを使って EF なクラスを作るような CakePHP の Bake のような方法をとると思うので、これも後で試してみる。

ノーム・チョムスキーとコードファーストな余談

ふと Noam Chomsky: Bernie Sanders has the best policies な記事にあたって、チョムスキーが87歳で健在なことを知る。チョムスキーといえば言語学者で人工知能絡みで対話型の形態素解析とかで出てくるんだけど(最近では人工知能というと自動運転とか深層学習とかになっているので「言語」から離れているが)「哲学者」というよりもレッキとした科学者だ。言語の発生から扱い方はウンベルト・エーコの「ウンベルト・エーコの文体練習」を合わせて読むといい。いちだいブームになった「薔薇の名前」や「フーコーの振り子」も読んでもいいかもしれない。「薔薇の名前」がマスコミ的にもブームになったのは、そういう余裕があった時代ではなかったのかなと思ったり。

で、言語学的に言えばプログラミング言語も「言語」のうちだろう。非常に限られた文法しか持たない(最終的には厳密なアセンブリに変換するという束縛があるので)し、限定的な使われ方しかしないから、普通の自然言語とは違った見方がされる。狭いプログラム言語の中で、C# という言語があって、SQLという言語がある。コードファーストってのは、C# のコードの言語を、SQL という言語(正確にはデータベースの世界)に変換することだ。それを「テクスト」と言い直せば、C# が持つテクストと SQL が持つテクストを結び合わせて同じコンテクストで語ろうという話だ。翻訳の世界と同じになる。当然、日本語と英語のように、というよりも日本国内でも東北弁と鹿児島弁の違いのように単純な変換だけでは済まない。片方のテクスト上で語られるものは、片方のテクストの一語としては対応するものがない。しかし、とあるテクストが全体を網羅を持つという前提条件を付ければ、別のテクストはもうひとつのテクストに対して語数の違いはあるかもしれないが正確に変換できることになる。2つのテクストが存在する空間が、完全に重なりあっていないのであれば変換しえないものが出てくる。これがコードファーストとデータベースファーストとO/Rマッピングのインピーダンスミスマッチだったり、それぞれの違和感だったりする。ただし、「完全に一致する」ことを捨て去ってしまい、ある程度の共有領域を多くするという方針にすれば言語同士の変換は比較的楽になる。

このあたり、Scratch をはじめとする教育用言語も同じだよなーと。「教育用」という縛りを設けるのか、プログラミングをする思考に対する完全性を重視するのか(いわゆる道具立て)というところで、そこに「子どもだから」というプログラム言語的な欠損を最初に認めて作ってしまうと、そもそもが「教育」→「プログラミングができる」の達成すら危うくなってしまうのではないかなーと思ったりもする。だら、あくまで「プログラミング的な思考を養う」のほうがよいのかなと。

教育はさておき、現実的なところで言語で伝えることの限界と、その厳密性を突き詰めていくと、修辞法やら過去の習慣やら小学校での教育やらを含めて、相手とのずれとそれを許容する現実がある。それと同時に、許容できない現実もあることを認める…というか許容しなくても良いというのがある。なんというか、自分だけを中心にして世界が廻っているような人が権力を持つと(どういう経緯なのかよく知らんけど)あれこれ変なところに逝ってしまうんだなーと呆れたりする。まあ、現実的に呆れてもどうしようもないところに落ち着く前に、チョムスキーのように「いい加減にしろよ」ぐらいの釘は刺したほうがいいかもね。半藤一利によれば下り坂の30年間だったりするのだから。

カテゴリー: ASP.NET | 2件のコメント

Xamarin.Froms を使ってパズドラのように駒を動かす

Xamrin.Forms のドラッグシリーズの続き
ひと駒だけドラッグができたので、今度は複数の駒を移動させてみます。いわゆるパズドラのように隣の駒を入れ替えながら動かすというやつですね。

やっていることは前の BoxView のドラッグと同じで移動時のイベントを ManipulationDelta に変換して PCL にコールバックします。

AbsoluteLayoutに配置する

位置がわかりやすくなるように、AbsoluteLayout を使っています。UWP の Canvas に似た動きをしますね。iOS/Android/UWP の3種類でどういう作り方をしているかは知らなくても大丈夫です。Xamarin.Froms の AbsoluteLayout タグに則って配置させます。

/// <summary>
/// 初期化ボタン
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void clickInit(object sender, EventArgs e)
{
    var cols = new List<Color>()
    {
        Color.Red,
        Color.Navy,
        Color.Yellow,
        Color.Pink,
        Color.Purple,
    };
    var rnd = new Random();

    // キャンバスに Box を25個並べる
    boxes = new List<BoxViewEx>();
    for (int i = 0; i < 25; i++) {
        var box = new BoxViewEx();
        boxes.Add(box);
        canvas.Children.Add(box);
        int w = 60;
        int h = 60;

        int x = (i % 5) * (w + 10) + 30;
        int y = (i / 5) * (h + 10) + 30;

        var rect = new Rectangle(new Point(x, y), new Size(w, h));
        box.LayoutTo(rect);
        box.BackgroundColor = cols[ rnd.Next(cols.Count)];
        box.ManipulationDelta += Box_ManipulationDelta;
        box.ManipulationCompleted += Box_ManipulationCompleted;
        box.ManipulationStarted += Box_ManipulationStarted;
    }
}

BoxView のタップイベントは、BoxView 自身につけています。自前の ManipulationDelta のコールバックでは移動している BoxView のオブジェクトそのものを sender として渡すので、このコールバックを AbsoluteLayout に集約してしまっても大丈夫なはずです。

タップ操作イベント

BoxView を移動させて隣の駒の中心付近になったときに駒の入れ替えをします。このあたりは感覚的なものがあるので実地で調節するのと、素早く動かしたときに SingleOrDefault なところでエラーになるので注意が必要です。これは LayoutTo メソッドを使ってアニメーションで駒を移動させているからです。Layout を使ってパッと移動させても良いのですが、ひとまずゲームっぽい感じで。

/// <summary>
/// ボックスの移動開始
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
private void Box_ManipulationStarted(object sender , EventArgs arg2)
{
    var box = sender as BoxViewEx;
    /// 移動開始位置を覚えておく
    boxBlank = box.Bounds;
}
private bool _flag = false;
/// <summary>
/// box の移動中
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Box_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    // box の中心が他のboxに入ったときに入れ替える
    var box1 = sender as BoxViewEx;
    double x1 = box1.Bounds.Left + box1.Bounds.Width / 2;
    double y1 = box1.Bounds.Top + box1.Bounds.Height / 2;

    // 1.中心が他のboxに入っていないか調べる
    try {
        var box2 = boxes
            .Where(x => x != box1)
            .SingleOrDefault<BoxViewEx>(x => x.Bounds.Contains(x1, y1));
        if (box2 == null) return;

        if (_flag == true) return;
        _flag = true;
        // 2.中心に入っていれば、空位置へ移動する
        var b = boxBlank;
        boxBlank = box2.Bounds;
        box2.LayoutTo(b).ContinueWith((_) => { _flag = false; });
    }catch
    {
        return;
    }

}
/// <summary>
/// タップを離したとき
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
private void Box_ManipulationCompleted(object sender, EventArgs arg2)
{
    // 移動中の box は空きに収まるように移動させる
    var box = sender as BoxViewEx;
    box.LayoutTo(boxBlank);
    boxBlank = new Rectangle(); // 空にしておく
}

そんな訳で次は、カードゲームでよくある強化カードを選ぶ、みたいなドラッグサンプルを。

サンプルコード

moonmile/PazzleDrag
https://github.com/moonmile/PazzleDrag

カテゴリー: C#, Xamarin | Xamarin.Froms を使ってパズドラのように駒を動かす はコメントを受け付けていません

追悼 デスマーチ

「デスマーチ」の著者 エドワード・ヨードン氏が亡くなったそうなので、追悼記念に書き下しておく。ちなみに、ツイッターでも書いたけど「デスマーチ」は「終わりなき行軍」を意味するので、現在の短納期(半年ぐらい)のプロジェクトは当てはまらないと思う。そういう場合は、すでに精神が「デス」か「Dead」してしまう場合が多いので別なラベリングが必要だろう。「お前は既に死んでいる」…でもいいけど、意味が違うから別なのを考えよう。

「デスマーチ」を手に取った頃

私が「デスマーチ」を知ったのは、まだ阿佐ヶ谷に住んでいたころだから一人暮らしで寝て起きて渋谷だったか新宿だったかに通っていたころだ。20年前になると思う。DoCoMo のプロジェクトで i-mode の前身を作っていたころだ。いまでこそ i モードを知っている人は少なくないけど(つーか、終わっているから知らない人のほうが多いかも)当時、なんだかわからない2,3年目の新人が、なんだかわからないプロジェクトのまま、なんだかわからない新宿の地下で毎日仕事をしているのは、いま考えればちょっと精神が病みそうな感じだったのだと思う。だが、会社員としてのプログラマをやりつつ、流行りだしたインターネット&ホームページ作りをやりつつということで、仕事と自分の将来を折り合いが付くか付かないかのぎりぎりのところだったと思う。

そういう頃に、プログラミング(当時は VB や C++ が主流)の技術のあれこれとともに、いつ果てるともない仕事の流れに漂っている感じ、朝会社に行く昼を食べる夕方に夜食を少し食べる10時頃に新宿を出る、という毎日を続けていた。合間にプログラミング&テスト&不具合直しを繰り返して、自分の担当の仕様をあれこれ考えてやっていても、多少プログラミングの腕に自信があるぐらいではどうしようもない量の仕事(プロジェクト自体にかかわっているのは、自社で30名弱、他社も入れれば200名ぐらいだろうか)があって、徒労感がひどい状態だった。でも、まあ、こういうのが「仕事」かもしれないと思ってやっていたころだ。

ふと、阿佐ヶ谷の本屋でいつもの技術書コーナーじゃなくて(当時はコンピューターの本はたくさん棚に並んでいたのだ)、なにかちょっと読みものっぽいコーナーを見た。「人月の神話」を買った前後だったか覚えていないのだが、「デスマーチ」という薄い本(日経BPのは分厚いけども、当時はもっと薄かった)があった。日経BPのほうでは挿絵を削られてしまっている(と思う)のだが、当時の本には、行軍のイラストが書いてあった。

ある意味、それだけで私は「デスマーチ」という本は十分だったのである。

見えない現象に名前を付けるということ

タイトルを失念したが、細野不二彦の漫画に「子供に見えない恐怖に名前を付けて克服できるように教える」というシーンがある。もともと現象なり恐怖なりはそこにあるものだけど、それを認識するためにはなんらかの「認識する」行為そのものが必要となる。それが「名前付け」であり、認知学の基本だったりする。現象学を紐解けば、シニフェ/シニフィアンという対応がとられているのがわかる。

いつ終わるともわからないプロジェクト、完成がいつかわからないプロジェクト、毎日忙しいけれどそれがいつ忙しくなくなるのかがわからないプロジェクトが2,3年続けば、それは「デスマーチ」プロジェクトと言ってよい。当時は、プロジェクトの成功率が 30%程度(日本の成功率が高いのは、「失敗したと認めない」からだ)だった。もっと厳密に「成功率」を考えれば、

  • 当初、計画した期間でプロジェクトを終わらせる
  • 当初、計画した予算(人員)でプロジェクトを終わらせる
  • 完成時に、当初の要望を満足できる形で製品が完成される

のが「成功」と言えるだろう。もちろん、もっとゆるゆるにして「ほぼ成功」とか「一部成功」なんてことも言えるけど、きちんと計画駆動(ウォーターフォール)するのだったら、成功の定義ぐらいきちんとして欲しいところだ。まあ、カウボーイプロセスも似た感じではあるけれど。

そういうものを曖昧にしたまま、「仕事」だからというスタイルでずるずるとソフトウェア開発をしていると、それは「デスマーチ」だ。目標もゴールも予算もなにもない。ただ「行軍」だけが目的化してしまった状態だ。

その渦中にいると、なにでこうなっているか分からないのだが、一度「デスマーチ」という名前付けをしてしまうと、一方で「デスマーチではない状態とは何か?」がわかってくる。いくつか「デスマーチではない状態」を列挙してみれば、

  • 納期が決まっている(それが不可能であっても、動かない納期がある)
  • 予算が決まっている。予算がなくなれば、プロジェクト自体が死ぬので、デスマーチも止まる。
  • タスクが決まっている。クリアすべきタスク/機能がわかっていれば、それさえ作ってしまえば、いつでもプロジェクトを終わらせられる。

という状態がある。なので、2000年頃からスタートした Web 開発の場合、短納期であることが多いので「デスマーチ」にならない。その代り「デス」=「即死」してしまうことはある。

ヨードンとデマルコ

実は、エドワード・ヨードン氏は数学者で(だったよね?)数学的なアプローチでソフトウェア開発プロセスを組み直そうとしている。構造化分析とかシステムダイナミクスとかそれだ。一方で、がちがちの管理主義者だったデマルコ氏だが一変して「ピープルウェア」を提唱している。このあたりは(今思えば)両者ともコンサルタントの立場から離れないという感じで、あまりプログラミングそのものには介在しないような感じなのではあるが、「ソフトウェア開発」というもの自体が「営利活動」である間は、経営/雇用/被雇用/外注などの普通の会社と変わらないものが出てくる。そこのあたりはドラッカー言うところの、何処に「顧客を創る」かだ。

おそらく、そのあたりで SAP などの BI(ビジネス・インテリジェンス)ブームに乗ってしまったと思うのだが、この二人がプロジェクト・ナビゲーターを作っていた頃の講演を聞きにいったことがある。

当時の私は、会場で手を挙げて質問をするのが趣味だったので、数学者としてのヨードン氏に質問をした。「TOC(制約理論)を応用した CCPM をどう思われるでしょうか?」。私のほうは TOC にハマっていた頃で、CCPM を自分の会社に応用できればいいかと思っていろいろ考えて、同時に XP をはじめとするアジャイル開発界隈にも顔を出していた頃だった。彼の答えの雰囲気としては、「TOC は非常に素晴らしい/興味ある理論だけども…」という感じだった。まあ、そりゃそうだ。一方で「プロジェクト・ナビゲーター」を売り出していこうとしたところで、TOC/CCPM の話をされたらあまり良い気はしなかったのだろう。

プロジェクト・ナビゲーターの最大の欠点は?

その講演があったのは10年前ぐらいだろうか。アジャイル開発が盛り上がっていた頃でもあって、管理側と開発側の対立が顕著になっていた頃だ。むろん、アジャイル開発をする場合には、スクラムプロセスのように管理と開発が一体にならないとダメなのだが、もともと管理側の人間からすれば「人を管理する」に徹するほうが分かりやすかったりする。少なくtも、管理側の人間からすれば、管理するのが楽なのだ。あたりまえだけどそういう視点がある。このあたりはワインバーグの「ライト、ついてますか」にもあって、「管理者になれ、被管理者になるな」ってのがある。

私が、BI が胡散臭いと思っているのは(今の BI はわからないが、当時の BI は非常に胡散臭かった)、現状だけを眺める、いわば工場のラインを上から眺めるだけの仕事になっているからだ。そのラインで働いている人のことを全然考えていない。というか、そのラインで働いている人を「バカ」だと思っているか、そういう制限された能力しかない(少なくとも、管理者を超える能力や、管理者が想像もできないような能力は思いもよらない)というのが前提になっている。だから、ラインで働いている人は、飛び抜けて出来ても困るし、もちろん飛び抜けてできなくても困る。そういう「ソフトウェア・ファクトリ」(昔の Microsoft のスローガンだ)の視点でしか見ない現象である。以前は、こういう会社を憎んでいた私でははあるが、いやいや今だだと旧態依然でやってくれたほうが相対的に自分に利益になることが分かったので放置である。だって「仲間」じゃないんだもん。

でもって、「ナビゲーター」の話に戻ると、それは株のデイトレーダーと同じように開発プロセスを見ることを目的としている(ヨードンは「デイトレード」という名称を使わなかったけど、絵柄はそっくりだ)。確かに、経営的な視点とかマクロ的な視点とかであればいいのだが、どうもそういう尻馬に乗っているスタイルは私は好きではない。いや、もっと積極的に言えば、尻馬に乗るぐらいだったら、馬自体の能力をもっと引き出したらどうだろうか?ってな具合だ。

「ナビゲーター」は現状を数値化して不都合のありそうなところをフォローするように働きかけることができる。いわば、マイナス部分を削っていけば、プラスだけが残る。少しずつ平均を上回るということだ。私も「マイナス削除」の方法をとることはあるけど、それはミクロ的なレベルだ。けれど、マクロ的にマイナスの削除をするということは、人を部品として切り捨てるということになる。あるいは、人を標準化するということになる。

もちろん、CCPM も含めて「部分最適化」よりも「全体最適化」のほうが効果が大きいことがわかっている。CCPM もプロジェクトナビゲーターも全体を俯瞰するというツールという点では同じだ。進捗状況を、Excel で管理したり、MS Project であれこれとやったり、バーンダウンチャートを作ったりするのも同じだ。全体の俯瞰するためのツールである。が、全体を俯瞰して弱点を見つけたあとに何をするかというのが「プロジェクトナビゲーター」にはない。いや、なかった。弱点を補強するのか、差し替えるのか、または全体をシミュレーションし直すのかという視点がナビゲーターにはない。それは、あくまでも俯瞰するための「図」でしかなくて、ナビゲーションしていない。そうそう、弱点を補強するという形では、ツェッペリが鉄球で馬の癖を大きくする、という方法もあるのだ。

そういう意味でもピープルウェアから外れている感じがするのだが、当時は CMMI ブームでもあったので仕方がないといったところだろう。

再びデスマーチへ?

過去のものが「経験」であるならば、未来に進むためには「予測」や「予知」が必要となる。私たちはソフトウェア界隈の多くの屍(主に大手企業の屍)を乗り越えここに至っている…ような気がするけど、実はそうでもない。豆蔵のボランティアのアレもどうかと思うけど、どちらかといえば、屍になっちゃった人は屍のままで、その臭いを避けて通った人が生き残っている気がする。

かつてデスマーチ撲滅メーリングリスト(でしたっけ、プロセス改善 ML だったっけ?)ってのがあったのだが、廃れてしまった。その第一の理由が、「デスマーチになるような会社を辞めて、転職/独立しちゃった」ので、デスマーチに巻き込まれなくなったんですよね。そうすると、自分のことではなくなってしまうので、あまり興味がなくなっちゃうわけですよ。緊急性が低くなる感じ。

という訳で、某銀行プロジェクトのような大規模デスマーチ予備軍に配属されない限り、あまりデスマーチに出会うことはない。むしろ、冒頭で書いたのだが「デス」の場合が多い。配属 → 即死フラグってのがあまりにもアレなんだけど、そこは要領良く無駄な作業を省くということだろう。超概算見積もりとか、三面図型 CCPM とか、まあ色々避ける手段はある。

[amazonjs asin=”B00F4QOMUO” locale=”JP” title=”デスマーチ 第2版 ソフトウエア開発プロジェクトはなぜ混乱するのか”]

[amazonjs asin=”B00I96CJWO” locale=”JP” title=”ピープルウエア 第3版”]

[amazonjs asin=”4320023684″ locale=”JP” title=”ライト、ついてますか―問題発見の人間学”]

 

カテゴリー: プロジェクト管理 | 追悼 デスマーチ はコメントを受け付けていません

Is TDD Dead ? のその後

今更 TDD なのか?それとも今から TDD なのか…という疑問が出たのは既に2年前なのですが、状況はあまり変わっていないような気がします。

TDD is dead. Long live testing. (DHH)
http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
テスト駆動開発(TDD)はもう終わっているのか? Part 1 | 開発手法・プロジェクト管理 | POSTD
http://postd.cc/is-tdd-dead-part1/
テスト駆動開発(TDD)はもう終わっているのか? Part 2 | 開発手法・プロジェクト管理 | POSTD
http://postd.cc/is-tdd-dead-part-2/
Is TDD dead? – YouTube
https://www.youtube.com/watch?v=z9quxZsLcfo

以前、マーチン・ファウラーが Japan XP User Group のために 2002年頃に来日して話した中に

  • TDD はプログラマに安心を与える。
  • MDA はプログラマを殺さないし、今後も大丈夫だ。

というのがありました。折しも「MDA(Model-Driven Architecture)」が発表されたころで、設計者 Model を作って UML のように繋げ、プログラミングの工数を大幅に減らす。そう、ソフトウェア開発の自動化によって大量にプログラマはいらなくなる(昨今の「ロボットに仕事が奪われる」ブームと同じ状態)がありました。当時から、大手ITのゼネコン体制が問題になっていたし、今でも問題な訳ですが(それは社員も含めて「社蓄」と言われているわけですが)そのあたりも含めての「今後も大丈夫だ」の発言です。ただし、ワタクシ的に言えばマクロ的には後退しているような気がしています(個人的には問題ないんですけどね、それは私の年齢的なものもあるので一般化できません)。

それから XP/TDD と言われて「アジャイル開発」という名前のブームも去りつつあり、そもそもソフトウェアプロセスについてのブームが去ってしまい、VRや人工知能、ロボット等の技術的な側面が全面に出てきています。これはスマートフォンやウェブサイトが一般に広まって零細/個人レベルでの売り出し(技術そのものを売る)ことができるようになったからでしょう。だから、内情がアジャイルであれ、ウォーターフォール式の委託であれ、カウボーイプロセスであれ、できあがった「製品」に対しての評価比がプロセス自体よりも高くなったということです。

そういうところで、Webサイトやスマートフォンのアプリを創るときに「TDD」だけにこだわるのはナンセンスです。

が、一方で業務アプリケーションのように十数年レベルで使われているもの(十数年使われていたもの)やインフラ開発のように障害発生時の損失が非常に大きい場合と、数年で消え去ってしまう Web サイトというか数か月でリリースしないと機会損失が発生してしまう Web サイトやスマートフォンのアプリとは別のアプローチが必要になってきます。それだけ TDD 自体の適用範囲が異なるということでしょう。

ただし、TDD にどのような価値があるかといえば「プログラマに安心を与える」のが最初になります。綿密な設計をし組み合わせを考え障害や例外を考慮したフローチャートを書いた後に、慎重なコーディングをした後に、このコードが期待通り動くかどうかを確認するには「ユーザーと同じような操作を画面から行う必要がある」というのは、心理的な負担が大きいのです。設計による試行錯誤、コードの書き換えによってどんな「変化」がおこってしまうかを、ずっと後の工程となる「テスト工程」ではなくて、その場でロジックとして即座に確認できる、というのが TDD の良いところです。この試行錯誤を手軽に扱える道具が TDD の UnitTest になります。逆に言えば、プログラマの安心のためにプログラマ中心の考え方が TDD にあります。

品質を向上するための手段はいろいろある

逆に言えば、プログラマを中心に据えないスタイルで、品質を向上する手段もあります。

  • プログラムコードを書かない設計書からの自動生成(富士通のアレ)
  • スクラム、CCPM をはじめとする、ソフトウェア開発プロセス/マネージメントからのアプローチ
  • 要求定義/開発というスタイルでの、要求の絞り込み (豆蔵のアレ)
  • ワインバーグの提唱する、要求定義工程からの品質の作りこみ
  • カバレッジやプロファイルツールを使い、テストの網羅率や複雑度からのアプローチ (富士通のアレ)
  • テスト工程での QA、不具合混入原因の調査、標準的な不具合発生率の導入
  • 関数型言語などの数学/証明的アプローチの活用
  • フレームワークを活用して、手動で作成する部分を減らす方法 (EJB, EF, Cake等)
  • UMLによるパターンやオブジェクト粒度などの経験的な手法の導入

TDD も含めて、品質を向上する(いわゆる製品の不具合率を下げる)方法はたくさんあります。どれを使ってもよいし、どのような組み合わせをしてもよいのです。ですが、残念ながら人はそれぞれの立場があり、その立場から得る利得のゆえに偏った「手段」を選ぶことがあります。それは TDD/UnitTest も同じ立場ではあるのですが、どこかに偏ってしまったときには、たいていの場合うまくいかない可能性が高まります。

逆に言えば、うまくいかない可能性を低めるのがこれらの品質マネージメントの目標になります。

品質を悪化させるものを削る

スマートフォンのアプリであれ、業務システムであれ、宇宙開発であれ、ロボット開発のためのソフトウェアであれ、要は「要求通り正しく動けばよい」という基準があります(品質工学の考え方で、管理図からはみ出さなければ良い、匠のように中央値に集約させなくてもよい、という考え方です)。特に素晴らしい品質/性能ではなくても、そこそこの品質を保てば平均以上になるということです(Intel プロセスのアレです)。

悪いものを削っていけば、だんだん良いものだけ残るだろうという発想のもとから、品質を悪化させるものを削っていくスタイルを取ってみます。そうすると、先の品質を向上するものが行き過ぎてしまったときに悪さを減らすことが可能です。

  • コーディングの手間がかかる部分で一定の法則があれば、コードを自動生成してしまう。
  • 要求の変更が少ない場合はウォーターフォールで、試行錯誤が必要であればスパイラルで、要求自体が時間軸で変わる可能性があればアジャイルプロセスを適用する。
  • 実装できない/実現の難しい要求を排除する。
  • 過去に実現できた要求を組み合わせて実現する。
  • TDD とカバレッジを組み合わせてテスト網羅率をほどよく観察する。観察した後は、TDD や設計にフィードバックする。
  • 似たプロジェクトあるいは以前のプロジェクトでの不具合混入率や原因をあらかじめ知っておく。
  • 部分的に強固なライブラリとして関数型言語を利用する。複数のプログラム言語を混在させる。
  • 部品が買えるのであれば、購入する。
  • 似たパターンを探し、パターンに沿って作成/ルール化する。ただし、20% の例外がある。

のように、悪化させる要因を減らしていきます。理論的なアプローチのほうが「美しい」と思われるでしょうが、そういう実現可能なものが「工学的なアプローチ」的に美しいものです。

Is Dead TDD のその後

その後、どうなったかといえば「Is Dead TDD」と検索してもさほど有用なデータは出てきません。使う人は使うし、使わない人は使わないという二極状態のような気がします。

おまけ、構造行列を使う方法

鴨澤さんの https://twitter.com/kamosawa/status/688208924764311557 順列組み合わせ絡みとコンポーネント化のところは、デザイン・ルールの「構造行列」という実例があります。この本はコンピュータのハードウェア設計を例にとって、設計時の情報伝達と手戻りのコストを行列として合わらわしたものです。訳本が 2004年で、原著は 2000年出版ですね。

以前、これをもとにして 30 コンポーネント/機能程度の構造行列を出して開発コスト&手戻りコストを計算したことがあります。あるコンポーネントが他のコンポーネントにどれだけ影響があるかを遷移表として調べて、関連を数え上げるだけです。この関連部分を少なくするように設計/UMLを書けば、各コンポーネントの独立度が高くなるため変更に強い(変更に相互影響が少ない)コンポーネント/モジュールが作れるという訳です。

image

[amazonjs asin=”4492521453″ locale=”JP” title=”デザイン・ルール―モジュール化パワー”]

カテゴリー: 開発, TDD | Is TDD Dead ? のその後 はコメントを受け付けていません

TDD はロストテクノロジー化しているか?

何故か、英語で CppUnit の解説を小一時間することになってしまったので、その後の展開的に残しておく。

使っている人は使っているらしいが

github のコードを見ると大抵のフレームワーク系のライブラリは、 test フォルダがあってテストプロジェクトがある。まあ、実際に自分でやってみると適当な xUnit を書かないと立ち行かない場合が多い(スタート時点の複雑さに追いつかない&後で混乱して修正コストが高くなる)のとで残しておくのと、ライブラリのマニュアル的なものとして残しておくと便利なのでちらっと書いておくとよい。

が、どこでどう間違ってしまったのか、様々な原因で身の回りに実務で TDD をやっている人は少ない…というかいない。これは主に業務アプリを作っている私だからなのか、最近はあまりであることが少なくなったのか分からないが、テストコードを書いている人は少ない。変わりに、従来通りの UI の打鍵テストをやったり、Excel で管理された単体テストや結合テストを繰り返している。

おそらく、XP のプラクティスと業務アプリ開発に取り入れるには「ペアプログラミングはコストが高い」、「UnitTest を継続的に使うにはコストが掛かる」があり、もうひとつ「単体テストをやると、単体テストのバグ票が出ない」かつ「単体テストの項目数が数えられない」というものがある。これは、2000年頃に XP/TDD を導入したときから言われてきたものだ。契約時に工数見積もりをするとき、「単体試験工程」を含ませてる必要があって、単体テストの項目数やバグ数という指標に引きずられてしまうというパターンである。まあ、そういう場合は

  • 単体テスト込みの製造工程/ガントチャートとする。
  • 単体テストの不具合票は出さない。

で押し切るのがベター。なんらかの QA 絡みで押し切られる場合には、不具合票自体をでっち上げるという手段をとる。

そんな訳で TDD は実質あまり広がらずに廃れてしまったものの、本質的に便利なところは便利だとわかっている人が使っている、という状態なのだろう。ちなみに言えば、UnitTest のフレームワークを使うか使わないかで、私的には工数的には倍以上異なる。MVVM パターンの解説のときにもよく言うのだが、

  • 内部ロジック(M-VM)部分をできるだけ多くして、UnitTest を使う。
  • UnitTest が適用しやすいクラス構造に徹する(できるだけモックを使わない)。
  • 適用外の場合は、通常の UI 打鍵で行う。

というスタイルが良い。TDD を適用しようとしたとき失敗するパターンが多いのが、すべての TDD を適用しようとするパターンである。どんな方法でも「適用可能な範囲」があるので、TDD で苦手な部分は多い。特に、ユーザーがいろいろなタイミングで打鍵するパターンは、TDD だけではうまくいかない場合がある(とはいえ、解析的に UnitTest を使うとうまくいくパターンも多い)。そのあたりを含めて、部分的に XP/TDD を取り入れればよいのである。

ちょっと変わった UnitTest フレームワーク

.NET Framework の場合は、Visual Studio で MS 謹製の UnitTest を使ったり、由緒正しい NUnit を使ったりする。Java の場合は JUnit だし、探せばたいていのプログラム言語に UnitTest フレームワークが用意されている。

UnitTest の基本は簡単で、

  1. テストコードを(できるだけ同じ言語で)プログラミングする。
  2. テストコードを何等かの形で実行する。
  3. テストの結果を自動判別する。

の 3 点があれば十分である。以前、Visual Test という GUI のテストを VB を使ってテストするフレームワークがあったがあまりはやらなかった。中身が C++/MFC で書かれていても、VB でテストしない駄目だったからだ。まあ、GUI の自動テストってのが難しいというのあるんだけど。

テストの実行環境は、できるだけ実環境と同じにあわせる。例えば、ガラケーの開発をしていたときには CPU の種類が異なるので、PC のテストとガラケー内のテストではずれが生じる可能性がある。ので、ガラケー内でテストが実行されるようにするのがベター、だがロジック的には PC でも十分だったりする。

結果を自動判別するのは、1,2,3 のサイクルを自動的に回すために必須である。昔 TDD 出る前に Excel で単体試験項目を書いて自動で動かす VBA を組んだことがあったが、結果は目検で行っていた。いま考えれば、結果を VBA で取り込んで Countif あたりで集計すればよかったのである。

そんなわけで、通常の PC あるいはサーバーで動く UnitTest はプログラム言語対応をそのまま使い、PC 以外の場合はちょっとした工夫が必要になる。

CppUnit

CppUnit – C++ port of JUnit – Browse /cppunit/1.12.1 at SourceForge.net
http://sourceforge.net/projects/cppunit/files/cppunit/1.12.1/

2008年で更新が止まっているが、これで十分。Visual C++, g++ で動くのでこれを使い続けている。一時期は更新結果が Green/Red になることが重要だったりしたが、実はそれは些末なところでしかない。本質は、All Success なのか Any Failure なのかにある。失敗したことが確認できて、どこでエラーになったか判別できればそれで十分である。

image

これは内部で FEM を計算するときの歪み関数をテストしているところだ。こういう科学計算の結果チェックに使える。

FsUnit

What is FsUnit?
http://fsprojects.github.io/FsUnit/

内部的には NUnit のラッパーなのだが、テストの書き方がちょっと異なる。たいていの xUnit テストは assertEqulas( 期待値, 実行値 ) という形でテストを書き連ねるのだが、FsUnit の場合は 実行値 |> should 期待値 というテストの書き方になる。期待値と実行値の語順が逆なのは関数型言語特融のカリー化/パイプを使っているため。この語順が便利かというと、実は結構混乱をしていて私の場合、CppUnit や NUnit の語順に慣れているのでよく間違う。

とはいえ、パイプを使うことで通常の assertEqulas 関数とは違った使い方ができる。特にパイプの間にログのフィルタを挟み込めるのは便利だ。バグ解析をするときに

実行値 |> dprint |> should be …

のように、適切な dprint を用意して挟み込めるようにするスタイルができる。

ArduinoUnit

先に書いた通り、組み込みシステムを作っているときは実機で動くような UnitTest を書くのがベストだ。Arduino の場合には Sketch と呼ばれるプログラム(実質、プログラムだ)を書いて動作させる。言語は C++ 風になっているので、ほぼ CppUnit が使える。ただし、 Arduino の sketch 自体が完全に C++ 準拠なのかどうかはわからないので、できれば Arduino 専用の UnitTest を使って、実行は Arduino 上でやったほうがいい。特に、GPIO をリアルタイムで制御するとか、センサーからの値を取得するとかは実機でしかできない(勿論、センサーからの値は毎回異なるので、assertEquals の対象としては使えない。センサーから読み込める/読み込めない、とか BLE 通信をするとか、そういう扱いになる)。

mmurdoch/arduinounit: ArduinoUnit is a unit testing framework for Arduino libraries
https://github.com/mmurdoch/arduinounit

まだ使ってみたことはないが、ひと通り AssertEquals が使えて結果がシリアル通信で送られてくるらしい。BLEやWiFi モジュールの通信系とかを作るときに便利そう。

Xamarin の UI Test App

Xamarin.UITest – Xamarin
https://developer.xamarin.com/guides/testcloud/uitest/
Xamarin.UITests クイック スタート | Xamarin : XLsoft エクセルソフト
http://www.xlsoft.com/jp/products/xamarin/uitest-quickstarts.html

Xamarin Test Cloud が必須っぽいのだが(テスト自体はローカルで実行できる?)、Android/iOS 上でテストをするツールである。これも試したことがないので記事を読む限り、昔の Visual Test に近いスタイルを取っている。UI コントロールに直接代入をする形になる。ただし、テストコード自体が C# であるのと、Xamarin 自体が C# で組む(F#/VB でも可能だけど)ので、テストフレームワークとして同じ言語を使える。スマートフォンのテストに特化しているので、ユーザーからの入力と画面キャプチャができるのがメリットだろう。

内部的には NUnit で動いているようなので、Xamarin.UITest.IApp と ITestServer を実装すればローカルで動かす限り Xamarin Test Cloud は必要ないのではないか?という作りになっている。勿論、Xamarin Test Cloud  自体は Android のいろいろな機種で動くというメリットがあるので、手元にある機種限定になるだろうけど。

Android Studio で UnitTest

AndroidStudioでUnitTestを行う | Anysense Blog
http://blog.anysense.co.jp/app/androidunittest/

こちらのほうは、由緒正しい JUnit を使った Android のテスト。こっちの UnitTest は Android Studio 上で動かすパターンなので UI を介在しない。ロジックなどをテストときに使う。

これも本来ならば、Android 実機上で動かしてログなどの形で結果が通知されるとよい。たぶん、テスト用の画面を作って「テスト実行」のボタンを押すと TestMain が起動されるという形にすればよいと思う。

Swift で UnitTest

Swift 2 + Xcode 7: Unit Testing Access Made Easy!!!!
https://www.natashatherobot.com/swift-2-xcode-7-unit-testing-access/

以前から、Xcode には test プロジェクトを作るか否かのスイッチはあるものの使ったことがない。まあ仕事がら執筆用のサンプルを作ることが多いので単体テストをやること自体が少ないのだが。

これも Android Studio と同じように Xcode 上でテストを実行するパターン。@testable というキーワードを使ってテストクラスを指定する(NUnit は属性を使ってテストクラスを指定する)。これも、実機の iPhone 上で動くことが望ましい。

参考文献

そんな訳で、実行環境が変わるとそれに追随して UnitTest のフレームワークも再作成される。が、もともと UnitTest のパターンは同じなので他言語で作ったテストパターン/知識は流用できる。つまり知識の蓄積ができる分野なのだ。

という訳で、ロスとテクノロジー化していると思われたら再入門し、知らなかったら「損」だよという形でお薦めする。

[amazonjs asin=”4894717115″ locale=”JP” title=”テスト駆動開発入門”]

ちなみに、私が最初に読んだ TDD の本はこれです。

[amazonjs asin=”4756136877″ locale=”JP” title=”Rubyを256倍使うための本 極道編”]

カテゴリー: 開発, TDD | TDD はロストテクノロジー化しているか? はコメントを受け付けていません