特集

.NET開発者のための開発プロセス入門(後編)

.NET開発でアジャイルを導入するための実践テクニック

福井 厚 & 小井土 亨
2004/12/22
Page1 Page2 Page3 Page4

■Mockオブジェクトの利用

 Mock(模造品)オブジェクトとは、その名のとおり利用する側からは実際の処理を行うオブジェクトと同じように扱うことができるが、その実際の処理は行わないオブジェクトのことである。

 サブシステムに分割してシステムを開発する場合、ほかのサブシステムに変更が発生したときに、その変更の影響を受けて修正すべき個所が多くなると、それに比例して工数も増大してしまう。そこで、ほかのサブシステムを利用する個所を1つのクラスに集約する(デザインパターンのファサード・パターン)。そして外部との接続には、必ずこのファサード(玄関)と呼ばれるクラスを経由することを義務付ける。このことにより、ほかのサブシステムに変更が発生しても、局所的な修正で対応できる可能性が高くなる。

 ただし、これだけではまだほかのサブシステムへの依存度が低いとはいえない。具体的には、ほかのサブシステムが提供されないと動作しない部分があるといった問題や、ほかのサブシステムの品質が低くてテストを行うことができない、などという問題が発生してしまうためだ。

 そこで登場する有効な方法が、Mockオブジェクトの導入である。Mockオブジェクトを導入すれば、ほかのサブシステムの品質に依存することなく開発を行うことが可能となる。

 ほかのサブシステムを利用する処理のテストを行う場合に、実際のサブシステムを利用すると、非常に多くの工数が必要になってしまう可能性が高い。例えば、例外のテストを行う場合、状況を作ることは難しいことがほとんどである。しかし、Mockオブジェクトを使えば、自由自在に例外を発生させることができる。また、ハードウェアやデータベースに依存する場合などでも、Mockオブジェクトは非常に有効に機能する。

 Mockオブジェクトを導入するときには、NMockというフリーのツールがお勧めである。次のコードはNMockを使った簡単なテストの例である。

[TestFixture]
public class testStock
{
  IMock mock;
  IStockDao dao;

  [SetUp]
  public void SetUp()
  {
    mock = new DynamicMock(typeof(IStockDao));
    Stock retValue = new Stock("1001", 10);
    mock.ExpectAndReturn("GetStock", retValue, new IsEqual("1001"));
    mock.ExpectAndReturn("GetStock", null, new NotEqual("1001"));
    dao = (IStockDao)mock.MockInstance;
  }

  [Test]
  public void testStockDao()
  {
    Stock ret = dao.GetStock("1001");
    Assert.AreEqual("1001", ret.ProductId, "商品ID");
    Assert.AreEqual(10, ret.Count, "在庫数");

    ret = dao.GetStock("9999");
    Assert.AreEqual(null, ret);
    mock.Verify();
  }
}
NMockを利用したコード例
データアクセス部分をMockに置き換えている。

 なお、Mockオブジェクトを導入する場合、前準備としてファサードをクラスではなくインターフェイスとして定義し、さらにインスタンスを生成する部分を外部に切り出すことが必要になる。

 上記のコード例では、IStockDaoインターフェイスを継承した実際のクラスを呼び出す代わりにMockオブジェクトを使っている。Mockオブジェクトは実際のクラスの振る舞いを模倣するように、商品IDに1001を指定してGetStockメソッドが呼び出されたときには適切なStockクラスのインスタンスを返し、それ以外の商品IDが指定された場合にはnullを返すようにしている。

■リファクタリング

 単純に、システムに対して変更を繰り返していると、プログラム・コードは、いわゆる「スパゲティ」と呼ばれる状態になってしまう。反対に、変更を予測して一部のコードを共通化しておいた場合、勘が当たればよい結果を生むことはできるが、1カ所からしか呼ばれないコードが存在するなど、無意味に複雑なプログラムになってしまう。また、これは工数が無駄になるというリスクを生むことにもなる。実際、どんな優秀なプログラマでも、具体的な変化が明白でない場合に、完ぺきに変化に対応できるプログラムを作成することは不可能である。

 どちらにしても、いままでのやり方では、変化に対する作業コストは、時間の経過とともに指数関数的に上昇して、最後には不安定に積み上げられた積み木のようなシステムになってしまう。そして、お決まりのセリフである「機能追加をするくらいなら、作り直した方が早い」ということになってしまう。

 変化を繰り返しても品質を落とさないための、有効なプラクティスにリファクタリングがある。リファクタリングは、追加しなければならない機能を実装する前に、その機能が最初からあったと想定した状態にシステムを整形することである。このとき重要なのは、新しい機能を追加する前に、まずリファクタリングを行っておくことである。これは、機能追加とリファクタリングを同時に行うと、システムが動作しなくなった場合に、どちらが原因か分からなくなるためだ。

 例えば、新しい機能を追加するに当たって、すでにあるメソッドと処理内容が少しだけ異なったメソッドが必要になったとする。よくある解決方法は、ソース・コードをコピー&ペーストして、メソッド名を少しだけ変えて別のメソッドを作るというものである。これでは同じようなコードが複数存在することになり、機能追加以降のシステムに対する変更コストは増大することになる。

 別の解決方法としては、既存のメソッドにパラメータの異なる部分だけを追加するというものである。しかし、この方法を採用する場合、修正したメソッドを呼び出している既存コードをすべて修正する必要がある。これを行うにはビルド・エラーを基に変更すべき個所を特定し、手作業で修正するのが一般的な方法である。結果としてコードはよい状態に保たれるが、一時的な作業工数はコピー&ペーストより増えてしまう。

 次期Visual Studio 2005ではいくつかのリファクタリング機能が用意されており、C#に限定されるが、このようなメソッドへのパラメータ追加作業を論理的に判断して行うことができる。これは単純な置換と違い、言語上の論理的な意味を理解して処理を行う。具体的には、Visual Studio 2005の「ローカル変数をパラメータに昇格」を使って実現できるので、その簡単な例を紹介しよう。

 以下のようなソース・コードにおいて、AddPointメソッドに新しいパラメータを追加したいとしよう。

public class Customer
{
  private int point=0;
  public void AddPoint(int newPoint)
  {
    point += newPoint;
  }
  public void Buy(int price)
  {
    AddPoint(price/100);
  }
}
リファクタリング前のソース・コード

●ステップ1

 まず初めにコンパイルを行い、ビルド・エラーが発生しないことを確認しておく。次に、メソッドに追加したいパラメータをローカル変数として定義する。また、初期値を設定する。

public void AddPoint(int newPoint)
{
  int rate = 1;    // ローカル変数を追加
  point += newPoint * rate;
}
「ローカル変数をパラメータに昇格」の準備
追加したいパラメータ(この例では、int型の変数「rate」)を自動変数として定義する。

●ステップ2

 メニューから[リファクタリング]−[ローカル変数をパラメータに昇格]を選択すると、コードは以下のように変化する。

public class Customer
{
  private int point=0;
  public void AddPoint(int rate, int newPoint)
  {
    point += newPoint * rate;
  }
  public void Buy(int price)
  {
    AddPoint(1, price/100);
  }
}
リファクタリングを実行した後のソース・コード

 メソッドを呼び出す側のコードも自動的に修正されているのが分かるだろう。

 リファクタリングは、リファクタリングの前と後でプログラムがまったく同じように動作することを基本コンセプトとしている。このコンセプトによって、日常的にリファクタリングを行う勇気がわいてくる。さらに、Visual SourceSafeを併せて利用すれば、確実に元に戻すことができるので勇気がなくてもリファクタリングは行えるようになるので、お勧めである。今回紹介したリファクタリング機能の「ローカル変数をパラメータに昇格」は、本来は内部で使用していたローカル変数を外部から指定できるようにするためのものである。

 Visual Studio 2005では、今回紹介した機能以外に、以下のリファクタリング機能が提供される予定となっている。

機能 説明
メソッドの展開 プログラムの一部をメソッドとして切り出す
名前の変更 クラス名やメソッド名を変更する
フィールドの要約 private変数をプロパティとして公開する
インターフェイスの展開 クラスのpublicなメソッドなどをインターフェイスとして切り出す
パラメータの削除 パラメータを削除する
パラメータの順序の再変更 パラメータの順序を入れ替える
Visual Studio 2005で提供予定のリファクタリング機能

 1人でのアジャイル開発プロセスの導入でソフトウェア品質の向上に成功したら、次は数名のチームで導入できるローカル・ライトウェイト開発プロセスに挑戦してほしい。チームで開発プロセスを導入するときの目的は、ずばり「要求の変化に素早く対応するために」である。

 次に、その数名チームで導入できるローカル・ライトウェイト開発プロセスについて解説しよう。


 INDEX
  [特集] .NET開発者のための開発プロセス入門(前編)
    1.アジャイル開発プロセスが誕生するまで
    2.アジャイル型開発プロセスとは何か?
    3..NET開発者がアジャイル開発プロセスを導入できない理由
    4.アジャイル開発プロセスの導入
  [特集] .NET開発者のための開発プロセス入門(後編)
    1.1人からでも導入可能なローカル・ライトウェイト開発プロセス
    2.単体テストの自動化とテスト・ファースト
  3.Mockオブジェクトとリファクタリング
    4.数名のチームで導入できるローカル・ライトウェイト開発プロセス
 


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間