ARCHIVES
アーカイブス
アーカイブス

誰でもわかる リファクタリング入門

日経BP 日経ソフトウエア 2002年8月号に特集記事 「誰でもわかるリファクタリング入門」

平澤 章・馬場 大輔
2002年08月01日
※内容は公開当時のものです

皆さんは,リファクタリング(refactoring)という言葉を聞いたことがあるでしょうか?実生活では,ほとんど聞いたことのない言葉ですよね(注1)。一言で言えば「プログラムの外部仕様(入力と出力)を変えずに,内部構造を安全に改善するテクニック」がリファクタリングです(図1)。その目的はプログラムを理解しやすい状態に維持し,拡張性や再利用性を高めること。「こまめに手入れをしてプログラムを長持ちさせるテクニック」と言ってもいいでしょう。

図1

写真1

写真1

テクニックと言うくらいですから,リファクタリングは単なる「改善のススメ」といったコンセプトではありません。リファクタリングが必要となる状況「コードの不吉なにおい(bad smell)」や,実際の改善手順,サンプル・コードなどが具体的にまとめられ,そのものずばり「リファクタリング:プログラミングの体質改善テクニック」(写真1)(注2)という書籍に,カタログのように整理されています。

しかし皆さんの中には,「リファクタリングはデバッグやチューニングと何が違うの?」と思う方がいらっしゃるかもしれません。実はプログラムの修正という行為は「デバッグ」「機能拡張」「パフォーマンス改善(チューニング)」「リファクタリング」の四つに大きく分類できるのです。これらの中でリファクタリングは,どういう位置付けにあるのでしょう。

まず四つを「どの状態のプログラムを修正するのか」という視点で分類すると,デバッグと残り三つに分かれます。デバッグは,正常に動かないプログラムを修正することですね(注3)。一方,機能拡張,パフォーマンス改善,リファクタリングは,正常に動いているプログラムに対して行う修正です。機能拡張は,できあがったプログラムに新しい機能を加えたり,使い勝手を良くしたりする修正ですし,パフォーマンス改善は,実行速度が遅かったり,システムのリソース(メモリーやディスクの容量)を必要以上に消費していたりする場合に施す修正です。リファクタリングも,動作そのものには問題ないプログラムに対して行う修正です。

しかしこの四つを「修正をすると恩恵を受けるのは誰か」という視点で分類すると,リファクタリングが他の三つと比べて異なる点が明確になってきます(図2)。それは最初の三つが,プログラムを動かして使う人(ユーザー)のために行う修正であるのに対し,リファクタリングはプログラムを直す人(つまりプログラマ)のための修正であるという点です。ここで言うプログラマには,将来そのプログラムを直す他人だけでなく,自分自身(注4)も含まれています。つまり,リファクタリングは誰のためでもなく,プログラマ自身のために行う作業というわけです。「じゃあリファクタリングなんて,しょせんプログラマの自己満足なの?」----いいえ。プログラムの構造がわからなくなって,デバッグや機能拡張が滞ってしまえば,最終的に困るのはユーザーです。結局,リファクタリングを行うことはユーザーにとっても間接的なメリットがあると言えるのです。

図2

図2

リファクタリングを安全に行うコツ

しかし,リファクタリングには危険もあります。何しろ正しく動いているプログラムを直すのですから,新たなバグを作り込んでしまう可能性があります。頭の中で「こうすればうまくいく」と思っても、いざ試してみると思い通りにならないことがあったり,単純な間違いをしてしまったり,ということもあるでしょう。リファクタリングの過程でバグを作り込んでしまい,それを取り除くために一生懸命デバッグをしなければならないようでは本末転倒です。修正した後で,書き直したプログラムを元に戻せなくなってしまう危険もあります。

そこで大切になってくるのが,いかにリファクタリングを安全に行うかというコツでしょう。以下の五つが挙げられます。

リファクタリングと他の作業を分離する

プログラムに機能追加をするときに,リファクタリングをすることがよくあります。ただし両方の修正をまとめてやってはいけません。異なる種類の修正をまとめて行うと,間違いが起きたときにどこが原因かを調べるのが大変になるからです。それに,まとめて修正すると元に戻すことも難しくなってしまいます。

リファクタリングの前後で必ずテストをする

リファクタリングはプログラムの外部仕様を変えないため,プログラムの実行結果も変わりません。したがって,リファクタリングの前後では必ずテストを行い,バグを作り込んでいないことを確認する必要があります。テストは,極力簡単に行えるようにするのがポイントです。一般に,リファクタリングをする場合には,まずそのプログラムの正常動作を確認するためのテスト・プログラムを書き,リファクタリングの前後で実行しながら作業を進める方法が推奨されています。テスト・プログラムを簡単に作るため,xUnit (注5) と呼ばれるオープンソースのテスティング・フレームワークもあります(図3)。

図3

図3

1回の作業を極力小さくする

メソッドの名前を変える,if文などの条件記述を変更する,といった小さな修正を行い,そのたびにテストを行うようにしましょう(注6)。こうした小さな単位での修正と確認を積み上げる方法をとることで,仮に間違ってしまった場合でも原因を特定しやすくなります。

既存のロジックへの影響を少なくする

コードをガラッと書き換えはいけません。すでに正しく動いている既存のコードをできるだけ残して,徐々に進めていきましょう。

必ずバックアップをとっておく

どんなに慎重に進めても,人間のやることに間違いはつきものです。したがって,いつでも元に戻せるように細かくバックアップを保存しておくことが大切です。同じファイルをバージョン別に保存できる構成管理ツール(注7)を使うことも考えましょう。

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

以上五つのコツを読んで,なんだか面倒くさそうな感じがしてきた方はいませんか? 安心してください。リファクタリングは比較的新しいテクニックですが,そのノウハウは確立しています。それがリファクタリングの「カタログ」です。前述の「リファクタリング」書籍には,全部で68個のカタログがあり,大きく六つに分類されています(表1)。もしメソッドの構成についてリファクタリングを行いたければ,9個のカタログの中から最適な手順を探せばいいでしょう(注8)。  一つひとつのカタログには,名前,要約,動機,手順,サンプル・コードがありますから,何をどうすればいいか,すぐにわかります。例えば,代表的なリファクタリングである「メソッドの抽出」の手順を抜粋してみましょう。

  1. 適切な名前の新しいメソッドを作る
  2. 抽出したいロジックを新しいメソッドにコピーする
  3. 抽出されたロジック中のローカル変数を新しいメソッドに適合するように変更する(メソッドの引数や一時変数,戻り値に変更する)
  4. コンパイルしてテストする
  5. 元のメソッドを新しいメソッドを呼び出すように変更する
  6. コンパイルしてテストする

このようにカタログには,非常に細かい作業内容が具体的に書いてあり,さらに細かい注意点やサンプル・コードも紹介されています。リファクタリングのテクニックは,実際にプログラムを直そうとするときのハンドブックとして使えるわけです。

表1:リファクタリング・カタログの6分類

分 類 説 明 カタログの数
メソッドの構成 ロジックを適切な単位のメソッドにまとめる 9
オブジェクト間での特性の移動 クラスの役割分担を適切にする 8
データの再編成 データ操作の方法を適切にする 16
条件記述の単純化 条件記述(if文など)を単純化する 8
メソッド呼び出しの単純化 メソッドの呼び出し方を適切にする 15
継承の取り扱い 継承の使い方を適切にする 12

目的を決めてリファクタリングしよう

「リファクタリングの目的も,その方法もだいたいわかった。でも,いつリファクタリングを行うべきか?」----そうですね。前述のようにリファクタリングはユーザーのためではなく,プログラマのために行うものです。ユーザーから「今からリファクタリングをしてほしい」なんていう要望はけっして出てきません。いつリファクタリングするかの判断は,プログラマ次第というわけです。  ところがデバッグ,機能追加,パフォーマンス改善に比べると,プログラムの構造をきれいにして,拡張性や再利用性を高めることは実際にはかなりアイマイです。「きれいかどうか」という判断には多分に主観が入りますし,拡張性や再利用性も,何をどこまで考慮するかによってずいぶん基準が変わります。こうした作業はある意味キリがありません(注9)。

したがって,リファクタリングをいつ行うか,どこまでするかという判断は案外難しいものと言えます。そこで筆者がお勧めするのが「目的を決めて」リファクタリングをすることです。特に有効なのは,機能拡張をする必要が出た時にリファクタリングをすることです。すでに動いているプログラムに機能を追加しようとしたときに「そのままでは修正がしにくい」「プログラムのここがこうなっていたら良かったのに」と思ったときが,リファクタリングの絶好のタイミングなのです(注10)。

実践!リファクタリング

それでは,実際にJavaのサンプル・プログラムを使ってリファクタリングを体験してみましょう。取り上げるのは,バイオリズムのプログラムです。  バイオリズムとは,人間の身体,感情,知性の三つの要素について,生まれてからの周期的な変化を表したものです。それぞれの要素の数値(ポイント)と,その組み合わせで「調子が良いのか悪いのか」を判断します(注11)。  ある日のバイオリズムを計算するには,まず誕生日からその日までの日数を計算します。次に三つの要素ごとに異なる周期の変化を(図4)の式で算出します。

  • 身体 :p=sin(term÷23×2π)
  • 感情 :s=sin(term÷28×2π)
  • 知性 :i=sin(term÷33×2π)

図4:termは誕生日からその日までの日数

サンプル・プログラムでは,誕生日を入力するとプログラム実行日のバイオリズムを計算する仕様になっています。

リスト1:バイオリズムを計算するSimpleBiorhythmクラスコード(SimpleBiorhythm.java)

リスト1がリファクタリング前のJavaプログラム(SimpleBiorhythm.java)です。(1)mainメソッドと(2)calculateDaysInADメソッドの二つで構成しています。日付,sin関数,円周率(π)の計算には,java.util.Calendarクラスやjava.lang.Mathクラスを利用しています。このプログラムの実行時引数に誕生日を指定すると,今日のバイオリズムの得点が表示されるというわけです(注12)。例えば1975年4月22日を引数とすれば,実行結果は(図5)のようになります。プログラムの動作には,何の問題もありません。

図5

図5

しかしリスト1のプログラムを眺めていたら,気になる点がいくつか見つかりました。また,どうせ計算するなら,毎日のバイオリズムの数値をデスクトップ画面に表示したり,数日分のバイオリズムをグラフで見たい,といったさまざまなアイデアも沸いてきました。

そう,先に述べましたね。「機能拡張したいと思ったときが,リファクタリングの絶好のタイミング」です。そこで,こうした対応が簡単にできることを目標にリファクタリングを行っていきましょう。

リファクタリング1 理解しやすい変数名に変更する

リスト1を眺めて最初に気付くのは,mainメソッドの中に変数がたくさんあることでしょう。year, month, dayについては,それぞれ末尾に0と1がつく変数があります。ほかにも日数を表すdays0とdays1や,バイオリズムの三つの要素を表すbio0,bio1,bio2などがあり,全体的にごちゃごちゃした感じです。

そこで,少しでもわかりやすくなるように,こうした変数の名前を変更してみましょう(注13)。まずは最初に登場する三つの変数year0,month0,day0から手をつけましょう。これらの変数は実行時の引数として渡されますが,ロジックを追いかけてみると,誕生日を表していることがわかります。そこでそれぞれの名前を,birthYear,birthMonth,birthDay に変更します(リスト2)。

リスト2

public class SimpleBiorhythm {

 public static void main(String[] args) {
  int birthYear = Integer.parseInt(args[0]);
  int birthMonth = Integer.parseInt(args[1]);
  int birthDay = Integer.parseInt(args[2]);
  Calendar calendar = Calendar.getInstance();
  int year1= calendar.get(Calendar.YEAR);
  int month1= calendar.get(Calendar.MONTH) + 1;
  int day1= calendar.get(Calendar.DAY_OF_MONTH);
  // 日数計算
  int days0= calculateDaysInAD(birthYear, birthMonth, birthDay);
  int days1 = calculateDaysInAD(year1, month1, day1); 
 ~ 略 ~

この修正が終わった段階で,SimpleBiorhythm.javaをコンパイルしてテストを実行します。テストは図5と同じ条件でプログラムを実行し,同じ結果になればOKと判断します。この例では特にJUnitなどのテスティング・フレームワークを用いませんが,お持ちの方はぜひ使ってみてください。引き続いて,次の変数についても同様の手順を繰り返して変更します。

  • year1,month1,day1の名前を,それぞれcurrentYear,currentMonth,currentDayに変更する
  • days0,days1の名前を,それぞれbirthDays,currentDaysに変更する
  • bio0,bio1,bio2の名前を,それぞれphysicalPoint,emotionalPoint,intellectualPointに変更する

リファクタリング2 複雑な条件判定をわかりやすくする

もう少しリスト1を眺めてみましょう。(2)のcalculateDaysInADメソッドはどうでしょうか。これは指定された年月日について西暦1年1月1日からの日数を求める計算を行っています。このうち,わかりにくいのが(3)の条件判定です。

この条件判定の後半部分では西暦の年を4,100,400でわり算したときの余りを調べていますね。これが何を意味しているかわかりますか?

そう,ここではその年がうるう年かどうかを調べているのです。ちょっと見ただけではわかりませんね。そこで,そのものズバリのisLeapYear(うるう年かどうかを判定する)という名前のメソッド(注14)に変更してしまいましょう。これは「条件記述の分解」と呼ばれるリファクタリングです。 まずは,うるう年を判定するisLeapYearメソッドを新たに作ります。判定するだけですから,戻り値はboolean型にして,うるう年なら真,違うなら偽となるようにします。判定ロジックは,リスト1のうるう年判定の部分をそのままコピーすればいいでしょう(リスト3)。

リスト3

//うるう年かどうかを判定する
private static boolean isLeapYear(int year) {
 return ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0));
}

この修正が終わった段階で,SimpleBiorhythm.javaをコンパイルしてテストを実行します。テストは図5と同じ条件でプログラムを実行し,同じ結果になればOKと判断します。この例では特にJUnitなどのテスティング・フレームワークを用いませんが,お持ちの方はぜひ使ってみてください。引き続いて,次の変数についても同様の手順を繰り返して変更します。

リスト4

private static int calculateDaysInAD(int year, int month, int day) {

~ 略 ~

 if ((month > 2) && isLeapYear(year)) {
 days++;
 }

~ 略 ~

}

とてもすっきりしてわかりやすくなりましたね。リスト4は,2月を過ぎてその年がうるう年ならば,2月29日の1日分を日数として加算することを表しています。これならコメントを書かなくてもプログラムを読むだけで意味が伝わります。コメントを軽視してはいけませんが,コメントがなくても理解できるようなプログラムが「わかりやすい」プログラムですからね。それでは,再びコンパイルとテストを実行して,結果が同じになることを確認します。

リファクタリング3 主要な処理をメソッドとして抽出する

ここまでのリファクタリングを行った結果,プログラムはだいぶ理解しやすくなりました。でもまだ「それだけ」です。今回のリファクタリングの目標は,バイオリズムの値をデスクトップ画面に表示したり,グラフィックス表示をしたりすることです。それを達成するまで,もう少しがんばらなくてはいけません。

これらの目標を達成するには,どうしたらいいでしょう。ポイントとなるのは,バイオリズムの値を単純に取り出せるようにすることです。そうすれば,後はグラフィックス表示などを行う別のプログラムにバイオリズムの結果を渡せます。ところが現時点のプログラムでは,バイオリズムの計算とコンソールへの表示(System.out.println~の部分)の両方がmainメソッドの中で行われています。これでは,計算結果だけ取り出したいのに,プログラムを実行するたびにいちいちコンソールに結果が表示されてしまいます。そこでmainメソッドから,バイオリズムの計算処理だけを取り出して,別のメソッドとして独立させましょう。これは「メソッドの抽出」と呼ばれる最も代表的なリファクタリングです。 まず先ほどと同様に,バイオリズムの計算処理だけを行うメソッドを新しく追加します。名前はcalculateBiorhythmメソッドでいいでしょう。そのものズバリですよね。バイオリズムの三つの値を返す必要があるため,戻り値はdouble型の配列とします。ロジックの中身は,mainメソッドから使えそうな部分をコピーして作ります(リスト5)。

リスト5

// バイオリズムの値を計算する
private static double[] calculateBiorhythm(
 int birthYear, int birthMonth, int birthDay,
 int currentYear, int currentMonth, int currentDay) {
 // 日数計算
 int birthDays = calculateDaysInAD(birthYear, birthMonth, birthDay);
 int currentDays = calculateDaysInAD(
 currentYear, currentMonth, currentDay);
 int term = currentDays - birthDays;
 // バイオリズムを計算
 double physicalPoint = Math.sin(
 (double) term / 23 * 2 * Math.PI);
 double emotionalPoint = Math.sin(
 (double) term / 28 * 2 * Math.PI);
 double intellectualPoint = Math.sin(
 (double) term / 33 * 2 * Math.PI);
 return new double[] {
 physicalPoint, emotionalPoint, intellectualPoint};
}

ここでもコンパイルとテストを実行してきちんと動くかどうかを確かめましょう。次に,新しく追加したcalculateBiorhythmメソッドを利用するように,mainメソッドを書き換えます(リスト6)。もちろんコンパイルとテストの実行を忘れてはいけません。

リスト6

public static void main(String[] args) {
 // 誕生日
 int birthYear = Integer.parseInt(args[0]);
 int birthMonth = Integer.parseInt(args[1]);
 int birthDay = Integer.parseInt(args[2]);
 // 今日
 Calendar calendar = Calendar.getInstance();
 int currentYear = calendar.get(Calendar.YEAR);
 int currentMonth = calendar.get(Calendar.MONTH) + 1;
 int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
 // バイオリズムを計算
 double[] points = calculateBiorhythm(birthYear, birthMonth, birthDay,
 currentYear, currentMonth, currentDay);
 // 判定
 System.out.println("今日(" + currentYear + "/" + currentMonth +
 "/" + currentDay + ")のバイオリズム:");
 System.out.println("身体リズム:" + (int) (points[0] * 100));
 System.out.println("感情リズム:" + (int) (points[1] * 100));
 System.out.println("知性リズムl:" + (int) (points[2] * 100));
}

リファクタリング4 戻り値をオブジェクトに置き換える

ここまでで,バイオリズムの計算部分をメソッドとして抽出できました。これで,グラフィックス表示などにも対応できるようになったと言えそうです。でも,もう少し改善したいところがあります。それは,新たに作ったcalculateBiorhythmメソッドの戻り値です。リスト5ではdouble型の配列を戻しています。この配列には,0番目の要素に「身体」,1番目に「感情」,2番目に「知性」を格納しています。しかし,こうした配列の順序に意味を持たせる方法は,プログラマが順番を間違えてもコンパイル時にチェックできないため好ましくありません。 そこでこの部分もリファクタリングしてしまいましょう。こういうときには,受け渡し専用のクラスを新たに作るというテクニックが役に立ちます。これは,「引数オブジェクトの導入」というリファクタリングの変形です。

具体的には,バイオリズムの三つの値を保持するクラスを別に作り,それをcalculateBiorhythmメソッドの戻り値として利用します。 それでは,まずバイオリズムの値を保持するクラスを新しく作りましょう。クラスの名前はバイオリズムの情報を保持するという意味でBiorhythmInfo(BiorhythmInfo.java)とします。バイオリズムの三つの値をフィールドに持って,それを取得するメソッド(getPhysicalPoint( ),getEmotionalPoint( ),getIntellecturalPoint( )を定義するだけのクラスです(リスト7)

リスト7

public class BiorhythmInfo {
 private final double physicalPoint;
 private final double emotionalPoint;
 private final double intellectualPoint;

 public BiorhythmInfo(double physicalPoint, double emotionalPoint,
 double intellectualPoint) {
 this.physicalPoint = physicalPoint;
 this.emotionalPoint = emotionalPoint;
 this.intellectualPoint = intellectualPoint;
 }

 // 身体リズムのポイントを取得
 public double getPhysicalPoint() {
 return this.physicalPoint;
 }
 // 感情リズムのポイントを取得
 public double getEmotionalPoint() {
 return this.emotionalPoint;
 }
 // 知性リズムのポイントを取得
 public double getIntellectualPoint() {
 return this.intellectualPoint;
 }
}

だいぶしつこくなってきましたが,新たに作ったBiorhythmInfo.javaもコンパイルし,コンパイル・エラーが起きないことを確認します。そして,BiorhythmInfoクラスをcalculateBiorhythmメソッドの戻り値に変更し(リスト8),コンパイルとテストを実行します。

リスト8

public class SimpleBiorhythm {
 private static BiorhythmInfo calculateBiorhythm(
  int birthYear, int birthMonth, int birthDay,
  int currentYear, int currentMonth, int currentDay) {

~ 略 ~

  return new BiorhythmInfo(
  physicalPoint, emotionalPoint, intellectualPoint);
  }
  public static void main(String[] args) {

~ 略 ~

  // バイオリズムを計算
  BiorhythmInfo[] points = calculateBiorhythm(birthYear, birthMonth, birthDay,
  currentYear, currentMonth, currentDay);
  // 表示
  System.out.println("今日(" + currentYear + "/" + currentMonth +
  "/" + currentDay + ")のバイオリズム:");
  System.out.println("身体リズム:" +
  (int) (biorhythmInfo.getPhysicalPoint() * 100));
  System.out.println("感情リズム:" +
  (int) (biorhythmInfo.getEmotionalPoint() * 100));
  System.out.println("知性リズムl:" +
  (int) (biorhythmInfo.getIntellectualPoint() * 100));

~ 略 ~

リファクタリング5 クラスの責任を切り分ける

バイオリズム計算処理を独立したメソッドにし,戻り値も使いやすくなりました。しかしまだバイオリズム計算処理が,コンソールに値を表示するSimpleBiorhythmクラスの一部になっているため,やや中途半端な感じです。最後の仕上げとして,バイオリズム計算処理の部分を専用のクラスとして独立させてしまいましょう。これは「クラスの抽出」というリファクタリングです(注15)。 新しいクラスの名前は,バイオリズムを計算するわけですから,そのものズバリのBiorhythmCalculator(BiorhythmCalculator.java)としましょう。ここにSimpleBiorhythmクラスからバイオリズム計算に必要なロジックをコピーします。また,初期化の際に誕生日を引数として受け取るようにします(リスト9)。また,このBiorhytmCalculatorを使うように,SimpleBiorhythmクラスも変更します(リスト10)。

リスト9

public class BiorhythmCalculator {

 private final int birthYear;
 private final int birthMonth;
 private final int birthDay;

 // コンストラクタ
 public BiorhythmCalculator(int birthYear, int birthMonth, int birthDay) {
  this.birthYear = birthYear;
  this.birthMonth = birthMonth;
  this.birthDay = birthDay;
}

 // 日数計算
 private static int calculateDaysInAD(int year, int month, int day) {
  int[] monthTerm = new int[] {
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  int days = (year - 1) * 365 + (year - 1) / 4 - (year - 1) / 100 +
  (year - 1) / 400;
  for (int i = 0; i < month - 2; i++) {
  days += monthTerm[i];
}
  if ((month > 2) && isLeapYear(year)) {
  days++;
}
  days += day;
  return days;
}

 private static boolean isLeapYear(int year) {
  return ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0));
}

 // バイオリズム計算
 public BiorhythmInfo calculateBiorhythm(int year, int month, int day) {
  int birthDays = calculateDaysInAD(
  this.birthYear, this.birthMonth, this.birthDay);
  int currentDays = calculateDaysInAD(
  year, month, day);
  int term = currentDays - birthDays;
  double physicalPoint = Math.sin(
  (double) term / 23 * 2 * Math.PI);
  double emotionalPoint = Math.sin(
  (double) term / 28 * 2 * Math.PI);
  double intellectualPoint = Math.sin(
  (double) term / 33 * 2 * Math.PI);
  return new BiorhythmInfo(
  physicalPoint, emotionalPoint, intellectualPoint);
 }
}

リスト10

import java.util.Calendar;

public class SimpleBiorhythm {

 public static void main(String[] args) {
  int birthYear = Integer.parseInt(args[0]);
  int birthMonth = Integer.parseInt(args[1]);
  int birthDay = Integer.parseInt(args[2]);
  Calendar calendar = Calendar.getInstance();
  int currentYear = calendar.get(Calendar.YEAR);
  int currentMonth = calendar.get(Calendar.MONTH) + 1;
  int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
  // バイオリズム計算
  BiorhythmCalculator calculator =
  new BiorhythmCalculator(birthYear, birthMonth, birthDay);
  BiorhythmInfo biorhythmInfo = calculator.calculateBiorhythm(
  currentYear, currentMonth, currentDay);
  // 判定
  System.out.println("今日(" + currentYear + "/" + currentMonth +
  "/" + currentDay + ")のバイオリズム:");
  System.out.println("身体リズム:" +
  (int) (biorhythmInfo.getPhysicalPoint() * 100));
  System.out.println("感情リズム:" +
  (int) (biorhythmInfo.getEmotionalPoint() * 100));
  System.out.println("知性リズムl:" +
  (int) (biorhythmInfo.getIntellectualPoint() * 100));
 }
}

もちろんここでもコンパイルとテストを行って,最初と同じ結果になることを確認します。これで,最終的にクラスは当初の一つだけから,SimpleBiorhythmクラス,BiorhythmInfoクラス,BiorhytmCalculatorクラスの三つになりました。リスト10の(1)の部分を見ると,バイオリズムの計算がシンプルかつスマートに実現されていることがわかりますね。コメントがなくてもわかりやすいですし,これならデスクトップ表示やグラフィックス表示のためのプログラムも簡単に作れそうです。拡張性や再利用性というリファクタリングの目的は,達成できたと言っていいでしょう(注16)。

ではこの辺で,リファクタリング作業は一段落しましょう。まだ直したいところも見つかりそうですが,プログラムを拡張する準備もできたようですから,そろそろそちらの作業に取りかかりましょう。もし後からリファクタリングをする必要性が出てきたら,そのときの状況に応じてまた手をつければいいのです。

リファクタリングで既存プログラムを有効活用しよう

いかがだったでしょうか? 「プログラムの設計は,最初に作るときに行い,後から変えるのはよくない」「正しく動いているコードには触るな」----従来はそれが常識でした。リファクタリングは,そうしたタブーを破る考え方と言ってもいいでしょう。

リファクタリングのテクニックは,一つひとつは単純なものです。しかし既存のコードに影響を及ぼさないように少しずつ修正を加えていくスタイルは,意外に強力な武器となります。動いているプログラムを有効活用することも重要なのです。

プログラムを長持ちさせるためには,お肌と同じように日頃の手入れが大切です。プログラムの手入れを怠るとそのうちに荒れ放題になってしまいます。リファクタリングのテクニックを身に付けると,皆さんのプログラミング・スキルもきっとワンランク上になると思いますよ。

  • 注1:確かに,リファクタリングと言う言葉を一般の辞書で引いても見つからない。因数分解などを意味するfactoringをもじった造語らしい。もし語源をご存知の方がいたら教えてほしい。
  • 注2:リファクタリング:プログラミングの体質改善テクニック」,マーチン・ファウラー著,児玉 公信,友野 晶夫,平澤 章,梅澤 真史訳,ピアソン・エデュケーション発行。著者のWebサイト( http://www.refactoring.com/ )では,新しいカタログなどの最新情報が公開されている。
  • 注3:英語でデバッグ(debug)は「欠陥を除去する」の意味。
  • 注4:数カ月後に自分で作ったプログラムを修正するとき,細かいロジックまで覚えているとは限らない。
  • 注5:xUnitは主にプログラムの単体(ユニット)テストを支援するテスティング・フレームワークの総称で,Java用のJUnit,C++用のCppUnit,Visual Basic用のVBUnitなど,さまざまなプログラミング言語用のフレームワークがある。ちなみに,xUnitがなければリファクタリングを行ってはならない,ということはない。
  • 注6:前述の書籍「リファクタリング」も,非常に小さな修正の単位でまとめられている。
  • 注7:構成管理ツールとしては,オープンソース・ソフトウエアのCVS,マイクロソフトのVisual SourceSafeなどがある。
  • 注8:リファクタリングのカタログを学ぶことは,新規にプログラムを作るときの参考にもなる。
  • 注9:せっかくリファクタリングをしても,将来その部分に修正が入らなければ,あまり意味がない。
  • 注10:もちろん特別なタイミングがなくても,何かのきっかけでリファクタリングを行うのは悪いことではない。
  • 注11:要素の数値(ポイント)が0に近い日は,調子が不安定となるため要注意日と言われている。
  • 注12:本来ならば,実行時引数が正しく設定されているかどうかを判定するロジックを組み込む必要があるが,今回は省略した。
  • 注13:このリファクタリングは,前述のカタログには載っていない。しかしこれとよく似た「メソッド名の変更」というリファクタリングがある。
  • 注14:グレゴリオ暦を表すjava.util.GregorianCalendarクラスに同名のメソッドがあるが,今回のサンプルでは利用していない。
  • 注15:Javaではクラスとして独立させることにより,一般的にファイルも別ファイルになるため,再利用しやすくなる。
  • 注16:記事で紹介しているバイオリズムのJavaプログラムは,日経ソフトウエアのWebサイト( http://software.nikkeibp.co.jp/ )からダウンロードできる。リファクタリング以前のものから,五つのリファクタリングそれぞれの段階のコードもすべて収録しているので,ぜひリファクタリングの過程を実感してほしい。
アーカイブス一覧へ