📌

関数の責任範囲は命名で(ほぼ)決まる

に公開

記事の立ち位置

関数の命名のとき考えていることをすこし言語化できたので記録。

関数を作るとき単一責任になるように作るのが重要。
では実際、関数が負うべき責任の範囲はどのように設定すべきなのか分かるか?
熟練のプログラマーならともかく、初学者にはよくわからないのではないか?
感覚を言語化できたので書いてみた。

要約・結論

理想的には関数名 = 役割 = 責任範囲であるべき。
実際には責任範囲にはコーナーケースや例外処理なども含まれるので関数名だけで説明しきるのは難しいことが多いが、関数名と役割の不一致は大抵おかしいことが起きている。

具体的な名前の関数

文字列が部分文字列を含むか判定する関数を例にする。

public static boolean contains(String str,String searchStr){
    ...
}

contains = 文字列が部分文字列を含むか判定するのように処理の内容がそのまま関数名になっている。
この手の関数は以下の特徴があると思っている。

  • 処理内容は具体的で分かりやすいが、どういう用途で使われるかは不明。(呼び出し側依存なので)
  • シンプルな役割故に利用範囲が大きくなりやすい。
  • 普通、仕様変更はないし、あるとしても軽微で前方互換があることが多い。

それゆえ、もし仕様変更したくなったら、別の関数の作成、使用を検討すべき。
関数名=役割という関係が崩れてしまいかねない。

この例で関数の責任範囲を遵守すると(処理がシンプルため責任範囲から逸脱することのほうが難しいかもしれないが)

  • 呼び出し側は文字列が部分文字列を含むか判定すること以外の役割を期待してはいけない。
  • 関数の作成者、変更者は文字列が部分文字列を含むか判定すること以外の役割を与えてはいけない。

となる。

抽象的な名前の関数

何かしらのメールの宛先人を抽出する関数を例にする。

public static List<User> extractQuxMailRecipients(List<User> user) {
    ...
}

どんな条件で誰が抽出されるかを関数名で表していない。関数名(=役割)が抽象的になっている。
この手の関数は以下の特徴があると思っている。

  • 関数がなにかを抽象化している。
  • ビジネスロジックの変更で破壊的変更の可能性がある。

この例で関数の責任範囲を遵守すると

  • 呼び出し側はメールのメールの宛先人を抽出すること以外の役割を期待してはいけない。
  • 関数の作成者、変更者はメールの宛先人を抽出すること以外の役割を与えてはいけない。
    となる。

具体的な名前の関数と異なり仕様が変わりやすいため、この責任範囲を守るというやり方は有効だと思う。最小限の変更で処理を変更できるようになる。

この例で仕様変更を考えてみると
ユーザーの年齢が30歳以下の人を対象にする ⇒ 40歳以上の人を対象にする
と変更してもメールの宛先人を抽出するという役割は変わっていない。
そのため、呼び出し側の変更は必要ない。

思いもよらない使い方

上記で責任範囲を変えないなら呼び出し側の変更は不要、と述べた。が、多くの場合は呼び出し側も変更が必要になる。関数の責任範囲を誤っている、つまり本来想定されていない、誤った使い方をしているからだ。プログラムを書く主体(人間、AI)が間違えるのは仕方ないことだが、責任範囲を遵守することを目指すべきだと思う。

結論

具体的な名前の関数抽象的な名前の関数では責任の種類が違う。
どちらにせよSRPは遵守しよう。

考え中

具体的な名前の関数抽象的な名前の関数という書き方にしたが、
抽象的な名前の関数ビジネスロジックを表す関数と呼んだほうがいいのかも。
具体的な名前の関数は何と呼べばいいんだろう。

GitHubで編集を提案

Discussion