BOOK Preview

Code Complete 第2版 上・下
― 完全なプログラミングを目指して

第24章 リファクタリング

マイクロソフトプレスの書籍紹介ページ
書籍情報のページ
2005/05/10


 本コーナーは、.NET関連の新刊書籍から主要なチャプターをそのまま転載し、その内容を紹介するものです。

 今回は、日経BPソフトプレス/マイクロソフトプレスより2005年3月28日に発行の書籍『Code Complete 第2版 下 ― 完全なプログラミングを目指して』より、同社の許可を得てその内容を転載しています。

 同書は、11年前に出版された名著「Code Complete」の第2版です。第2版では、全体をとおしてオブジェクト指向の考え方が反映され、新しい章も多数追加されています。また、開発言語としてC#やVisual Basic .NETも取り上げられています。“完全な”コーディングのための鉄則を凝縮した本書は、開発者ならば必読といえるでしょう。

 本記事では「第24章 リファクタリング」を転載しています。この章では主に、リファクタリングする理由、具体的な各種リファクタリング手法、安全なリファクタリング実施方法、リファクタリング実施時の計画(戦略)の4つについて解説されています。

 本来一冊の書籍として解説されているリファクタリングですが、 ここでは著者が最も効果的であると考えているリファクタリングを中心に、各項目が非常にコンパクトに要約されています。そのため、本章を読むだけで、リファクタリングのエッセンスをつかむことができます。

 なお、書籍の詳細については書籍情報のページをご覧ください。

神話:よく管理されたソフトウェアプロジェクトでは、整然と要求が策定され、不変のプログラム役割リストが作成され、要求に従って設計が行われる。この作業は、コーディングが最初から最後まで一直線に進むように、つまりほとんどのコードが一度だけ書かれ、テストされ、後は忘れてもかまわないように、慎重に進められる。

 この神話によれば、コードが大幅に変更されるのは、ソフトウェアの保守段階だけということになる。ソフトウェアの保守は、システムの最初のバージョンがリリースされた後に、初めて行われるものだ。

大当たりしたソフトウェアには間違いなく変更が加えられる。
─ Fred Brooks
 

現実:コードは最初の開発時に大きく進化する。最初のコーディングで行われる変更の多くは、少なくとも保守段階で行われる変更と同じくらい大がかりなものだ。プロジェクトの規模にもよるが、コーディング、デバッグ、単体テストは、一般的なプロジェクトの作業の30〜65%を占める。仮に、コーディングと単体テストが一直線につながっていたとしたら(つまり変更がまったくないとしたら)、プロジェクト全体の20〜30%以上を占めることはないはずだ。だが、よく管理されたプロジェクトでさえ、要求の変更は1か月に約1〜4%の割合で発生する(Jones 2000)。要求を変更すれば、それに伴ってコードが変更される。そのためにコードが大幅に変更されることもある。

 
参照
コーディング、デバッグ、単体テストが占める割合については、「第27章プログラムサイズが及ぼす影響」を参照。
 

もう1つの現実:現代の開発プラクティスは、コンストラクション段階でコードを変更する可能性を増やしている。従来のライフサイクルでは、それがうまくいくかどうかに関係なく、コードの変更を避ける傾向にあった。最近のアプローチでは、「断定的なコーディング」離れが進んでいる。現在のアプローチはよりコード中心で、プロジェクト全体にわたってコードがどんどん進化していくことが予想される。

24.1 ソフトウェアの進化の種類

ご注意:本記事は、書籍の内容を改訂することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

 ソフトウェアの進化は、少数の突然変異がプラスに転じることがあるものの、たいていはそうでないという点で、生物の進化に似ている。ソフトウェアの進化によって生成されたコードは、うまくいけば、猿からネアンデルタール人を経て、現代のソフトウェア開発者へと上りつめるのと同じような進化をたどる。だが、進化がプログラムを逆の方向へと導き、プログラムを退化の渦に巻き込むこともある。

 ソフトウェアの進化を分ける重要な違いは、変更によってプログラムの品質が向上するか、それとも低下するかである。ダクトテープを貼ったり迷信に基づいたりするエラーの修正は、品質を低下させる。変更を、プログラムの最初の設計を強固にする機会と捉えれば、品質は向上する。プログラムの品質が低下していることに気付いたら、それは以前話した坑道の鳴かないカナリアのようなものだ。プログラムが誤った方向に進化しているという警告である。

 ソフトウェアの進化の種類を分けるもう1つの違いは、コンストラクション時に行われる変更と、保守段階で行われる変更との違いである。この2種類の進化はさまざまな点で異なる。コンストラクション時の変更は、通常はプログラムが完全に忘れ去られる前に、最初に担当した開発者によって行われる。システムはまだ運用されていないので、変更を完了させる圧力となるのはスケジュールだけである。どうしてシステムがダウンしたのかと腹を立てている500人のユーザーがいるわけではない。同じ理由から、コンストラクション時の変更の方が、自由度が高い可能性がある。システムはまだ流動的な状態で、ミスに対するペナルティは低い。こうした状況から、ソフトウェアの進化のスタイルが、ソフトウェアの保守段階のものとは異なることがわかる。

24.1.1 ソフトウェアの進化の哲学

 ソフトウェアの進化でよく見られる失敗は、プログラマの自覚がないまま進行していることだ。開発時の進化が必然的で重要な現象であることを認識し、そのための計画を立てれば、進化をうまく利用することができる。

 進化は危険に満ちていると同時に、完璧さに近づくチャンスでもある。コードを変更しなければならない場合には、将来簡単に変更できるようなコードになるよう努力する。プログラムを書き始めた段階では、後になってみないとわからないことがたくさんある。プログラムを改良する機会があったら、これまで学んできたことを基にプログラムを改善する。最初にコードを書くときも、コードを変更するときも、将来の変更を念頭に置いて作業する。

 ソフトウェアの進化の鉄則は、「進化によってプログラムの内部品質が改善される」ということだ。次は、これを実行する方法について説明しよう。

コードが大きいとか、ねじれているとか、複雑だとかいうことは、保守でひどいコードになることに比べれば何でもない。
─ Gerald Weinberg

24.2 リファクタリング概論

 ソフトウェアの進化の鉄則を実現するうえで重要なのは、リファクタリングである。Martin Fowlerは、これを「ソフトウェアの目に見える振る舞いを変えずに、ソフトウェアの内部構造を理解しやすく安価に修正できるようにする変更」と定義している(Fowler 1999)。現代のプログラミングにおける「リファクタリング」という言葉は、Larry Constantineが構造化プログラミングで初めて「ファクタリング」という言葉を使ったことに由来する。「ファクタリング」とは、プログラムの成分をできるだけ細かく分解することを意味する(Yourdon and Constantine 1979)。

24.2.1 ファクタリングする理由

 保守しているコードが古くなったり、コードが最初からあまり良くなかったりすることがある。いずれにしても、コードに次のような兆候があったら要注意だ。これらはリファクタリングが必要であることを示唆するもので、「におい(smell)」とも呼ばれる(Fowler 1999)。

■ コードが重複している
 重複したコードがあることは、ほぼ必ずと言ってよいほど、最初の段階で設計が完全に分解されていないことを意味する。コードが重複している場合は、修正を並行作業で行わなければならない。つまり、ある場所を変更したら、必ずもう1つの場所も並行して変更しなければならない。このことは、AndrewHuntとDave Thomasが定めた「DRY(Don't Repeat Yourself)原則」にも違反している(Hunt and Thomas 2000)。これを最も的確に表現しているのはDavid Parnasの「コピーアンドペーストは設計ミスである」だろう(McConnell1998b)。

■ ルーチンが長すぎる
 オブジェクト指向プログラミングでは、画面に表示しきれない長さのルーチンが必要になることはめったにない。そのようなルーチンが必要になるとしたら、たいていは、構造化プログラミングの足をオブジェクト指向の靴に無理やり押し込もうとしている兆候だ。

 私のクライアントの1人が、レガシシステムの最も長いルーチンを分割する仕事を担当した。そのルーチンは12,000行もの長さがあった。やっとのことで、彼はこのルーチンを4,000行ほどに減らすことができた。

 システムを改善する方法の1つは、モジュール性を高めることである。つまり、1つのことをしっかり行う、明確に定義された良い名前のルーチンを増やす。変更のためにコードの一部を見直す必要がある場合は、その機会に、その部分のルーチンをモジュール化できるかどうかを調査する。ルーチンの一部を別のルーチンに分けた方がすっきりする場合は、別のルーチンを作成する。

■ ループが長すぎる、またはネストが深すぎる
 ループの内部は、ルーチンへの変換を検討する有力な候補である。ルーチンへの変換によってコードがさらに分解され、ループの複雑さを緩和するのに役立つ。

■ クラスのモジュール凝集度が小さい
 関係のない機能が詰め込まれたクラスを発見したら、そのクラスを複数のクラスに分解し、それぞれのモジュール凝集度を大きくする。

■ クラスのインターフェイスが一貫性のある抽象化を実現していない
 一貫性のあるインターフェイスを持っていたクラスが、当初の一貫性を失ってしまうことがある。インターフェイスの整合性を強めるためにはずみで行った変更のせいで、クラスのインターフェイスは徐々に変形していく傾向にある。やがて、クラスのインターフェイスは保守の施しようのない怪物と化し、プログラムを頭で理解しやすくする役割をほとんど果たさなくなる。

■ 引数リストの引数の数が多すぎる
 十分に分解されたプログラムは、明確に定義された小さなルーチンをたくさん持つ傾向にある。このようなルーチンは大きな引数リストを必要としない。長い引数リストは、ルーチンのインターフェイスの抽象化が十分に検討されていない兆候である。

■ 変更がクラス内部の他の部分に影響しない
 クラスにまったく異なる機能がいくつか含まれている場合、クラスの一部分を変更するときに、クラスの別の部分にほとんど影響しないことに気付くことがある。これは、クラスを機能別に複数のクラスに分割せよという合図である。

■ 変更が発生したら複数のクラスを同時に修正しなければならない
 あるプロジェクトで、新しい変更が発生するたびに修正しなければならないクラスのチェックリストがあり、15個ほどのクラスが並んでいるのを見たことがある。いつも決まって同じクラスの集合を変更しているという場合は、それらのクラスのコードを整理すれば、変更を1つのクラスにまとめられることの暗示である。私の経験では、なかなかそうはいかないものだが、それでも良い目標になる。

■ 継承の階層を並行して修正しなければならない
 あるクラスのサブクラスを作成するたびに、別のクラスのサブクラスを作成していることに気付いたら、それは特殊な並行修正であり、対処する必要がある。

 

 INDEX
  Code Complete 第2版 上・下
  第24章 リファクタリング
  1.24.1 ソフトウェアの進化の種類
    2.24.2 リファクタリング概論
    3.24.3 リファクタリングの詳細(1)
    4.24.3 リファクタリングの詳細(2)
    5.24.4 安全なリファクタリング
    6.24.5 リファクタリング戦略
 
インデックス・ページヘ  「BOOK Preview」


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

本日 月間