リファクタリングRubyを読んでいる

社のもくもく会リファクタリングRubyを読んでいる。 4章まで読み進めていて、以降の章はリファクタリングの実際的な手法の話になるため、プロダクションのコードを見ながら本書で紹介されている手法が適用できないかどうか考えながら読み進めるつもりだ。

ここには、1章から4章まで読んだメモをまとめておく。

優れたコードは書き換えやすい ソフトウェア開発は有機的で持続的な活動である

リファクタリングとは

コードの外から見た振る舞いを変えずに、内部構造を改良することによってソフトウェアシステムを変えていくプロセス

本質的には、コードが書かれた後でコードの設計を改良しているということ

インデックス

1章 リファクタリングの本質を理解したければ読め

2章 リファクタリングの一般原則、定義、リファクタリングする理由(なぜリファクタリングが必要なのか?)

3章 Kent Beckの力を借りてコードの「臭い」を見つける

4章 コードにテストを組み込む方法

5〜12章 リファクタリングのカタログ(リファレンスなので一度に全部読むのは辛そう。実際にリファクタリングが必要になったら読む。※グロ放題とかグロプラのリファクタリングに役立ちそうな章を選んで読んでから、実際にPRを作ってみるという夢)

1章

  • 基本的に、メソッドは(そのメソッドが使用している)データを持つオブジェクトに割り当てるべき
  • 新メソッドに処理を委ねるという形で古いメソッドを残すことがある(古いメソッドが公開メソッドで、他のクラスのインターフェイスを書き換えたく無い時に役立つ)
  • 一時変数を使用してメソッド呼び出しの戻り値を代入しているが、その後何も変更を加えていない → 「一時変数から問い合わせメソッドへ」(一時変数を取り除く)
    • 一時変数を取り除くことでメソッド呼び出しが増え、パフォーマンスが下がるのでは無いかという懸念があるかもしれないが、リファクタリングの時にこのような考え方をするべきでは無く、リファクタリングをしている時にはわかりやすくすることに集中するべきである
    • 一時変数は無用に多くの引数をやり取りする原因になるという点で、無い方が良い
    • 一時変数が役に立つのは、それが含まれているルーチンの中だけなので、一時変数は長くて複雑なルーチンが作られることを助長する
    • 一時変数を使う代わりに新しいメソッドを作ってそれを呼び出す(これも、「一時変数から問い合わせメソッドへ」)
    • まずコードをわかりやすくしてから、プロファイラを使ってパフォーマンス問題に取り組む(コードをわかりやすくするのが先)
  • メソッド切り出しのリファクタリングでは、新メソッドの冪等性(何度実行しても同じ結果になる性質)に注意を払うべき
  • 「ループからコレクションクロージャメソッドへ」を使ってコードを簡潔にする
# リファクタ前(ループ)
def total_charge
  result = 0
  @rentals.each do |rental|
    result += rental.charge
  end

  result
end
# リファクタ後(コレクションクロージャ)
def total_charge
  @rentals.inject(0) { |sum, rental| sum + rental.charge }
end
  • 他のオブジェクトの属性に基づいてcase文を書くのは間違っている。case文を使わなければならない場合には、他のオブジェクトではなく自分自身のデータに基づくべき

2章

リファクタリング(名詞)... 外から見えるふるまいを変えずに、ソフトウェアをわかりやすくし、安いコストで変更できるようにするために、ソフトウェアの内部構造に加えられる変更

リファクタリングする(動詞)... 外から見えるふるまいを変えずに、一連のリファクタリングを適用してソフトウェアの構造を変えること

Kent Beckの2つの帽子

リファクタリングを活用してソフトウェアを開発するときは、機能の追加とリファクタリングという2つの別々の活動のために時間をはっきりと区別すべきであるという考え方を説明する比喩。

機能の追加をする時には既存のコードを書き換えず、新しい機能を追加することに専念する。テストを追加し、テストが成功するようになるということによって作業の進度がわかる。

リファクタリングをするときは、機能を追加しないように努力し、コードの改造だけに力を注ぐ。以前は見落としていた条件に気づいた場合、インターフェイスの変更に対処するためにどうしても必要な場合以外ではテストの追加、変更を行ってはならない。

プログラムが扱いにくくなる4つの要因(Kent Beck

  • 読みにくいプログラムは書き換えにくい
  • ロジックの重複があるプログラムは書き換えにくい
  • 機能の追加によって、動いているコードを書き換えなければならなくなるようなプログラムは書き換えにくい
  • 条件分岐が複雑なプログラムは書き換えにくい

リファクタリングと設計

柔軟な解は、単純な解よりも複雑になるのだ。このような設計から作られたソフトウェアは、最初に考えた方向では対応しやすいが、一般にメンテナンスがかえって難しくなってしまう。

対応できる場合でも、設計にどのように手を入れたら良いかを理解しなければならない。... 柔軟な解をすぐに実装するのではなく、「リファクタリングで単純な解を柔軟な解に帰るのはどの程度難しいだろうか」ということを考える。答えが「かなり簡単」であれば(ほとんどの場合はそうなるが)、単純な解を実装すれば良い。(p92)

3章 リファクタリングによって解決できる問題点の兆候

問題点の兆候を簡単にまとめたけど、本読むと解決策も書いてあるので本を見る

  1. コードの重複
  2. 長いメソッド
    1. 古い言語ではサブルーチン呼び出しにかかるオーバーヘッドが大きかったため、メソッドを小さく分割することがなかなかできなかった
    2. 小さく分割したメソッドを人間が理解しやすくするために、メソッド名の付け方に気をつける。名前がよければ中身を見る必要がない
    3. メソッド名はコードの仕組みではなく、目的に基づいて付けられる
    4. メソッド名がコードの目的を説明しているなら、置き換えられるコードよりもメソッド呼び出しの方が長くても、メソッド呼び出しに変えて良い
  3. 大きなクラス
    1. 一般的に、クラス内のサブセットの変数に共通のプレフィックスサフィックスが付けられている場合には、コンポーネントにまとめられる可能性がある
  4. 長い引数リスト
  5. 変更系統の分岐
    1. あんまりピンとこなかった
  6. ショットガン創の手術
    1. 1つの種類の変更をしようとするたびに、様々なクラスに無数の小さな変更を加えなければならないとき
    2. すべての変更点が1つのクラスにまとまるようにメソッドの移動やフィールドの移動をする
  7. メソッドの浮気
    1. メソッドが自分の所属クラス以外のクラスにやたらと関心を持っているように見える場合
    2. メソッドの移動をして、気になっているクラスに移動する
    3. メソッドが複数のクラスのメンバを使っている場合は、メソッドの抽出をして、それぞれ別のクラスに移動できるようにする
  8. 群れたがるデータ
    1. 連れ立って行動する一連のデータは、それら専用のクラスを持つようにした方が良い
  9. プリミティブ強迫症
    1. プリミティブ型(Stringとか)をちょっとだけ組み合わせた小さいクラス(金額クラスとか)を作っていいんやで(データ値からオブジェクトへ)
  10. case文
    1. case文が比較的少ないことがオブジェクト指向らしいコードの特徴
    2. ポリモーフィズムの概念を使えばエレガントに処理できる
  11. パラレルな継承階層
    1. あるクラスのサブクラスを作ると、別のクラスのサブクラスも作らなければならなくなること
  12. 仕事をしないクラス
    1. 名誉の死を
  13. 空論的一般化
    1. 人間「いずれこういったこともできるようにする必要があると思うよ」 → 使われていないのなら、残しておく必要がない。からくりは邪魔なだけであり、取り除くべきだ
  14. 一時フィールド
  15. メッセージの連鎖
  16. 横流しブローカー
    1. オブジェクトのカプセル化が行きすぎて、クラスの半分以上のメソッドが他のクラスに処理を委譲しているような場合
    2. 何が行われるか実際に知っているオブジェクトとやり取りをする
  17. 親密すぎるクラス
    1. 2つのクラスが親しくなりすぎて、非公開なAPI部分に長々と入り込んでしまう場合
  18. インターフェイスの異なるクラス群
  19. 不完全なライブラリクラス
    1. Rubyオープンクラスなのでライブラリに不満があったら自分で解消できる
  20. データクラス(属性だけを持っているクラス)
  21. 継承した遺産の拒絶
  22. コメント
    1. コメントを書かなければならないという感じがした時には、まずコメントが不要になるまでコードをリファクタリングする
    2. コメントが効果的に使えるのは、何をしたら良いかはっきりしていない時。よくわからないことが何かを示すことができる
  23. メタプログラミング
    1. method_missing...
  24. 柔軟すぎるAPI
    1. ?
  25. 紋切り型コードの繰り返し
    1. クラスアノテーションの導入なコードの意図を明確にする

Rubyの書き方的な話 https://techracho.bpsinc.jp/hachi8833/2017_05_15/38869

4章 テストの構築

自分で開発するときには、その過程でテストを書いていくが、他のプログラマリファクタリングの仕事をするときには、テストのないコードを相手にしなければならない。そこで、リファクタリングする前に、コードを自己検証コード(自分自身のテストを行う)にしなければならない。

(そのAPIを)テストをするかどうかはリスクによって決めるべきだ。現在または将来に現れそうなバグを見つけるためにテストをしているのだということを忘れてはならない。

まとめ

リファクタリング」が意味するところをはっきりと言葉にしてくれていたのがよかった。(コードの外から見た振る舞いを変えずに、内部構造を改良することによってソフトウェアシステムを変えていくプロセス

ガンガンリファクタリングしていくぞい!