特集

Visual Studio 2005
「リファクタリング支援機能」徹底レビュー

株式会社ピーデー 川俣 晶
2006/02/08


■インターフェイスの抽出

 複数のクラスが共通のメソッドなどの機能を持っている場合、それをまとめて扱うと便利である。これを行う最もストレートな(そして実施すべきでない)方法は、共通機能だけを持つクラスを作り、それを継承することである(継承を使うほかに委譲などによって扱うケースもあるが、説明の便宜上ここではそれらは横に置いて先に進める)。しかし、多重継承の機能を持たないC#では、この方法ではうまく機能しないことがある。継承は1つのクラスからしか行うことができないためだ。

 このようなケースではインターフェイスの機能を使って対処することになるのだが、すでに実装済みのメソッドなどの宣言を切り出してインターフェイスの定義を記述するのもけっこう面倒である。

 リファクタリング支援機能の「インターフェイスの抽出」は、これを自動化する。

 例えば、以下のサンプル・コードから、GetFamilyNameメソッドと、GetFirstNameメソッドを持つインターフェイスを抽出してみよう。

using System;

namespace Sample006
{
  public class Author
  {
    public string GetFamilyName()
    {
      return "小泉";
    }
    public string GetFirstName()
    {
      return "八雲";
    }
  }
}
「インターフェイスの抽出」」のためのサンプル・コード
GetFamilyNameメソッドとGetFirstNameメソッドの宣言を持つインターフェイスの定義を生成してみる。

 ここで、クラス名「Author」を右クリックし、[リファクタ]−[インターフェイスの抽出]と選んでみよう。すると、生成されるインターフェイスの名前、それを収めるソース・コードのファイル名、インターフェイスに含めるべきメソッドなどを指定するダイアログが表示される。

[インターフェイスの抽出]ダイアログ
新しいインターフェイスにどのメソッドを含めるかなどを指定する。

 ここでは、[すべて選択]ボタンをクリックしてすべてのメソッドを持つインターフェイスを作成させてみよう。実際に利用する場合は、必要なメソッドにのみチェックを入れて使うことになるだろう。

 すると、このソース・コードは、以下のように修正される。

using System;

namespace Sample006
{
  public class Author : Sample006.IAuthor
  {
    public string GetFamilyName()
    {
      return "小泉";
    }
    public string GetFirstName()
    {
      return "八雲";
    }
  }
}
「インターフェイスの抽出」により書き換えられたコード
Authorクラスがインターフェイスである「IAuthor」を実装している。

 見てのとおり、新しく作成したインターフェイスSample006.IAuthorを実装した形に書き換えられている。

 もう1つ、ダイアログで指定したIAuthor.csというファイルが生成されているが、その内容は以下のようになる。

using System;

namespace Sample006
{
  interface IAuthor
  {
    string GetFamilyName();
    string GetFirstName();
  }
}
「インターフェイスの抽出」により作成されたインターフェイス
インターフェイスの定義は独立したファイルとして生成されるので、ほかのクラスでこれを実装することも容易である。

 このファイルは紛れもなく、すべての文字が自動生成されたもので、一切の修正は加えていない。また、インターフェイスの定義がこのように独立したファイルとして生成されるので、ほかのクラスでこれを実装することも容易だろう。

■ローカル変数をパラメータへ昇格

 リファクタリングには、コードの共通部分を発見してそれを1つにまとめるという作業が多く発生する。その際、あるメソッドにパラメータを追加すれば、ほかの用途にも使えることに気付くこともあるだろう。

 具体的にいえば、ローカル変数をパラメータに置き換えてしまうようなケースもあるだろう。例えば、以下のような円の面積を計算するプログラムがあったとしよう。

using System;

namespace Sample007
{
  class Program
  {
    private static double calc(double r)
    {
      const double pi = 3.14;
      return pi * r * r;
    }

    static void Main(string[] args)
    {
      Console.WriteLine("半径2.0の円の面積は{0}", calc(2.0));
    }
  }
}
「ローカル変数をパラメータへ昇格」のためのサンプル・コード
ここでは変数piをメソッドのパラメータにしてみる。

 ここで円周率が「3.14」の場合だけでなく、「3」の場合の結果も出力させたいと思ったとする。しかし円周率が3の場合の計算メソッドを別途作成するのはあまりに無駄である。定数piをメソッドのパラメータにして、そこに3.14でも3.0でも好きな値を指定可能にすれば、このメソッド1つですべてのケースの計算を行うことが可能となる。

 このようなリファクタリングを行う手順はとても簡単である。「pi」を右クリックし、[リファクタ]−[ローカル変数をパラメータへ昇格]を選ぶだけである。これでソース・コードは以下のように変化する。

using System;

namespace Sample007
{
  class Program
  {
    private static double calc(double r, double pi)
    {

      return pi * r * r;
    }

    static void Main(string[] args)
    {
      Console.WriteLine("半径2.0の円の面積は{0}", calc(2.0, 3.14));
    }
  }
}
「ローカル変数をパラメータへ昇格」により書き換えられたコード
calcメソッドのパラメータとして「double pi」が追加され、3.14という値の記述は、それを呼び出している側に移動している。

 ここで注目していただきたいのは、calcメソッドの呼び出し側「calc(2.0, 3.14)」という部分である。メソッドのパラメータが増えたことに対応して、呼び出し側のパラメータも追加されている。そして、calcメソッド内から消え去った3.14という数値は、ここに挿入されているわけである。その結果、この修正を行った直後にビルド&実行を行っても、修正前と同じ結果が出力されるのである。

 念のためにもう一度強調するが、リファクタリングとは「外部から見たときの振る舞いを保ちつつ内部構造を変化させる」ことである。つまり、同じ結果が出力されることがリファクタリングの正しい結果である。これ以降に行う修正は、外部から見た振る舞いが変わるのでリファクタリングの一部ではない。

 ここまでくれば、円周率3.0の場合を追加するのは簡単である。calc(2.0, 3.0)という式の結果を出力する行を追加するだけでよい。以下はそれを追加した例である。

using System;

namespace Sample007
{
  class Program
  {
    private static double calc(double r, double pi)
    {
      return pi * r * r;
    }

    static void Main(string[] args)
    {
      Console.WriteLine("半径2.0の円の面積は{0}", calc(2.0, 3.14));
      Console.WriteLine("でも円周率3.0で計算すると{0}", calc(2.0, 3.0));
    }
  }
}
別のcalcメソッド呼び出しを追加したコード

■「パラメータの削除」と「パラメータ順序の再変更」

 残されたリファクタリング支援機能は2つ。この2つは相互に関係があるので、まとめて説明しよう。

 メソッドのパラメータというものは、当然ながら必要もないのに追加されることはない。しかし、プログラムの修正が繰り返されるうちに、使われないパラメータが発生することも珍しくはない。そのようなパラメータを「後からまた使うかもしれない」と思って残しておくのはリファクタリング的には誤りである。そういう意味のない盲腸のような存在をソース・コード上に残すと、それを読む際の作業の妨げになるからである。

 もし、削除すると呼び出しコードもすべて正しく修正を要するから削除したくない……というのであれば、この機能を使うとよい。呼び出し側もまとめてパラメータをきれいに削除してくれる。

 以下のサンプル・コードには、使用されていないパラメータを含むメソッドがある。このパラメータを削除してみよう。

using System;

namespace Sample008
{
  class Program
  {
    private static int calc(int x, int y, int z)
    {
      return x * z;
    }
    static void Main(string[] args)
    {
      Console.WriteLine(calc(1, 2, 3));
    }
  }
}
「パラメータの削除」のためのサンプル・コード
calcメソッドの第2パラメータは使用されていないため、これを削除してみる。

 メソッドを宣言している行を右クリックし、[リファクタ]−[パラメータの削除]を選ぶ。すると、どのパラメータを削除するかを指定するダイアログが表示される。

[パラメータの削除]ダイアログ
ここでは削除するパラメータを指定する。

 ここで「int y」を選択して[削除]ボタンをクリックすると、その項目に打ち消し線が表示される。

「int y」を選択して[削除]ボタンをクリックしたところ
int yの上に打ち消し線が表示される。

 ここで[OK]ボタンをクリックすると、リファクタリング結果のプレビューが表示される。

[変更のプレビュー - パラメータの削除]ダイアログ
パラメータ削除後の状態をプレビューすることができる。「int y」のパラメータが削除されているのが分かる。

 [適用]ボタンをクリックすると、リファクタリングの結果がソース・コードに反映される。以下のコードはその結果である。

using System;

namespace Sample008
{
  class Program
  {
    private static int calc(int x, int z)
    {
      return x * z;
    }
    static void Main(string[] args)
    {
      Console.WriteLine(calc(1, 3));
    }
  }
}
「パラメータの削除」により書き換えられたコード
calcメソッド呼び出しの第2パラメータとして指定していた「2」も削除されている。

 さて、「パラメータ順序の再変更」もほとんど同じ手順で実行できる。違うのは、削除するパラメータを指定するダイアログが、パラメータの順序を指定するダイアログに変わるだけである。

[パラメータの順番の再変更]ダイアログ
右側の上・下の矢印ボタンでパラメータの順序を入れ替える。

 もちろん、パラメータの順番を入れ替えると、メソッドの宣言だけでなく、呼び出し側のコードもパラメータの順番が入れ替わる。


 INDEX
  [特集] VS 2005「リファクタリング支援機能」徹底レビュー
    1. リファクタリングとは何か?
    2. 名前の変更
    3. メソッドの抽出/フィールドのカプセル化
  4. インターフェイスの抽出/ローカル変数をパラメータへ昇格/パラメータの削除とパラメータ順序の再変更
    5. 実は不完全なVS 2005のリファクタリング機能
 


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 記事ランキング

本日 月間