特集

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

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

Page1 Page2 Page3 Page4

■何のためにソース・コードを書き換えるのか?

 ここで疑問を抱いた読者も多いだろう。

 このような書き換えに果たしてどのような意味があるのだろうか。少しだけ処理の効率が上がるかもしれないが、いまどきのコンピュータの処理速度はすさまじく高速である。当然、大した改善は見込めないだろう。

 一方、問題なく動いているコードを書き換えることは、明らかにまずい選択であるというのが経験的な真理である。書き換えれば、どこにバグが混入するか分からない。改善が小さい割にリスクが大きすぎ、このような書き換えを行うメリットは見いだせないと思うのが当然のことである。つまり、期待されるメリットの小ささと、リスクの大きさという2つの疑問が出てくると思うのである。

 このうち、リスクの大きさについては「自動化された単体テスト」というテクニックを併用することで、問題ない水準まで低くすることができる。これはリファクタリングの本質とは違う話なので、後で詳しく説明しよう。

 もう1つの問題は、期待されるメリットの小ささである。ほんの少しだけ処理を効率化するために、リファクタリングはソース・コードの書き換えを行うべきだというのだろうか? そうではない。リファクタリングが求めているのは、ソース・コードをシンプルにすることである。決して、処理効率を上げることを目的としているわけではない。リファクタリングによって行う書き換えには、むしろ処理速度を落とすようなものも含まれる。

 では、具体的に上記の書き換えでは、いったい何がシンプルになるというのだろうか。書き換える前と後の相違点は、書き換える前にはフラグ変数flagがあり、書き換えた後にはExit Doステートメントがある、ということである。その差が、それほど重要なことだろうか。この程度の短いコードでは実感できないかもしれないが、これがもっと大きくて複雑なコードの一部だとすれば、その差は歴然としてくる。

 例えば、100行もの長いループの中で「flag = True」というコードを見たとき、それが何を引き起こすかを理解するためには100行をチェックする必要がある。このような変数は、条件判断の制御に使われたり、計算式の一部に使われたりと、さまざまな目的に使われる可能性があり得るが、目的を特定するにはコードを一通りチェックしなければならない。

 しかし、Exit Doステートメントであれば、それはDoループからの脱出という機能しかないので、意図は明白である。つまり、あるコードが持つかもしれない機能性として見たとき、フラグ変数flagよりもExit Doステートメントの方がより単純明快であるということである。これにより、ソース・コード修正時に誤った書き換えを行う可能性は著しく低下する。

 ループの途中で、変数flagを別の目的に利用するようなコードを書き足してもコンパイラはエラーを出さないが、正常な動作は阻害される。もし、変数ではなくExit Doステートメントを使用した場合であれば、これをDoループからの脱出という目的以外には使えないので、それ以外の効果を引き起こす誤った書き換えはそもそも不可能になる。

 「フラグ変数の使用」のようなソース・コードを分かりにくくするコーディングのパターンは、経験的にいくつも知られているが、リファクタリングはそれらを、より分かりやすい形に書き換えるテクニックである。より具体的には、書き換え可能なパターンが多数カタログ化されていて、それらを適用して書き換えていくことがリファクタリングという作業である(カタログについては後で取り上げる)。

 それらの書き換えは、「クラスの役割」などを知らなくても可能なものばかりである。つまり、理解できないソース・コードに適用できる。このような書き換えを徹底的に行うことで、手に負えない汚いソース・コードが、なんと手に負えるソース・コードに変容していくのである。この段階に到達することが、リファクタリングの1つの目的である。手に負えるソース・コードが手に入れば、後は思いどおりにそれに手を加えるだけである。

 このような話を読んで、それでもなおウソくさいと思った読者もいるだろう。ソース・コードをすべて書き直せば確かに分かりやすいソース・コードが得られるだろうが、すべて捨ててゼロから書き直すのとどこが違うのだろうか? そして、そのような書き換えを行う時間が常に得られるとは限らない、という意見もあるだろう。

 それに対する答えは簡単である。リファクタリングによる書き換えは、常にソース・コードの一部分だけを、機能を変えない形で行われる。

−POINT−
 リファクタリングはソース・コードの一部分だけを機能を変えないで書き換える

 つまり、リファクタリングを適用してもプログラムの機能は一切変化しない。万一、ドキュメントに記載されていない機能がある場合、ゼロから作り直すとそれが欠落する可能性があるが、リファクタリングではあり得ない。そして、リファクタリングの書き換えは、常に機能を変えない小さな修正を繰り返すため、作業はいつでも打ち切ることができる。ゼロから書き直せば完成するまで運用できないが、リファクタリング中のコードではそれが可能である。また、修正したい個所にのみリファクタリングを適用し、修正対象ではない場所には適用しないという選択も可能である。

−POINT−
 リファクタリングは途中で作業を打ち切ってもプログラムは正常に動作する

−POINT−
 リファクタリングはソース・コードの一部分にのみ適用することもできる

 これらの特徴により、リファクタリングの開発現場への適応力は非常に高いものであると考えられる。そして、リファクタリングがもたらす効果は絶大である。何が書いてあるのかさっぱり分からないソース・コードが、読めば分かるソース・コードに変化していくのである。

■継続的なリファクタリング

 プログラマが、このような効能を一度体験して味を占めると、常にリファクタリングし続ける「リファクタリング中毒」とも呼ばれる状態に陥る場合がある。しかし、これは悪いことではない。むしろ、好ましい態度とすらいえる。なぜなら、リファクタリングを行うタイミングが早ければ早いほど、より少ない手間、少ない時間で完了できるためである。そして、常にリファクタリングし続けるソース・コードは、手に負えない汚いソース・コードに陥ることがない。それによって得られる生産性の改善は、とても大きなものになる。

 このような効能を前提に、作業手順の中にリファクタリングする手順を組み込んでいるものに「テスト駆動開発」がある。テスト駆動開発では、1サイクルを

レッド(単体テストが失敗する段階)
グリーン(単体テストが成功する段階)
リファクタリング

という3段階に分けている。これにより、コードを書いて単体テストをパスしたら、その直後に必ずリファクタリングを行うことが求められる。つまり、リファクタリングせずにサイクルを完了できないようになっている。コードを書いた直後にリファクタリングすれば、後から行うよりもはるかに少ない時間と手間で実現することができる。

 テスト駆動開発の詳細については、「特集:『テスト駆動開発』はプログラマのストレスを軽減するか?を参照いただきたい。

■なぜクラスやモジュールの役割が変わってもよいのか?

 リファクタリングはソース・コードを理解することなくソース・コードを書き換えるため、最初に決めたクラスやモジュールの役割などに反したコードが発生する可能性がある。

 例えば、リファクタリングを進めるうちに、組織情報クラスに含まれていたメソッドが、個人情報クラスに移動してしまう、という事態が発生することがある。あらかじめ定められた役割を与えられたクラスやモジュールから個々の機能が飛び出してほかの場所に行ってしまうのである。つまり、メソッドの移動によりクラスの役割が変わってしまうが、そのような書き換えが行われてよいのだろうか? これは、「クラスの役割は1つに限るべき」というような考え方に反するのではないだろうか?

 異論のある方もいると思うが、筆者の解釈を述べよう。

 上記の文章は、1点だけ修正すれば、矛盾なく整合する。そのために修正する1点とは、「あらかじめ定められた役割を与えられた」という部分である。これこそが、ある意味で諸悪の根源であり、混迷をもたらす恐怖の大王そのものといえるのではないだろうか。なぜ、これが悪であるのか。その理由は簡単である。そもそも「正しい」役割を「あらかじめ」定めることはほとんど不可能に近いからである。

 そんなばかなと思う読者もいると思うが、開発に着手する前に、完成したプログラムの全体像を「確実に」予測可能であるか考えてみれば分かると思う。そもそも人間には、すべてを完ぺきに矛盾なく構想する能力はない。そのため、実際に開発に着手してから判明することがいろいろ出てくる。それだけなら、科学的かつ厳密な手法を取り入れれば乗り越えることができるだろう、という希望を持つことができるかもしれない。

 しかし、事前に予測不可能な仕様変更は、どんなに科学的、厳密的に取り組んでも対応できない。例えば、社会情勢の変化などで要求される仕様変更には事前に対応できない。このような状況で、あるべき正しい役割を事前に決定することには無理がある。それでも役割を決め、それを変更せずに最後まで維持し続けることは、ソース・コードを手に負えない汚い姿に変容させる危険を持つ。

 さらに、役割とは「構文(syntax)」ではなく「意味(semantics)」を示すものである。構文は人工言語を使うことであいまいさを低減することができるが、意味は日本語や英語のような自然言語を使って記述するしかなく、それらの言語はあいまいさを含むために、プログラム開発が要求するレベルの厳密な表記に使用するには十分ではない。

 では、役割について考えることは無意味なのだろうか。そうではないと筆者は考える。ただ、「あらかじめ役割を定める」という行為が目的のために十分ではないというだけの話である。事前に定められた役割などない、と考えてリファクタリングを行い、まずは十分にシンプルなソース・コードを得る。そこから、そこに含まれるクラスやモジュールの役割を読み取り、それにふさわしい名前に付け替えていくこと(名前を変えるだけでメソッドの移動などは含まない作業)もまた広義のリファクタリングの手順の一部といってよいと思う。リファクタリングされた結果となるクラスやモジュールは、十分にシンプルであり、たった1つの役割を見いだすことも容易だろう。

 つまり、クラスの役割(クラスの名前とそれを構成するメンバの決定)というのは先に決めるのが難しいので、とりあえずクラスを書いてみて、メソッド内をシンプルにし、メソッドを移動させ、クラス名を変更すればよいということだ。このためクラスの役割が変わるのは当然である。

 逆に、すべての役割が事前に厳格に決定され、それを変えることが許されないプロジェクトも存在するだろう。そのような場合には、リファクタリングのカタログ(次に紹介する)にあるすべての書き換えを実行することはできないだろうが、実行できるものも少なくないだろう。全力発揮とはいかないかもしれないが、リファクタリングの効能を発揮できる余地は十分にあり得ると考えてよいと思う。


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

本日 月間