特集

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

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


■メソッドの抽出

 「メソッドの抽出」とは、メソッド内の一部のコードを抜き出して、それを別のメソッドにして呼び出すように修正するリファクタリングである。といっても、このような作業はリファクタリングと呼ぶまでもなく、常識的に行われてきた作業だ。メソッドが長くなりすぎたら分割するというのは、古今東西を問わず常識的な開発手順の1つだろう。

 では、さっそく試してみよう。ここでは以下のソース・コードのforループを、別のメソッドとして抽出してみよう。

using System;

namespace Sample002
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Starting...");
      for (int i = 0; i < 10; i++)
      {
        Console.WriteLine(i);
      }
      Console.WriteLine("Ending...");
    }
  }
}
「メソッドの抽出」のためのサンプル・コード
ここではforループ部分を別のメソッドにしてみる。

 forループを構成する4行をマウスで選択し、右クリックを行う。そして、[リファクタ]−[メソッドの抽出]と選んでみよう。

「メソッドの抽出」を行う部分の選択
forループの範囲を選択する。そしてその部分を右クリックして[リファクタ]−[メソッドの抽出]を実行する。

 これにより[メソッドの抽出]ダイアログが表示される。

[メソッドの抽出]ダイアログ
抽出するメソッドの名前を指定する。

 ここで、新しいメソッドの名前を「writeCounts」と入力してみよう。すると、以下のようなコードが得られる。

using System;

namespace Sample002
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Starting...");
      writeCounts();
      Console.WriteLine("Ending...");
    }

    private static void writeCounts()
    {
      for (int i = 0; i < 10; i++)
      {
        Console.WriteLine(i);
      }
    }
  }
}
「メソッドの抽出」により書き換えられたコード
forループの部分をwriteCountsメソッドとして「メソッドの抽出」を行った結果。

 見てのとおり、新しいメソッドが作り出されているばかりか、そのメソッドを呼び出すコードまで元のメソッドには挿入されている、もちろん、このコードはそのまますぐに実行できる。ソース・コードの分かりやすさを損なう長すぎるメソッドは、この機能で気軽に分割してメンテナンス性を高めよう!

 さて、ここで変数が絡んだらどうなるのかと気になった読者もいるだろう。もちろん、変数が絡んでも問題はない。以下のコードで、「count += i;」の1行だけを選択して、これをメソッドに抽出してみよう。

using System;

namespace Sample003
{
  class Program
  {
    static void Main(string[] args)
    {
      int count = 0;
      for (int i = 0; i < 10; i++)
      {
        count += i;
      }
      Console.WriteLine(count);
    }
  }
}
「メソッドの抽出」のためのサンプル・コード
ここでは「count += i;」の1行だけを選択して、これをメソッドに抽出してみる。

 これはかなり意地悪な事例だが、以下のようにきれいにメソッドとして抽出されている。これを行うために指定したのは、「calcIt」というメソッド名だけである。

using System;

namespace Sample003
{
  class Program
  {
    static void Main(string[] args)
    {
      int count = 0;
      for (int i = 0; i < 10; i++)
      {
        count = calcIt(count, i);
      }
      Console.WriteLine(count);
    }

    private static int calcIt(int count, int i)
    {
      count += i;
      return count;
    }
  }
}
「メソッドの抽出」により書き換えられたコード
calcItメソッドが抽出したメソッドである。メソッド名のみを指定しただけでこのようなメソッドを自動作成できる。

 ここでは、新しいメソッドに2つのパラメータと戻り値が存在しているが、これらはすべてリファクタリング支援機能が自動的に作り出したもので、プログラマーが指定する必要はない。ちなみに、メソッドが静的であることを示すために使われるstaticキーワードも自動的に挿入される。そのクラスのほかのメンバを参照していないメソッドには自動的にstaticキーワードが付加される。

 では戻す値が2つあったらどうなるのだろうか? 以下のコードに対して、「count1 += i;」と「count2 *= 2;」という2行を選択してメソッドを抽出してみよう。

using System;

namespace Sample004
{
  class Program
  {
    static void Main(string[] args)
    {
      int count1 = 0, count2 = 1;
      for (int i = 0; i < 10; i++)
      {
        count1 += i;
        count2 *= 2;
      }
      Console.WriteLine("{0},{1}", count1, count2);
    }
  }
}
「メソッドの抽出」のためのサンプル・コード
「count1 += i;」と「count2 *= 2;」という2行を選択してメソッドを抽出してみる。

 これに対しては、refキーワード(参照渡し)を活用した、以下のようなコードを生成してくれる。パーフェクトである。

using System;

namespace Sample004
{
  class Program
  {
    static void Main(string[] args)
    {
      int count1 = 0, count2 = 1;
      for (int i = 0; i < 10; i++)
      {
        calcIt(ref count1, ref count2, i);
      }
      Console.WriteLine("{0},{1}", count1, count2);
    }

    private static void calcIt(ref int count1, ref int count2, int i)
    {
      count1 += i;
      count2 *= 2;
    }
  }
}
「メソッドの抽出」により書き換えられたコード
抽出により自動生成されたcalcItメソッドに2つの値を返す必要がある。このような場合には、参照渡しを行うためのrefキーワードを用いたメソッドのパラメータが使用される。

 しかし、残念ながらこの機能にも正しく対処できないケースが存在する。

 例えば、制約(whereキーワードにより指定)のないジェネリック型のパラメータを持つメソッドを抽出する場合、生成されるコードでは、そのパラメータに値が割り当てられない限り、refキーワードが追加されない。また、ローカル変数への参照を含む匿名メソッドについて、そのローカル変数が匿名メソッドの外部で宣言または参照されている場合に、その匿名メソッドの一部を抽出しようとすると、ローカル変数の値が匿名メソッドに渡されるタイミングが異なってしまう。

 これらのケースでは、正しく動作するかの検証や、動作させるための修正を手動書き換えで行う必要がある。

■フィールドのカプセル化

 「フィールドはpublicに指定してはいけない、必ずアクセサ(setメソッドやgetメソッド)経由でアクセスするように修正せよ……」というのは実はリファクタリングというよりはオブジェクト指向プログラミングの常識である。

 といっても、このルールを厳格に守る必要があるのはJavaなどのアクセサを経由させるとソース・コードの大規模な修正を必要とするプログラム言語の話であって、プロパティという強力なツールを持っているC#では、アクセサが必要とされてからフィールドをプロパティ(アクセサ)に置き換えてもよいだろう。しかし、それはアクセサを付けるタイミングを遅延できるというだけの話であって、アクセサ不要という意味ではない。やはりC#でもアクセサは必要とされることがある。

 さて、リファクタリング支援機能は、フィールドにアクセサを付ける(つまりカプセル化する)という手順もサポートする。例えば、以下のクラスのCounterフィールドをカプセル化してみよう。

public class SampleClass
{
  public int Counter;
}
「フィールドのカプセル化」のためのサンプル・コード
Counterフィールドをプロパティにカプセル化してみる。

 ここで、Counterキーワードを右クリックし、そして、[リファクタ]−[フィールドのカプセル化]を選んでみよう。すると以下のようなダイアログが開く。

[フィールドのカプセル化]ダイアログ
フィールドをカプセル化するプロパティの名前を指定する

 プロパティ名を指定して[OK]ボタンを押すと以下のようなプレビューが表示される。

[参照の変更のプレビュー - フィールドのカプセル化]ダイアログ
「フィールドのカプセル化」を実行した場合に表示されるプレビュー。ここではプロパティが生成されていないように見えるが、実際には生成される。なお、先のサンプル・コードには掲載されていないコードの修正についても表示されていることに注意。

 ここで、2つの注意点がある。1つは、プレビューにはプロパティが表示されないということである。ここで確認すべきは、フィールドの参照が、すべて新しく指定されたプロパティ名に置き換えられることである。ここで[適用]ボタンをクリックすると、ソース・コードが修正され、プロパティが書き込まれる。

 もう1つの注意点は、フィールドのアクセス修飾子はpublicからprivateに自動的に変更されるが、元のフィールドの名前は変更されないということである。その結果、privateなら名前の先頭は小文字だがpublicなら大文字というようなネーミング・ルールで運用していると、このルールから逸脱してしまうことになる。これを解決するには、「名前の変更」を別途適用する必要がある。

 実際に生成されたコードは以下のようになる。

public class SampleClass
{
  private int Counter;

  public int PublicCounter
  {
    get { return Counter; }
    set { Counter = value; }
  }
}
「フィールドのカプセル化」により書き換えられたコード
getアクセサとsetアクセサの機能を含むプロパティが自動生成されている。

 以上のように、getアクセサとsetアクセサの機能を含むプロパティが自動的に生成されたわけである。

 ちなみに、readonlyキーワードが付加されたフィールドは、setアクセサなしでgetアクセサだけ生成するという気の利いた機能も持っている。


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

本日 月間