【Clean Architecture】第7章 単一責任の原則

「Clean Architecture」の学習記録。
「第7章 単一責任の原則」のまとめ。

原則の概要

この原則の名前を聞いたプログラマは、「どのモジュールもたったひとつのことだけを行うべき」と受け取ってしまう。
確かにそのような原則も存在する。最下位レベルの関数はたったひとつのことだけ行うべきである。

これは単一責任の原則とは別のものである。

単一責任の原則はかつて以下のように語られてきた。

モジュールを変更する理由はたった一つだけであるべきである。

「モジュールを変更する理由」とは、ユーザやステークホルダーを満足させるためである。
つまり以下のように言い換えられる。

モジュールはたったひとりのユーザやステークホルダーに対して責務を負うべきである。

複数のユーザやステークホルダーが同じように変更したい、と考えることもあるため、そのような変更を望む人たちをひとまとめにしたグループをアクターと呼ぶ。

つまり原則は以下のようになる。

モジュールを変更する理由はたった一つのアクターに対して責務を負うべきである。

症例1: 想定外の重複

この原則を理解するには、違反している例を見るのが一番良い。

給与システムにおけるEmployeeクラスを考える。
このクラスは単一責任の原則(SRP)に違反している。
3つのメソッドがそれぞれ別々の芥ーに対する責務を負っている。

http://www.plantuml.com/plantuml/png/7Of1oW8n34RtEKMN_uyRZp0k1Dq9dY3J-S1YCYr9AbJnxWwpU-_n9W-sw2hapx8sDjQKXTrF4LRc7hZckjxB6-b8Zx8WGLkCfD0PvaJxdCxQf0xWGuUKMN8N3jnu_FrlmT2gnQLsyzsTNzZWIpHXNRhA3m00

  • calculatePay() : 経理部門が規定する。CFOが利用する。
  • reportHours() : 人事部門が規定する。COOが利用する。
  • calculatePay() : データベース管理者が規定する。CTOが利用する。

http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKfCBialKd1syuUn_E6i47-kejJaaiIyz9nK1GNNt8AS_ChKL2uke685FJqxX8YpFxf029g0GsfU2j0s0000

これらのメソッドをひとつのEmployeeに入れるとすべてのアクターを結合することになる。
これが原因となり、CFOチームの何らかの操作が、COOチームの使うものに影響を及ぼしてしまうこともある。

例えばcalculatePay()とreportHours()の両方で所定労働時間を計算しているとする。
計算アルゴリズムはどちらも同じなためコードの重複を嫌い計算部分をregularHours()メソッドに切り出したとする。

ここでCFOチームで所定労働時間の算出方法に手を加える必要が出てきたとする。
CFOチームはregularHours()を修正してしまうと、reportHours()に影響が出てしまう。

http://www.plantuml.com/plantuml/png/SoWkIImgAStDuIfAJIv9p4lFILL8Jin9Bir9B4aDIAn44YvABSWlAl78BoqgHX5D1wej0WihAFjc5fTK1kKNfofmSI4Rew2hQuUYb0l9D4n9XzIy5A2R0000

問題が起こった原因は別々のアクターのコードをひとまとめにしてしまったことである。
単一責任の原則(SRP)はアクターの異なるコードは分割するべきという原則である。

解決策

この問題には様々な解決策があるが、いずれも関数を別のクラスに移動するというものである。
一番わかりやすい解決策は、データを関数から切り離すことである。

例えば3つのクラスからEmployeeDataクラスを使うようにする。
このクラスはシンプルなデータ構造を持つだけで、メソッドはひとつも含まれない。
3つのクラスはそれぞれ、特定の機能に必要なコードを保持している。
また他クラスについて知ることは許可されていない。

http://www.plantuml.com/plantuml/png/PSv1oi8m40NW_PmYo_z72fv0AYheJkm9fkc0mQG9aubGYtUtjIsqTZTvxuEyF0go96SXFamTaD4fRJ76lGYRCWbTjAyByoArXRkfI94BqYX7hYIE05-ihjWSqEfaKJoh5ZPAZq_gghz_U4_ox1FVA7YXmN5ti_HfdBEJ2zOtH0K-HnYxQqe7-KEFADZs-N9CbkqM1EOBLkNotrKDETLLSlO3

この解決策にも弱点がある。
それは開発者が3つのクラスをインスタンス化して、追跡しなければいけないという点である。
このジレンマを解決するために一般的に使われるのが、Facadeパターンである。

EmployeeFacadeに含まれるコードはわずかで、その責務は、実行したいメソッドを持つクラスのインスタンスを生成して、処理を委譲するだけである。

http://www.plantuml.com/plantuml/png/ROvRIiOm48NVPnNpg4Z15KZ52_ecTWNJTC1WqeJ9HAZ5lTjBY-PltsREzt4uTL8IpTv1-hHz926Fnb5AzwfYswpyQaBVs6_6M_Ym7IFxg0DwzYrB0bXaVAFXWPp9ZZG8Zd23PdjvGbVNyBERBo7BEyUWl7eotQ9pclvhJpwwC30tzBM9QRhsT3Klr4TI6eyDpsIeumMK4LRLNJdyH2Zsdl5oA1Hzir3CWx-s4rXM7N3DVPUz-mK0

重要なビジネスルールはデータの近くに置いておきたい、と考える場合もある。
その場合は、元のEmployeeクラスに重要なメソッドだけを残し、重要ではないメソッドを呼び出すFacadeとして使えばよい。

http://www.plantuml.com/plantuml/png/ROvHQiCm38RVVGezRWp3JZ0os61xAyq954TGKpisibn8G-zUfyGKXxx9dxx_jxeao9AzK-bifuYC7ep3bBv5s0vPwD-4gRTNWazriYC1-IWpUF83SLBg8SDVo7oa68GO5lK1N6QMbQQtTtMhsg-FBin4FLusCPMfqbTcaJJGzlX1mP8nw4nsA7J0kMHslssb-xAQNrPS0MZzrTpURjfpdxkWcqhLqJHcx-u0

メソッドが一つしかないクラスばかり作られるという理由で、この方法を気に入らない人もいる。
しかしそのようなことになるはずがない。
給与計算や帳票出力やデータの保存に必要なメソッドの数は多くなる。
また各クラスには、privateメソッドがいくつも含まれることになる。

まとめ

単一責任の原則(SRP)は関数やクラスに関する原則だが、同じような原則が別のレベルでも登場する。
コンポーネントレベルでは、この原則は「閉鎖性共通の原則(CCP)」と呼ばれる。 またアーキテクチャレベルでは、「アーキテクチャの境界」を作るための「変更の軸」と呼ばれる。