テストとリファクタリングがよい設計を作る -創発的設計の考え方-
クリーンコードに「創発」という気になる名前の章があったので読んでみた。ざっくりいうと、とりあえず動くものを作ってから後から設計するという考え方で、私も最近はこういった感じで開発している。
創発的設計
よい設計を最初に考えるのではなく、コードを改善しながらよい設計にたどり着くという設計の仕方があります。
ケント・ベック氏は下記の規則に従うと、単純な設計が行えると主張しています。
- 全テストを実行する
- 重複をなくす
- プログラマの意図を表現する
- クラスとメソッドを最小限にする
全テストを実行する
テストを記述することは、設計を改善します。
最初に記述したコードに対するテストが書きにくい場合、2つの可能性が考えられます。
- 一つのクラスやメソッドでいろいろなことをしすぎている
- クラスやメソッドの結合度が高い
そういった場合、下記のようにコードを修正することでテストをしやすくできます。
- クラスを小さくする
- メソッドを小さくする
- 1つのクラスが単一の機能を実装するようにクラスを分割する
- クラス同士の結合度を下げる
- インターフェースやクラスの依存関係を単純にする
このようにして、テストを単純に記述することを意識すると、自然とSRP(単一責任の原則)やDIP(依存関係逆転の原則)に導かれます。
重複の排除
下記のようなクラスがあるとする。imageはImage
クラスのインスタンスである。
class ImageContainer def initialize(image) @image = image end def scale_to_one_dimesion(desired_dimesion, image_dimension) return if (desired_dimesion - image_dimension) < error_threshold scaling_factor = desired_dimension / image_dimension scaling_factor = scaling_factor.floor new_image = ImageUtilities.get_scaled_image(@image, scaling_factor, scaling_factor) @image.dispose System.gc() @image = new_image end def rotate(degrees) new_image = ImageUtilities.get_scaled_image(image, degrees) image.dispose System.gc() image = new_image end end
明らかに重複したコードがあるので、replace
メソッドに抽出する。
class ImageContainer def initialize(image) @image = image end def scale_to_one_dimesion(desired_dimesion, image_dimension) return if (desired_dimesion - image_dimension) < error_threshold scaling_factor = desired_dimension / image_dimension scaling_factor = scaling_factor.floor replace_image(ImageUtilities.get_scaled_image(@image, scaling_factor, scaling_factor)) end def rotate(degrees) replace_image(ImageUtilities.get_scaled_image(@image, degrees)) end def replace_image(new_image) @image.dispose System.gc() @image = new_image end end
こうしたことで、replaceメソッドは、imageに関する問題のみを扱っていることが明らかになった。すなわち、replace_imageメソッドはImageContainerではなくImageが持つべきメソッドであることがわかる。
重複を排除したことによって、クラスが持つべき責務を明らかにして、あるべき状態に変えることができる。
class Image def replace(new_image) @image.dispose System.gc @image = new_image end end class ImageContainer def initialize(image) @image = image end def scale_to_one_dimesion(desired_dimesion, image_dimension) return if (desired_dimesion - image_dimension) < error_threshold scaling_factor = desired_dimension / image_dimension scaling_factor = scaling_factor @image.replace(ImageUtilities.get_scaled_image(@image, scaling_factor, scaling_factor)) end def rotate(degrees) @image.replace(ImageUtilities.get_scaled_image(@image, degrees)) end end
プログラマの意図を表現する
他のプログラマに、自分のコードの意図が分るようにすることも、設計を理解しやすくする上で重要。
- コードの保守性は、「書き手の意図のわかりやすさ」によって決まる
- クラス、メソッドを小さくし、適切な名前をつけることで、意図が伝わりやすくなる
- デザインパターンのような一般に知られた概念を流用することも、意図を示すのに利用できる
クラスとメソッドを最小限にする
コードの重複排除、SRPの実践というのは、やりすぎるとむやみにメソッドとクラスを増やしすぎることになる。多すぎるクラスとメソッドは、コードディングの作業を増やし、構造を複雑にしてしまう。
先に挙げた3つの規則を満たせる中で、メソッドとクラスの数が最小限になるようにする。