はじめに
あけましておめでとうございます
今回は昨年末に参加させて頂いたインターンで会社に購入頂いたオブジェクト指向設計実践ガイドを読んだ自分なりの解釈を書いていこうと思います。
SRP編とかタイトルに書いてる今の自分は、正月のやる気に押され連載にする気満々なのでエタらないうちにさっさと書き切りたい。
今回はSRP編です。
SRPとは何ぞや
オブジェクト指向においてなくてはならない原則。単一責任の原則。Single Responsibility Principle.
クラスは自身がしなくてはならない最小のことのみを行い、それ以外のことはするなという原則のこと。
ではこのSRPは何のために必要なのか。
それは 「変更が簡単なようにコードを書く」 ため。
実践
自分の練習も兼ねて本で取り上げられていた例とは違うオリジナルのSRPの実装をやってみる。
試しに人間というオブジェクトを考えよう。ここではクラスとして実装する。
オブジェクトには最低限データと振る舞いの2つがないといけない。
class Human
# これはデータ
attr_reader :weight, :height
def initalizer(:weight, :height)
@weight = weight
@height = height
end
# 適正体重を求めるメソッド(= 適正体重を返すという振る舞い)
def desirable_weight
(height ^ 2) * 22
end
# BMIを求めるメソッド
def bmi
weight + (height ^ 2)
end
end
このHumanクラスはデータと振る舞いを両方備えている。
簡単なHumanクラスでBMIを求めるメソッドを実装してみたので、これを発展させてHumanの肥満度を評価してみたい。
class Human
# ...既に定義したHumanクラス
def obesity_degree
case
when bmi < 18.5 then "やせ"
when 18.5 <= bmi && bmi < 25 then "普通"
when 25 <= bmi then "肥満"
end
end
end
さて、この拡張は正しいのか? SRPを破ってはいないか?
BMIは明らかにHumanの属性。そしてHumanに「Humanさん、あなたの肥満度を教えてください」と尋ねるのも妥当な気がする。
しかしBMIが18.5未満ならやせであるなんてことはHumanは知らなくていい。
つまり obesity_degree
がHumanにいるのは問題ないけれど、その実装はどこかへ分離したい。
しかしメソッドの実装をHumanクラスではないどこかへ移した方が良いことが分かっても「今すぐ」実際にそうすべきかは別の問題なのだ。なぜなら開発チームが今持っている知識で適切な移動先が見つかるとは限らないから。
この程度の規模のコードであれば、開発チームはそのための知識をまだ十分に持っているとは言えないので実装を切り出す決断は(可能なら)十分な知識を得る将来へ先延ばしにすべき。
よって今回はHumanに関係ないロジックが入り込んでいるので、クラスがSRPを守っているとは言えない。ただそれはHumanの他の部分からはcase式の切り出しやすい形で明確に分離されているのでこのままにしておくという結論に(自分は)なった。
データではなく振る舞いに依存せよ
- インスタンス変数はメソッドで包め
- 配列にインデックスアクセスしているのは、SRPを守れていない証拠。どのインデックスにどんなデータが入っているかという知識は別の1つメソッドに詰め込むこと。
まとめ
- オブジェクトはデータと振る舞いの2つを持つ
- 実装がSRPを守れているかオブジェクトに問いただすこと
- これには「オブジェクトさん、あなたのxxxを教えてください」と聞いてみると良い。
- クラス単位であってもメソッド単位であっても。
- 「新しいオブジェクトを作る」、「メソッドを切り出す」などの設計に関する決断は、可能なら先送りせよ
- 後で切り出しやすいよう異なる責任同士は(もし同じオブジェクトに書くなら)隔離して書くこと
- データではなく振る舞いに依存せよ。データに直接アクセスするのではなくラッパーを定義すること。