前編 リファクタリングとVisual Studio:特集:Visual Studioで始めるリファクタリング(1/3 ページ)
リファクタリングとは何か、リファクタリングを効率的かつ安全に行うためにVisual Studio 2017が提供している支援機能の概要を見ていこう。
最近のVisual Studioは、バージョンアップごとにリファクタリングのサポート機能も強化してきている。もはや「知らないと損」といって差し支えないだろう。そこで、本特集では前後編にわたってリファクタリングとVisual Studio 2017(以降、VS 2017)のサポート機能について解説していく。
- 前編: リファクタリングの概要とツールを使ったリファクタリング
- 後編: Visual Studio 2017のリファクタリングのサポート機能
なお、プログラミング言語はC#で解説していく。Visual Studioのリファクタリングサポート機能の範囲は言語によって差があり、C#が最も広いからだ(Visual Basicも、ほぼ同等のリファクタリングサポート機能を持っている)。
今回は、リファクタリングとは何なのかをおさらいし、リファクタリングにVisual Studioなどのツールを使う意義を考えてみよう。そして最後には、実際に簡単なリファクタリングを試してみる。
リファクタリングとは?
「リファクタリングとはソースコードを書き換えることである」という回答は、残念ながら正解とは言えない。その意味には「リライティング」(rewriting)という呼び方がふさわしいだろう。
リファクタリングとは、「ソースコードを、その外的な振る舞いを変えることなく、きれいなメンテナンスしやすいコードに書き換えること」である。
リファクタリングを世に知らしめたマーチン・ファウラー氏の定義は、次のようだ。
「リファクタリングとは、ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改善していく作業を指します」
※新装版「リファクタリング」(ISBN978-4274050190、2014年発行)の「はじめに」より
docs.microsoft.com(旧MSDN)のWebサイトでも、以下のように同様な定義をしている。
「Refactoring is the process of modifying code in order to make it easier to maintain, understand, and extend, but without changing its behavior.」
(試訳:「リファクタリングとは、保守しやすく、理解しやすく、拡張しやすいコードにするためにそれを変更するプロセスですが、その振る舞いを変更しません」)
※「Refactoring, Code Generation and Quick Actions in Visual Studio」(2017年3月23日付)より
リファクタリングとは、コードを単に書き換えるだけでなく、そこに条件が2つ付いているのだ。
- コードの外的な振る舞いは変えないこと
- リファクタリング後はよりメンテナンスしやすいコードになっていること
従って、バグ修正や機能追加を伴う書き換えは、リファクタリングではない。また、メンテナンスのしやすさを犠牲にして高速化を図る書き換えも、リファクタリングではないのである。
リファクタリングとは何かについては、本稿では定義を示すにとどめる。その詳細やリファクタリングのサポートツールに頼らない具体的な手法については、上に引用したマーチン・ファウラー氏の「リファクタリング」(以降、「リファクタリング本」)を読んでいただくのが一番だ。また、少々古い記事だが「特集:.NET開発者のためのリファクタリング入門」も参照していただきたい。
リファクタリングの成功基準
上の定義で挙げた2つの条件は、成功基準と言い換えることもできる。リファクタリング作業が終わったときに2つの条件を共に満たしていれば、リファクタリングに成功したといえるからだ。
この2つの成功基準を満たしたかどうかは、どのように判定したらよいのだろうか?
コードの外的な振る舞いが変わっていないこと
外的な振る舞い(外部設計と言い換えてもよい)が変わっていないことは、テストによって確認できる。
言うのは簡単だが、しかし、大きなハードルが立ちはだかっている。次の2点だ。
- 「正しい」外部設計がドキュメント化されていること
- テストを実施する手間
ここで「正しい」とかっこ書きにしたのは、要件定義などに照らして正しいという意味ではなくて、リファクタリング前のコードと一致しているという意味である。その「正しい」外部設計がなくては、リファクタリング後にいくらテストをしてみても、外的な振る舞いに変化があったかどうかは分からない。
とはいうものの、アプリ全体の振る舞いを細大漏らさず完璧に記述した外部設計書には、筆者はお目にかかったことがない(恐らく実務的に不可能だと思う)。また、仮にそのようなものがあったとしても、リファクタリングした部分の振る舞いの不変性を確かめるためにどれだけの範囲をテストすればよいかも分からないだろう(1行のリファクタリングのためにアプリ全体のフルテストが必要だろうか)。リファクタリングの影響が及び得る範囲に限定した外部設計があればよいのだ。
リファクタリングの影響が及び得る範囲に限定した外部設計とは、リファクタリングの規模によるが、多くはメソッドのレベルかクラスのレベルだろう。その粒度の「正しい」外部設計書が用意されていればよいのだが、しかしこれまた、メソッドやクラスのレベルで外部設計書を書いていることはまずないだろう。
その解決策となるのが、「テスト駆動開発(TDD)」である。テストコードから書き始めることで、コードが完成したときにはその外部設計に対する自動テストも出来上がっているのだ(ただし、現状はUIを除いたロジック部分だけとするのが現実的)。この自動テストはコードの外的振る舞いを「正しく」表現できている。TDDで作られた自動テストは「正しい」外部設計なのである。
TDDによって、リファクタリング前に「正しい」外部設計(=自動テスト)が得られ、リファクタリング後に再びその自動テストを実施すれば、コードの振る舞いが変わっていないと確信できる。また、2つ目の課題であるテストを実施する手間も、自動テストならばないに等しい(さらにVS 2017 Enterpriseエディションでは、Live Unit TestingをONにしておくとバックグラウンドで自動的にテストを実行してくれるので、全く手間いらずだ)。
なお、UI部分をTDDで作るのは、現状では不可能ではないがとても困難である。UI部分に関しては、まだ外部設計書のメンテナンスと手動テストの方が有利であろう。ただし、UI部分のコードが一通り完成し、その外部設計が完全にフィックスできた後ならば、「コード化されたUIテスト」を作成するとよい。その自動化テストは、やはり「正しい」外部設計であり、テストを実施する手間も大幅に削減されるからだ。
さて、全てのリファクタリングには自動テスト(またはUIの「正しい」外部設計書と手動テスト)が必須なのかというと、そうでもない。テストをしなくてもコードの外的な振る舞いが変わっていないことを確信できればよいのだ。それが本稿の主題である。
よりメンテナンスしやすいコードになっていること
メンテナンスしやすさのうち、名前やコメントの的確さなどは主観によるしかない。それ以外では、自動的に計測して数値化できる部分もある。
名前についてはここで言うまでもないだろうが、不適切に名付けられているとコードが理解しにくくなる。合計を求める「CalcAve」メソッドなどが不適切な例だ(「CalcSum」などとすべき)。理解しにくいということは、メンテナンスしにくいことにつながる。理解しやすさの面からは、業務に固有な名詞や動詞などには日本語を使うのも一案だ。また、コメントも同様に、コードと異なる説明が書いてあったりすると理解の妨げになる。
計測は、さまざまな指標(および、その測定方法)が提案されているが、いずれも手作業で勘定するのは大変だ。ここはツールに頼ることにしよう。VS 2017では、次の5つの指標が計測できる(「コードメトリックス」という)。
- 保守容易性指数(Maintainability Index)
- サイクロマティック複雑度(Cyclomatic Complexity)
- 継承の深さ(Depth of Inheritance)
- クラス結合(Class Coupling)
- コード行(Lines of Code)
各指標の意味は、MSDNの「コード メトリックス値」をご覧いただきたい(この文書は少々古いのだが、本稿執筆時点ではdocs.microsoftにある最新版は未翻訳だった)。
リファクタリングにおいては、この中の保守容易性指数を参考とするとよいだろう(次の画像)。ただし、例えば、コードを読みやすくするための説明用変数の導入(Extract Variable)を行うと、(変数が増えたために)保守容易性指数が下がってしまうこともある 。そのような保守容易性指数の特徴を把握した上で、想定外に保守容易性指数が下がってしまったら、リファクタリングに失敗したと判断してよいだろう。もちろん、保守容易性指数が大きく上がったのなら、リファクタリングに成功したと太鼓判を押せる。
コードメトリックスの例(VS 2017 Communityエディション)
TDDの例題によく取り上げられる「FizzBuzz」プログラムである。左側が元のコード、右側がリファクタリング後のコードだ。リファクタリングによってif文の入れ子が解消し、条件式をメソッドに切り出したことで「3と5の倍数のときは"Fizz Buzz"を、3の倍数のときは"Fizz"を、5の倍数のときは"Buzz"を、それ以外のときは数字を返す」というFizz Buzzのロジックが分かりやすくなった(切り出したメソッド名は、日本語圏以外の人にも見せる可能性のあるコードの場合は、分かりやすさを多少犠牲にしてもIsMultipleOf3などとしなければならない)。
コードメトリックスを計算する機能は、Communityエディション以上のVS 2017に搭載されている。この画像はCommunityエディションである。
ソリューションに含まれる全てのプロジェクトのコードメトリックスを計算させるには、ソリューションエクスプローラーでソリューション名を右クリックし、出てきたコンテキストメニューで[コードメトリックスを計算する]を選ぶ。しばらくするとコードメトリックスが表示される(画像の下側)。
ここではリファクタリング前後のコードメトリックスを一度に示すために、リファクタリング前後のそれぞれをあえて別のプロジェクトとしている。
このリファクタリングによって、保守容易性指数は73から81へと向上している。ただし、サイクロマティック複雑度(コードパスの複雑さ)は6から9へと悪化してしまった。また、本文に書いたように、読みやすくしたのに保守容易性指数は下がってしまうこともある。現在のところ、コードメトリックスは参考として、メンテナンスしやすさの判断は人間の主観によるしかなさそうだ。
なお、参考とはいえ、コードメトリックスの結果を無視しないでほしい。保守容易性指数の欄が赤くなったら、リファクタリングが必要だというサインだ(この画像では2つとも緑色だが、保守容易性指数が下がると黄色から赤色に変わっていく)。
Copyright© Digital Advantage Corp. All Rights Reserved.