プログラマが知るべき97のこと/単一責任原則

「変更する理由が同じものは集める、変更する理由が違うものは分ける。」良いデザインの基本原則を1つあげるとすればこれでしょう。

この原則は「単一責任原則 (Single Responsibility Principle :SR P)」と呼ばれています。これはつまり、1つのサブシステムやモジュール、クラス、関数などに、変更する理由が2つ以上あるようではいけない、ということです。1つ典型的な例をあげましょう。ビジネス ルール、レポート、データベースに関わるメソッドを持つクラスの例です。

public class Employee {
    public Money calculatePay() ...
    public String reportHours() ...
    public void save() ...
}

3つのメソッドが1つのクラスに集められていることを特に問題と思わず、むしろ望ましいと思うプログラマもいるでしょう。結局のところ、クラスというのは、閉じ変数を操作するメソッドの集まりなのだから、と考えるわけです。しかし、上のクラスが問題なのは、3つのメソッドがまったく違った理由によって変更される可能性があるということです。calculatePayメソッドには、給与計算に関わるビジネスルールが変わる度に、変更を加える必要があります。reportHoursメソッドには、レポートのフォーマットが変わる度に、変更を加える必要がありますsaveメソッドには、DBAがデータベーススキーマを変更する度に、変更を加える必要があります。3つも変更理由があるEmployeeクラスは、非常に不安定な存在と言えます。3つの理由のうちいずれかがあれば変更されてしまうからです。さらに重要なのは、Employeeに依存するクラスがすべて、Employeeの変更に影響されるということです。

良いシステムデザインとは、システムのコンポーネントがそれぞれ独立してデプロイできるようになっているデザインのことです。独立してデプロイできるというのは、あるコンポーネントに変更を加えたからといって、別のコンポーネントの再デプロイは不要であるという意味です。しかしEmployeeが、別のコンポーネントの数多くのクラスによって利用されるのだとしたら、Employeeに変更を加える度に、他のコンポーネントの再デプロイが必要になります。それたとコンポーネントデザイン(バズワードがお好きなら、SOAと呼んでもいいでしょう)の大きな利点が失われてしまいます。では、以下のようにクラスを分割したとしたらどうでしょうか。

public class Employee {
    public Money calculatePay() ...
}
public class EmployReporter {
    public String reportHours(Employee e) ...
}
public class EmployeeRepository {
    public void save(Employee e) ...
}

こうすれば、3つのクラスはそれぞれ別のコンポーネントに配置されることになります。レポート関連のクラスと、データベース関連のクラスと、ビジネスルール関連のクラスが、すべて別のコンポーネントに配置されるということです。

察しのいい人なら、上のコードでもやはり依存関係は生じていると気づいたはずです。Employeeクラスに、他の2つのクラスが依存しているのです。したがって、Employeeが変更されれば、他の2つのクラスの再コンパイル、再デプロイが必要になる可能性が高いと言えます。Employeeだけを独立して修正してデプロイすることは困難なのです。それでも、他の2つのクラスに関しては、独立して修正し、デプロイすることが可能です。この2つのクラスのいずれかに変更を加えても、他のクラスの再コンパイル、再デプロイが必要になるというわけではないからです。さらに言えば、実はEmployeeも「依存関係逆転の原則(Dependency Inversion Principle :D IP)」を厳格に守れば、独立してデプロイすることが可能です。ただその話は別の本に譲りましょう。

SRPを守り、違う理由で変更し得るコードを別の要素に分けることは、各コンポーネントを独立してデプロイできるような設計をする上で非常に重要な条件なのです。