変更するための理由が、一つのクラスに対して一つ以上あってはならない
Wikipedia: SOLIDより引用
いや、抽象的すぎて分からん って思ったことないですか?
本記事では SOLID 原則を大体理解したマンのぼくが単一責任原則を理解できなかった理由と、ぼくなりの考え方をざっくりと説明したいと思います。
詳しい説明は他の方々がしてくださっているので、SOLID 原則を勉強したもののよく分からない、何かヒントが欲しいといった人向けです。
なんで単一責任原則を理解できないのか
どこまで分割すれば良いのか分からない
「じゃあ、どこまで分解したらエエねん?」
って言うのが、単一責任原則のハマりポイントだと思います。
コードレビューで「単一責任原則に従い、クラスを分離してください」と指摘したものの、「具体的にどう分割したら良いですか?」という質問に答えられなかったり、クラス分割の粒度が毎回違ったり……というのは結構あるあるだと思います。
単一責任原則を遵守して何のメリットがあるのか分からない
「コードがあちこちに散らばってて分かりづらいとぼくは思うけどね」と上司に言われたことがあります。
2024 年の今でもテストコードを書かず、手動でテストしている企業・プロジェクトはたくさんあると思いますが、かつてのぼくのように、そういった環境が理解の妨げになっているパターンがあると思います。
自分なりの「単一責任原則とは」
全メンバ変数を使う関数しか実装されていないクラス
一部のクラスを除いて、メンバ変数と関数があって初めてクラスというものになりますが、ぼくがクラスを書くときはそこにさらに上記制約を加えるようにしています。
逆にいえば、メンバ変数全てを使っているならいくらでも関数を生やして良いと思っています。
ざっくり書くと、こんな感じのクラスです。
// じゃんけんの手
class JankenHand {
// 親指
private readonly Thumb _thumb;
// ...人差し指、中指、薬指省略
// 小指
private readonly LittleFinger _littleFinger;
public JankenHand(Thumb thumb,
IndexFinger indexFinger,
MiddleFinger middleFinger,
RingFinger ringFinger,
LittleFinger littleFinger) {
// ...代入省略
}
// グー
public JankenHand Rock() {
// 全部の指を曲げて JankenHand の新しいインスタンスを返す
return new JankenHand(_thumb.bend(),
_indexFinger.bend(),
_middleFinger.bend(),
_ringFinger.bend(),
_littleFinger.bend()
);
}
// チョキ・パーは省略
}
なんじゃそれって感じですが、メンバ変数 5 つと、それら全てを使用する関数を 3 つ実装したクラスを書いてみました。
イメージとしてはなんかのゲームでキャラクターの手の形を変えてグーチョキパーを表現する感じなのですが、
明日から急にグーは親指を伸ばすことになったり、
チョキは薬指も伸ばすことになったり、
逆にパーはなんか中指を曲げることになったりしてもこのクラスを修正すれば良いんです。
新しいじゃんけんの手が追加されたり、逆に突然グーがなくなることになったりしてもこのクラスを修正すれば良いですね。
「いや、それ変更するための理由が複数ですやん」
と思われた方もいると思いますが、上記 3 つはどれも「じゃんけんの手に関すること」なので単一責任なのです。
要するに「ものは言いよう」ってことです
ちなみにですが、上記のようなクラスを Facade パターンとしてぼくは認識しています。
(断言して間違ってたら恥ずかしいので認識してるだけにしておきます)
さいごに
初めての投稿なのですが、いかがでしたか?
こんな感じで気が向いたら次はオープン・クローズド原則についての記事を書きたいと思ってます。
よかったら皆さんの「単一責任原則をここが難しい」「こうやって覚えられた」を教えてください。
励みになります。