リファクタリングのエッセンス

ギルドワークスの増田です。

ソフトウェア設計の目的は「変更コスト」を下げることです。変更が容易なソフトウェアは、発展性に富み、生き生きとした活力を保ち続けます。変更がやりやすいソフトウェアは、事業やサービスの成功をもたらす原動力になります。変更がたいへんなソフトウェアは、しだいに活力を失っていきます。誰もさわることができなくなり、しだいに、事業やサービスの足かせになっていきます。

変更コストの大きな違いを生むのは、プログラミング言語/フレームワーク/開発ツールの違いでありません。ちょっとしたコードの書き方の違いの積み重ねが、ソフトウェアの変更コストに大きく影響します。

※注意:この記事は2014年9月9日に GuildWorks Blog で公開したエントリを移行したものです。

変更がたいへんになりそうなコード

if(date.isBefore(SUMMER_START)||date.isAfter(SUMMER_END))      charge = quantity * winterRate + winterServiceCharge;   else      charge = quantity * summerRate; 

冬季と夏季で異なるサービス料金の計算をしています。このコードは、それほど複雑ではありません。しかし、場合分けが増えたり、計算ルールが追加されると、if文が入れ子になったり、条件文の論理演算式が、膨らんでいきそうです。

このまま、このコードを変更し続けると、しだいに、コードがわかりにくくなり、変更が困難なプログラムになりかねません。

変更をやりやすく改善したコード

if(   notSummer  (date))      charge =   winterCharge  (quantity);  else      charge =   summerCharge  (quantity);...private boolean   notSummer  (LocalDate date){    return date.isBefore(SUMMER_START)||date.isAfter(SUMMER_END);}private int   winterCharge  (int quantity){    return quantity * winterRate + winterServiceCharge;}private int   summerCharge  (int quantity){    return quantity * summerRate;} 

これは、マーチンファウラの「リファクタリング」にでてくる「条件記述の分解(p.238)」です。条件の記述/then部/else部を、それぞれ「メソッドに抽出」した例です。

改善前のコードと比べてください。改善前のコードは、条件判断の論理演算式と料金の計算式を、そのままべた書きしています。リファクタリング後(設計改善後)では、メインのif文は、メソッドを呼び出すだけに単純化しました。それぞれの演算式の詳細は、メソッドに隠ぺいしました。

設計改善後のコードのほうが、変更がやりやすくなっています。

  • 場合分けの構造がわかりやすい(場合分けの変更がやりやすい)
  • それぞれの計算式を分けて宣言しているので、独立して変更しやすい

変更する時に、うっかりバグを混入させるリスクも減っていそうです。

さらなる改善

メソッドの抽出による変更のやりやすさの改善は、手続き型プログラミングのスタイルの延長にある改善方法です。オブジェクト指向の設計スタイルだと、このコードは、さらに、変更に強く改善できます。

notSummer()メソッドは「変更コスト」を考えると、いやな臭いがします。冬季・夏季の判断が、プログラム全体の中で、このクラスのこの場所でしか必要ないなら、このままでも良いでしょう。しかし、実際には、あちこちで、冬季・夏季の判断が必要になりそうです。そして、あちこちに、同じような論理演算式が重複して登場しそうです。

冬季・夏季を判断する式の重複は「変更地獄」のはじまりです。夏季の判断ルールが変更になったとき、プログラムのあちこちを調べまわって、すべての重複した判断式を、まちがいなく変更しなければなりません。重複が増えるほど、対象箇所の見落としや、変更の間違いのリスクが膨らんでゆきます。

コードの重複を防ぐためのオブジェクト指向らしい改善

if   (date.notSummer()  )charge = winterCharge(quantity);elsecharge = summerCharge(quantity);...   class ServiceDate  {  private final LocalDate date;  ServiceDate( LocalDate date )  {    this.date = date;  }...  boolean   notSummer  ()  {    return date.isBefore(SUMMER_START)||date.isAfter(SUMMER_END);  }} 

サービスを実行する日を表現するServiceDateクラスを作成しました。夏季か冬季かの判断の論理演算式を、元のクラスから、ServiceDateクラスに移動しました。

ServiceDateクラスにロジックを移動したことにより、プログラムの中で、冬季・夏季の判断式が登場するのは、このクラスだけになります。プログラムのあちこちで冬季・夏季の判断ロジックが重複する心配はありません。こういう構造になっていれば、冬季・夏季の判断ルールの変更は、簡単で安全な作業になります。

オブジェクト指向設計とは、こういうちょっとした工夫を実践的に積み重ねることです。コードをわかりやすく整理し、コードの重複をなくし、プログラムの変更を楽に安全にする実践的な工夫がオブジェクト指向設計なのです。

現場で、コードレベルで、実際に

ギルドワークスでは、こういう設計の考え方ややり方を学ぶためのワークショップやセミナーを定期的に開催しています。また、みなさんの現場に出かけて実際のコードを題材にやってみるオンサイトのワークショップにも力を入れています。ご興味を持たれた方は ギルドワークスのホームページ からお気軽に お問い合わせ ください。

この記事をシェア