リファクタリング:Rubyエディション を読んだ。 〜 6章まで
- 作者: Jay Fields,Shane Harvie,Martin Fowler,Kent Beck,長尾高弘
- 出版社/メーカー: アスキー・メディアワークス
- 発売日: 2010/02/27
- メディア: 大型本
- 購入: 9人 クリック: 321回
- この商品を含むブログ (47件) を見る
久しぶりにリファクタリングについて勉強したくなったので、 読んで、重要と思った部分をメモ書きする。
第1章 リファクタリング
一時変数をメソッドにリファクタリングする際にパフォーマンスの低下が懸念されるかもしれない。(P41)
- そんなに問題にならないケースが多い。
- できるだけ、リファクタリングは保守性を重視して、読みやすく変更を加えてやるべき。
- 一時変数は無用に多くの引数をやりとりする原因になる点でない方が良い。
- 一時変数を取り除けば、コードが何を使用としているのかがはっきりと分かるようになる。
※ 実際は一時変数をメソッドに置き換えて、リファクタリングをする人がどれくらいいるのか調べてみても良いかもしれない。
リファクタリングの問題点
インターフェースを公表済みの場合
- 公表済みのインターフェースをリファクタリングをするのは、それの周知にコストがかかる。
- したがって、普通は旧インターフェースと新インターフェースを両存させる。
第6章 メソッドの構成
メソッドの抽出
コードの断片をメソッドにして、その目的を説明する名前をつける。
利点
Tips
一時変数から問い合わせメソッドへ
式をメソッドにする。一時変数の全ての参照箇所を式に置き換える。新しいメソッドは他のメソッドからも使える。
利点
一時変数からチェインへ
チェイニングをサポートするようなメソッドに書き換え、一時変数を不要にする。
利点
- 不要な一時変数を削除できる。
- 自然に読めるようにコードを組み合わせられるインターフェースが増えるので、保守性が向上する。
Tips
- チェイニングできるようにしたいメソッドからはselfを返すように実装する。
- 一つのオブジェクト内でチェインは完結させたほうが良い。そうすることによって、表現力を高めることができる。
説明用変数の導入
処理の目的を説明するような名前を持つ一時変数に式、または式の一部の結果を代入する。
利点
- 複雑な条件式が読みにくい場合は一時変数を使えば、式を管理しやすい形に分解出来る。
Tips
一時変数の分割
代入毎に別々の一時変数を用意する。
利点
- ループ変数や結果蓄積用変数でないにもかかわらず、同じコンテキスト内で同名変数を使用すると、読み手が混乱するため、必ず別々の変数名を用意すること。
メソッドからメソッドオブジェクトへ
「メソッドの抽出」ができないようなローカル変数の使い方をしている長いメソッドがあるとして、メソッドを独自のオブジェクトに変え、全てのローカル変数がそのオブジェクトのインスタンス変数になるようにする。こうすれば、メソッドを分解して、同じオブジェクトの別のメソッドにすることが可能になる。
利点
- ローカル変数はメソッドオブジェクトの属性になり、このオブジェクトを対象として「メソッドの抽出」を行うと、元のメソッド分解して小さなメソッドを作ることが可能になる。
- 作成したオブジェクトのメソッドに処理を委譲することで、引数渡しの心配をせずにメソッドの抽出が可能になる。
ループからコレクションクロージャメソッドへ
利点
- コレクションの中を行き来したり、派生コレクションを作ったりするためのインフラストラクチャコードを隠し、ビジネスロジックに集中できるようにしてくれる。
Tips
managerOfiices = employees.select {|e| e.manager? }. collect{|e| e.office }
- 合計の計算のように、ループ内で一つの値を生み出すような処理をしなくてはいけない時には、
injectメソッドを使用する。
total = employees.inject(0) {|sum, e| sum + e.salary}
サンドイッチメソッドの抽出
重複部分を抽出して、ブロック付きのメソッドにする。この種のメソッドは呼び出し元に一時的に制御を返してくる。呼び出し元は、制御を返された時に実行するコードをブロック内に記述する。
利点
- 前後の重複部分を抽出してメソッドにまとめることができる。
- インフラストラクチャコード(コネクションを反復処理するためのコード)を隠して、ビジネスロジックを全面に押し出すことができる。
- つまり、公開メソッド内に保つことができるのでメンテナンスがしやすくなる。
クラスアノテーションの導入
クラス定義からクラスメソッドを呼び出してふるまいを宣言する。
利点
- 宣言的な構文でコードの目的が明確につかめる場合には、「クラスアノテーションの導入」を使うと、コードの意図を明確にできる。
- 有名な例としては、属性アクセッサが挙げられる。
Tips
- 様々なクラスで使いまわせそうであれば、Moduleとして抽出して、Classクラスに移した方が良い。
名前付き引数の導入
引数リストをハッシュに変換し、ハッシュキーを引数の名前として使う。
利点
- 他のオブジェクトに処理を移譲をしようする際に、渡す引数の役割が明確になっていれば(公開インターフェースが明確な振る舞いをきちんと表現していれば)、移譲先のオブジェクトの詳細を深く見なくても済む。
Tips
- アサーションの導入すると、下記2点の利点がある。
- 渡さなければいけない引数の種類の明確化
- キーの綴りの間違い時の検出
module AssertValidKeys def assert_valid_keys(*valid_keys) unknown_keys = keys - [valid_keys].flatten if unknown_keys.any? raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(",")}") end end Hash.send(:include, AssertValidKeys) # Hashオブジェクトを拡張する。 Class Books def self.find(selector, hash={}) # ここでhashに渡すキーの種類が確認できる。 # 誤ったキーが与えられるとArgument Error が発生する。 hash.assert_valid_keys :conditions, :joins # 以下、省略
Books.find(:all) Books.find(:first, :conditions => "authors.name = 'Jerry James'", :joins => [:authors])
- 呼び出し元のコードが分かりやすくなるという利点がある一方で、呼び出されるメソッドが複雑になるコストもかかってしまうので、呼び出されるメソッドをそこまで複雑にする必要性がない場合は、名前付き引数を取り除く
動的メソッド定義
メソッドを動的に定義する。
利点
- 読みやすくメンテナンスし易い形式でメソッド定義を簡潔に表現できる。
Tips
class Class def def_each(*method_names, $block) method_names.each do |method_name| define_method method_name do instance_exec method_name, &block end end end end
def_each :failure, :error do |method_name| self.state = mathod_name end
- クラスアノテーションを使用すれば、より表現力の高いコードが書ける。
class Post def self.states(*args) args.each do |arg| define_method arg do self.state = arg end end end states :failure, :error, :success end
- 動的に定義されたモジュールをextendしてメソッドを定義する。
class to_module def to_module hash = self Module.new.do hash.each_pair do |key, value| define_method key do value end end end end end
class PostData def initialize(post_data) self.extend post_data.to_module end end
動的レセプタから動的メソッド定義へ
利点
- method_missingを利用したクラスのデバッグは悲惨になりやすく、最悪の場合スタックレベルが深くなりすぎてエラーになる。オブジェクトがどのように使われているかわかっているケースではmethod_missingを使わずに振る舞いを実現できる。
Tips
- method_missingを使わない動的な移譲
- 無効なメソッド呼び出しはデコレータのNoMethodErrorsとして正しく報告される。
- method_missing定義もないため、スタックレベルが深くなりすぎることもない。
class Decorator def initialize(subject) subject.public_methods(false).each do |meth| (class << self; self; end).class_eval do define_method meth do |*args| subject.send meth, *args end end end end end
属性の中にnilのものがあるかどうかを判定する。
class Person def self.attrs_with_empty_predicate(*args) attr_accessor *args args.each do |attribute| define_method "empty_#{attribute}?" do self.send(attribute).nil? end end end attrs_with_empty_predicate :name, :age end
動的レセプタの分離
新しいクラスを導入し、method_missingのロジックをそのクラスに移す。
利点
- 新しいクラスにメソッドmethod_missingを移動させることによって、method_missingを利用することで発生するデメリット(予想外のNoMethodErrorの頻発やSystemStackErrorを発生)を抑えることができる。