とり研

iPhoneアプリ開発とかプログラミングとかの話題。

「リファクタリング 既存のコードを安全に改善する」を読んだ

(※この記事は、会社の勉強会での発表を元に起こしたものです。そのため、若干端折って書いております)

内容

こちらの本の紹介です。

[asin:B01IGW5MG0:detail]

今なら、第2版が出ています。言語がJavaからJavascriptに変更になりました。

読んだ動機

  • 「リファクタリング」って言うけど、やってることは人それぞれだよね
    • もやっとする
    • 体系的におさらいする方がいいのでは?
  • 「リファクタリング」の基本を学びたい
  • 長い間積ん読であったが、コロナ自粛期間中に読んでしまおうと思った

そもそもリファクタリングとは

この本の定義では、リファクタリングとは↓

リファクタリング(名詞) 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること

動詞バージョンはこちら↓

リファクタリングする(動詞) 一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること

リファクタリングを行う理由

  • ソフトウェア設計を改善する
  • ソフトウェアを理解しやすくする
  • バグを見つけ出す
  • より早くプログラミングできる

いつリファクタリングをすべきか

  • 3度めの法則
    • 似たようなコードを3回書いたら、まとめる
  • 機能追加の時
    • 理由1. 既存機能を理解するため
    • 理由2. 容易に機能追加出来ない設計を治す→機能追加作業
  • バグフィクスの時
    • 理由1. 既存機能を理解するため
  • コードレビューの時

リファクタリングの注意事項

  • 対象のユニットテストを書いてからリファクタリングすること
  • リファクタリングはバグの原因になりうる (これは、私の実体験でも多々あります...)

リファクタリング例

6章〜12章、270ページに渡りリファクタリングのパターンが紹介されているが、その中で 自分的にびっくりした手法 をご紹介します。

リファクタリング前のコード

※本に載っていた例を簡略化したものです。

// レンタルビデオ店で、レンタル商品の合計金額と獲得ポイントを計算・表示する機能
int amount = 0; // 合計金額
int points = 0; // 獲得ポイント

while(rentals.hasMoreElements()) {
  Rental each = rentals.nextElement();

  amount += each.getPrice();
  points++;
}

// 画面に表示
amountTextField = "合計金額は" + amount + "円です。";
pointTextField = "獲得ポイントは" + point + "ptです。" ;

上のコードの改善点はどこでしょうか...?
(1分間考えてみてください)
.
.
.
.
.
.

リファクタリング後のコード

// レンタルビデオ店で、レンタル商品の合計金額と獲得ポイントを計算・表示する機能
// 画面に表示
amountTextField = "合計金額は" + getAmount(rentals) + "円です。"   
pointTextField = "獲得ポイントは" + getPoints(rentals) + "ptです。" 

/// 合計金額を求める
func getAmount(rentals[Rental]) {
  int amount = 0; // 合計金額
  while(rentals.hasMoreElements()) {
    Rental each = rentals.nextElement();
    amount += each.getPrice();
  }
  return amount;
}

/// 獲得ポイントを求める
func getPoints(rentals[Rental]) {
  int points = 0; // 獲得ポイント
  while(rentals.hasMoreElements()) {
    Rental each = rentals.nextElement();
    points++;
  } 
  return points;
}

こういうコードは、「ループ回数が増えるので、良くない」と思っていたので、このように修正するのは意外でした...。

なぜこうすると良いのか?

  • ユニットテストが書ける
  • 修正前は、結果を直接UI要素に入れているので、テストは基本的に手動で行うしかない
  • 修正後は、計算処理がメソッドになっていて、入力と出力があるので、ユニットテストが書ける
  • 今回のような単純計算なら良いが、ポイントが複雑になってくると、いちいち手動テストするのはしんどい(* 例えば、「旧作映画を10本同時レンタルしたら+100ポイントキャンペーン」とか...)
  • 最近の計算機は十分速いので、この程度のループが増えても影響は少ない
    • 「1億本レンタルする人がいる」とかなら、大変そうですが...。

まとめ

  • リファクタリングのもやもやがスッキリ
  • なぜ行うのか?などの理由付けになる
  • 定番の技術書を読むのも良い
  • 「一般的に、こういうコードが良いコード」という安心感が得られる
  • 今回の例のようなコードは、自分は良くないコードと思っていたが...と気づくきっかけ。この本を読んでいなければ、気づかなかった