テストとリファクタリングがよい設計を作る -創発的設計の考え方-
クリーンコードに「創発」という気になる名前の章があったので読んでみた。ざっくりいうと、とりあえず動くものを作ってから後から設計するという考え方で、私も最近はこういった感じで開発している。
創発的設計
よい設計を最初に考えるのではなく、コードを改善しながらよい設計にたどり着くという設計の仕方がある。
ケント・ベック氏は下記の規則に従うと、単純な設計が行えると主張している。
- 全テストを実行する
- 重複をなくす
- プログラマの意図を表現する
- クラスとメソッドを最小限にする
全テストを実行する
テストを記述することは、設計を改善する。
最初に記述したコードに対するテストが書きにくい場合、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つの規則を満たせる中で、メソッドとクラスの数が最小限になるようにする。
参考文献
Robert C.Martin, Clean Code アジャイルソフトウェア達人の技, KADOKAWA.
Discussion