特集

.NET開発者のためのリファクタリング入門

株式会社ピーデー 川俣 晶
2004/12/08

Page1 Page2 Page3 Page4

リファクタリング・カタログ

 リファクタリングを単純化して説明してしまえば、ソース・コードを書き換えるためのパターンのカタログを適用する行為である。このカタログを、「リファクタリング・カタログ」という。

−POINT−
 リファクタリングとは書き換えパターンのカタログを適用する行為である

 では、なぜカタログが必要なのだろうか。混乱したソース・コードをシンプルにするというのは、ある程度のコーディング経験があれば、誰でもやったことのある作業といえるだろう。そのような一般的な作業を行うために、なぜカタログが必要なのだろうか。

 その理由は2つある。1つは、プログラマ個人が経験的に得た書き換えパターンよりも、多くの事例を基に、多くの優秀な人々が知恵を集めて成立させたリファクタリング・カタログの方が、はるかによくできているということである。

 ある書き換えによって発生するメリットとデメリットがすでに検討し尽くされているので、使うべきときと使うべきではないときの区別も明確である。

 もう1つの理由は、複数のプログラマが同じカタログを共有して書き換えを行うことで、全体が一貫性のあるソース・コードになることが期待できることにある。これによって、プログラマ個人の好みに偏る可能性が低くなり、ソース・コードのどこを見ても同じように読めることが期待される。もちろん、それはソース・コードに現れる個性が少なくなることを意味するが、個性の否定ではないことに注意が必要である。リファクタリングを行う際には、メリットとデメリットをてんびんに掛けて、書き換え方法に関してどちらともいえない微妙な判断を迫られることは多い。そこで、判断を行うプログラマ個人の個性が反映されることは十分にあり得ることである。

 さらにもう1つ、「オブジェクト指向プログラミングがよく分からない」というプログラマにはうれしいメリットがある。リファクタリングのカタログは、オブジェクト指向プログラム言語が備えるソース・コードをより分かりやすくするための機能を活用するように作られている。その結果、リファクタリングを行うと、オブジェクト指向プログラミングが分かっていなくても、ソース・コードがよりオブジェクト指向プログラミング的に変ぼうしていくのである。リファクタリングという実地体験を通して、「オブジェクト指向プログラミングとはこういうものであったのか」と体感できることもあるだろう。自分が経験的に得た書き換えのパターンではなく、他人が作った書き換えパターンを活用することで得られるメリットといえる。

 さて、リファクタリング・カタログのすべてを紹介することは、この記事内では到底不可能なので、どのような項目があるか、ここではカテゴリの概要だけを紹介しよう。

◆メソッドに関する書き換え

 リファクタリングで最初に手を付けるのにふさわしいのが、メソッド関係の書き換えだろう。これは対象となる範囲が比較的小さく、その効能も見えやすいためだ。

 このカテゴリには、「パラメータへの代入の除去」のような極めて常識的なものが含まれる。これは、メソッドの引数(パラメータ)に値を代入しているようなコードは取り除こう、という書き換えである。結果を伝えるために使われる引数を除けば、引数の役割は、メソッドを呼び出す際に値や参照を使えることであって、メソッド内の処理の都合で書き換えるためのものではない。そのため、そのような引数への代入を行うと、ソース・コードが分かりにくくなる。これは、一度ローカル変数にコピーした後、これを扱うように変更すると混乱を減らすことができる。これは、リファクタリング以前から同じような書き換えを行っているプログラマも多いと思うので、さほど驚くことではなかろう。

 しかし、「パラメータへの代入の除去」のような常識的な書き換えと違って、かなり常識に反しているかのように思える書き換えもこのカテゴリには含まれる。

 例えば、「問い合わせによる一時変数の置き換え」という書き換えは、ローカル変数(一時変数)をメソッドに置き換えるべきであるという、かなり「ええ!?」と驚かされる主張を行っている。それにどのような効能があるかを考える前に、具体的にどのように書き換えるかを見てみよう。

 以下はローカル変数を含んだメソッドの例である。「単価」と「数」から合計金額を計算するが、合計金額が100以上の場合は2割引にするという内容である。

Public Sub Sample001()
  Dim 合計 As Integer = 単価 * 数
  If 合計 >= 100 Then
    Console.WriteLine("割引価格 {0}", 合計 * 0.8)
  Else
    Console.WriteLine("合計価格 {0}", 合計)
  End If
End Sub
リスト3 ローカル変数(合計)を含んだメソッド例

 ここで置き換えるべき対象は、「合計」である。これを変数からメソッドに置き換えてみよう。

Private Function 合計() As Integer
  Return 単価 * 数
End Function

Public Sub Sample002()
  If 合計() >= 100 Then
    Console.WriteLine("割引価格 {0}", 合計() * 0.8)
  Else
    Console.WriteLine("合計価格 {0}", 合計())
  End If
End Sub
リスト4 リスト3のローカル変数「合計」をメソッドに置き換えた例

 この2つを見比べて、どのような感想を持つだろうか。メソッドが増えてかえって分かりにくくなったと思うだろうか。ソース・コードをシンプルにする、という目標に反した書き換えのように見えるだろうか。もちろん、これだけを見れば、そのように感じるのはもっともなことである。しかし、これが長いメソッドの一部の抜粋だったとしたらどうだろうか。

 この書き換えで真に対処すべき対象は、実は長すぎるメソッドなのである。通常、メソッドが過剰に長すぎるのはよくない兆候であるとされる。どの程度の長さが適切であるかは意見が分かれるところだが、いずれの場合でも、極端に長いメソッドはよくないという点で意見は一致するだろう。

 では、長いメソッドを複数の短いメソッドに分割する場合に問題になることは何だろうか。それは、ローカル変数のスコープである。ローカル変数のスコープは、それが記述されたメソッドを超えることはできない。それ故に、メソッドを複数のメソッドに分けようとすると、必要なローカル変数にアクセスできないという問題が発生する。しかし、ローカル変数を(Privateな)メソッドに置き換えれば、そのメソッドはクラス内のどこからでも呼び出し可能となり、分割したいメソッドを容易に分割することができるようになる。

 この書き換えに対しては1つの異論が考えられる。クラスのどこからでもアクセス可能であればよいのなら、メソッドではなくメンバ変数に置き換えてもよいはずだ、というものである。その方が、合計金額の計算回数も減って効率もよいはずである。例えば、以下のような書き換えが考えられる。

Private 合計 As Integer

Public Sub Sample003()
  If 合計 >= 100 Then
    Console.WriteLine("割引価格 {0}", 合計 * 0.8)
  Else
    Console.WriteLine("合計価格 {0}", 合計)
  End If
End Sub
リスト5 リスト3のローカル変数「合計」をメンバ変数に置き換えた例

 しかし、これは賢い選択とはいえない。なぜなら、誰がいつ変数「合計」に適切な値を代入するか、不明確なまま残されるためである。では効率の問題はどうだろうか。『リファクタリング プログラミングの体質改善テクニック』では、メソッド呼び出しに置き換えるオーバーヘッドは「十中八九問題にならない」としている。どうしても効率が問題になる特別なケースを除けば、ローカル変数のメソッドへの置き換え(「問い合わせによる一時変数の置き換え」)は有効であると考えてよいだろう。

◆オブジェクト間の移動に関する書き換え

 このカテゴリは、クラス間でメソッドやメンバ変数を移動させたり、クラスが生まれたり消えたりする書き換えを扱う。

 このカテゴリに含まれるリファクタリングの例として「メソッドの移動」を取り上げよう。このリファクタリングは、「そのクラスの特性よりもほかのクラスの特性の方が、そのメソッドを使うか、そのメソッドに使われることが多い」場合に適用する。なお、ここではメソッドやメンバ変数などのことを特性と呼んでいる(『リファクタリング プログラミングの体質改善テクニック』で使用されている用語である)。

 具体的な例を以下に見てみよう。

Class 請求書
  Public ReadOnly Property 単価() As Integer
    Get
      Return ……
    End Get
  End Property

  Public ReadOnly Property 数() As Integer
    Get
      Return ……
    End Get
  End Property
End Class

Class 請求書出力
  Public Function 価格計算(ByVal 請求 As 請求書) As Integer
    Return 請求.単価 * 請求.数
  End Function

  Public Sub 価格出力()
    Dim 請求 As New 請求書
    Console.WriteLine(価格計算(請求))
  End Sub
End Class
リスト6 価格計算メソッドは請求書出力クラスに所属しているが本当にそれが適切か?

 ちなみに、ここで与えられたクラスやメソッドの名前は意味がないので、それは意識しないでソース・コードを見てみよう。この中の価格計算メソッドは、請求書出力クラスに所属しているが、本当にそれが適切なのだろうか。「メソッドの移動」の条件によって検討してみよう。

 この価格計算メソッドは、このクラスのほかのメソッドより1回呼び出されている。つまり、請求書出力クラスにおいて「そのメソッドを使うか、そのメソッドに使われる回数」は1回である。

 それに対して、このメソッドは請求書クラスを2回利用している。「請求.単価 * 請求.数」という式の中に、請求書クラスのプロパティの参照が2カ所含まれているためである。それ故に、「請求書出力クラスの特性よりも請求書クラスの特性の方が、メソッド価格計算を使うか、メソッド価格計算に使われることが多い」という条件を満たし、「メソッドの移動」を行う価値が生じる。

 実際に移動させた結果を以下に示す。

Class 請求書
  Public ReadOnly Property 単価() As Integer
    Get
      Return ……
    End Get
  End Property

  Public ReadOnly Property 数() As Integer
    Get
      Return ……
    End Get
  End Property

  Public Function 価格計算() As Integer
    Return 単価 * 数
  End Function
End Class

Class 請求書出力
  Public Sub 価格出力()
    Dim 請求 As New 請求書
    Console.WriteLine(請求.価格計算())
  End Sub
End Class
リスト7 リスト6の価格計算メソッドを請求書クラスに移動

 この書き換えの結果、価格計算メソッドがシンプルになったことに注目しよう。「ByVal 請求 As 請求書」という引数は消滅し、数式も「請求.単価 * 請求.数」から「単価 * 数」へと単純化された。一方、このメソッドを呼び出す側の複雑さはほとんど増していないことも確認しておこう。「価格計算(請求)」が、「請求.価格計算()」に変化しただけである。

 この程度の改善を多数蓄積するだけでもソース・コードは分かりやすくなるのだが、この先、この書き換えを起点にして、もっとほかの改善をなし得る可能性も出てくる。

 例えば、もしもプロパティ「単価」とプロパティ「数」を外部から参照しているものが価格計算メソッドだけであったとすれば、このメソッドの移動によって、この2つのプロパティに「Public」と付ける必要性が消失する。そのまま「Private」と書き直したり、場合によっては単なるメンバ変数でよいということになり、プロパティそのものが消滅したりするかもしれない。

 このような書き換えは、このメソッドを移動させるだけではできなくても、ほかのメソッドも同様に移動させると可能になるかもしれない。そうなれば、請求書クラスそのものを大幅にシンプル化できる可能性も出てくる。ともかくPublicと付くものは減らせば減らすほどソース・コードの分かりやすさが向上するので(アクセスできる範囲が限定されるので)、このような書き換えの可能性は追求する価値がある。


 INDEX
  [特集] プログラミングの生産性をアップするリファクタリング入門
    1.リファクタリングの目的
    2.何のためにソース・コードを書き換えるのか?
  3.リファクタリング・カタログ(1)
    4.リファクタリング・カタログ(2)/リファクタリングとツール
 


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

本日 月間