こんにちは。
今回は、リファクタリング Rubyエディション を読みました。
本書は様々なリファクタリングのパターンについて、Rubyを使って説明している名著です。
学ぶことが多かったので、ポイントを定期的に見返せるよう記事にまとめました。
特にいいなと思ったリファクタリングのパターンは、サンプルコードを使って解説したブログへのリンクを貼っていますので、気になるパターンがあれば合わせてご覧ください。
リファクタリングについて
- リファクタリングとは、外から見えるふるまいを変えずに、ソフトウェアの内部構造を変えること
- リファクタリングの目的は、ソフトウェアをわかりやすくし、安いコストで変更できるようにすること
リファクタリングの基本的な考え方
-
ほとんどのリファクタリングは、プログラムに間接的な階層を導入する。つまり、大きなオブジェクトや大きなメソッドを分解して、複数の小さなオブジェクトやメソッドにしている。間接化によって以下のメリットが得られる
-
ロジックの共有を実現する
-
メソッド名などで意図を説明できる
-
変更箇所を他の部分から分離できる
-
-
プログラムが扱いにくくなる要因としては以下の4つがあり、これらが存在した場合はリファクタリングを検討すべき
-
読みにくい
-
ロジックの重複がある
-
機能の追加によって、動いているコードを書き換えなければならなくなる
-
条件分岐が複雑
-
-
ソフトウェアでは、使われていない柔軟性は悪である。この柔軟性は、メンテナンスのために時間を食い、バグが入る余地を作り、リファクタリングをかけにくくする
リファクタリングの基本的な進め方
-
-
機能を追加するときには、既存のコードを書き換えない。ただ新しい機能を追加することに専念する。最初に失敗するテストを追加し、テストを成功させる
-
リファクタリングする時には機能を追加しないようにし、コードの改造だけに力を注ぐ。テストの追加も行わない
-
-
バグレポートを受け取ったら、まずバグが浮かび上がるようなテストを書く。バグを突き止めるとともに、同様のバグがテストをすり抜けないようにするため
パフォーマンスについて
-
システムの内部で何が行われているかを正確に知っていたとしても、パフォーマンスは推測せずに計測すべき。推測の10回に9回は正しくない
-
パフォーマンスの面白いところは、実際に分析してみると、実行時間の大半がコードのごく小さな一部で消費されていることである。コード全体に平等に最適化をかけると、実行時間の短いコードを最適化することになり効率が悪い
具体的なリファクタリング
名前の変更
- 名前の変更の価値は大きい。優れたコードは、自分がしていることを明快に伝えられなければならないが、変数名やメソッド名はそのための鍵を握っている
- 分かりやすくするために、名前を書き換えることをためらってはならない
メソッド・フィールドの移動
- メソッドは、そのメソッドが使用するデータを持つクラスに割り当てるべき
- 条件分岐は、他のオブジェクトでなく自分自身のデータに基づくべきであり、他のオブジェクトのデータで条件分岐している箇所はクラスの移動を検討する
- メソッドやフィールドをどのオブジェクトに管理させるかは、オブジェクトの設計でもっとも重要な判断の1つである。最初から正解にたどり着くのは難しいので、リファクタリングを使って設計を変えていけばよい
メソッドの抽出
- 何かコメントを書くべきと感じる場合、コメントではなくメソッドに抽出することを考える。このときメソッド名はコードの仕組みではなく、目的に基づいてつける。メソッド名がコードの目的を説明しているなら、たった1行のコードであってもメソッド呼び出しに変える価値がある。コメントを探すとメソッド抽出すべきところが見つかりやすい
- 値を返すことと、オブジェクトの状態に変化を与えることを同時に行なっているメソッドがある場合、問い合わせ用と更新用のメソッドに分割する。こうすることで再利用しやすくなる
- ほぼ同じコードによる2つのメソッドがあり、違いはメソッドのちょうど中頃にある場合、重複部分を抽出してブロック付きのメソッドにして、違いの部分を yield で実行させると良い。この手法は「サンドイッチメソッドの抽出」という名前がついている(詳細をまとめたブログ記事)
ポリモーフィズム
- case文を見かけたら、ほとんどの場合はポリモーフィズムを利用することを検討すべき。case文の少なさは、オブジェクト指向らしいコードが持つ特徴の1つである。メリットとしては、新しい型を追加した時に存在している全ての条件式を探す必要がなくなり、新しいクラスを作って適切なメソッドを提供するだけでよくなる(詳細をまとめたブログ記事)
- 広い意味では同じようなステップを同じ順序で実行しているように見えるが、ステップが全く同じだとは言えない複数のメソッドは、共通部分をスーパークラスに移してポリモーフィズムに仕事をさせ、異なるステップはそれぞれがサブクラスで実行させる。この種のメソッドをテンプレートメソッドと呼ぶ。Rubyでは、モジュールのextendを使ったテンプレートメソッドの作成も可能である。モジュールをextendするクラスがーパークラスの役割を果たして共通部分を格納し、モジュールが特別なふるまいを実装する(詳細をまとめたブログ記事)
クラス・モジュールの抽出
- クラスが常に全てのインスタンス変数を使うわけではない場合がある。そのような時は、クラスの抽出、モジュールの抽出、サブクラスの抽出を検討する
- 1つのクラスが異なる理由でたびたび書き換えられる時には、理由ごとにクラスを分けることを検討する
- before_save などのコールバックを共通化したいときは、included フックを作ったモジュールに抽出するとよい(詳細をまとめたブログ記事)
- 抽象スーパークラス(インスタンスを生成しないスーパークラス)を作る場合は、スーパークラスをモジュールにすると意図を正しく表現できる(詳細をまとめたブログ記事)
ガード節
- 条件分岐がネストされている場合は、まずはガード節が使えないかを検討する
ファクトリメソッド
- オブジェクトを作成するときに単なる構築以上のことをしたい場合は、コンストラクタを取り除いてファクトリメソッドを作ると良い。例えば、作成するオブジェクトの種類を決めるために条件分岐を使っている場合などで有効となる(詳細をまとめたブログ記事)
委譲
- manager = john.department.manager というコードがあったとする。これだと、manager の管理は Departmant クラスで行なっているということを呼び出し側が知っておかなければならず依存が発生する。委譲メソッドを使って john.manager で取得できるようにすれば、余計な依存を解消することができる(詳細をまとめたブログ記事)
- サブクラスがスーパークラスのインターフェースの一部しか使っていない、あるいはデータの継承を望まない場合は、継承から委譲へ変更することを検討する。スーパークラスのインスタンスを参照するフィールドを作り、スーパークラスのインスタンスに処理を委譲するようにメソッドを修正して、継承構造を解消する(詳細をまとめたブログ記事)
その他
- method_missing を使うとコードがわかりにくくなることが多いので、なるべく使わない方がいい
- オブジェクトの代わりにnullを持っている可能性があり、コード中にnullかどうかの確認をしている箇所が多いとき、nullオブジェクトを導入するのが有効な場合がある(詳細をまとめたブログ記事)
おわりに
ここまで読んでいただきありがとうございます。
この本は今まで読んだ技術書の中でもトップクラスに学ぶことが多かったです。
また、値段もトップクラスでした(笑)
「これなんかリファクタリングできないかなー」という時にこの本が手元にあると役立ちそうなので、 手元に置いて少しずつ身につけていこうと思います。