7つの設計原則とオブジェクト指向プログラミング

設計原則はよい設計をするための指針です。
では、よい設計とはなんでしょうか?

もっとも重要なソフトウェア品質は発展性

ソフトウェアのよし悪しを判断する基準は、大きく分けて三つの観点があります。

  • 合目的性(入出力や計算判断の機能要求を満たす)
  • 安定性(可用性・性能とスケーラビリティ・セキュリティの要求を満たす)
  • 発展性(変更容易性や拡張性)

合目的性と安定性はソフトウェアの利用者が判断可能な外部品質です。
発展性は、ソフトウェアの利用者からは判断しにくい内部品質です。

ソフトウェアの設計品質は、この内部品質です。

目的に適合し安定して稼働しているソフトウェアは価値があります。しかし、内部の設計はボロボロかもしれません。
設計品質の劣ったソフトウェアは変更がやっかいで危険です。修正や拡張のコストが高く、現実的には変更不可というソフトウェアもたくさんあります。

ソフトウェアの発展性がビジネス価値を生む

事業活動はたえまなく変化します。顧客のニーズが変化し、市場での競合関係が変わります。そういう顧客や市場の変化にうまく対応しながら事業を発展させていくために、事業を支えるソフトウェアも変化を続けることが必要です。
「作って終わり」「動いていればよい」ではなく、事業の持続的な成長に貢献できる発展性を持つソフトウェアがビジネス価値を生み出します。

ソフトウェアの発展性が向上すれば、合目的性や安定性も継続的に改善できます。

参考記事:ソフトウェアのもっとも重要な品質は発展性 - ソフトウェア設計を考える

発展性をうみだす7つの設計原則

発展性を向上し、ソフトウェアの変更を楽で安全にするための設計原則には、以下のものがあります。

f:id:masuda220:20200602100546j:plain
7つの設計原則

7つの設計原則は、それぞれが独立した原則ではなく、お互いに強く関連します。
7つの設計原則の中心にあるのがモジュール化の原則です。

モジュール化

複雑なソフトウェアをわかりやすく整理し、変更を楽で安全にするための基本原則がモジュール化です。

モジュールは、簡単にいえば一つのプログラムファイルです。
複雑で大きなソフトウェアを複数のプログラムファイルに分割し、それらを組み合わせて全体をつくりあげるのがモジュール化です。

モジュール化の2つのアプローチ

モジュール化には2つの異なる方向性があります。

ひとつはオブジェクト指向プログラミングの基本である型によるモジュール化です。
もう一つは、「入力-処理-出力」の単位でソフトウェアを分割する手続き的なモジュール化です。

7つの設計原則は、どちらの設計アプローチでも適用可能です。
しかし、型によるモジュール化の設計アプローチと、手続き的なモジュール化の設計アプローチとでは、それぞれの設計原則をどう適用するかの考え方とやり方が大きく異なります。

この記事では、型によるモジュール化(オブジェクト指向プログラミング)の設計アプローチに焦点を合わせて、7つの設計原則を説明します。

モジュール化の2つの設計アプローチの違いを簡単にまとめておきます。

型によるモジュール化

オブジェクト指向プログラミングのモジュール化は型によるプログラムの分割です。

型とは値の種類です。
値の種類ごとに、その値を使った操作(計算や判断)をグルーピングして一つのモジュール(クラス)にまとめるのが、オブジェクト指向プログラミングの基本的な考え方です。
文字列型や整数型を表現したStringクラスやIntegerクラスは、型(値の種類)によるモジュール化の具体例です。

型には二種類あります。
ひとつはStringやIntegerのようにプログラミング言語に組み込みまれた型です。

もう一つは、プログラムの開発者が独自に定義する型です。
アプリケーションで扱いたい値、例えば金額・数量・率・日付・期間・日数などを型として開発者が独自に定義します。

金額や日付を扱うために独自の型をモジュールとして作成し、それらのモジュールを組み合わせてアプリケーションをボトムアップに組み立てていくのが、オブジェクト指向プログラミングの基本アプローチです。

型によるモジュール化は、値の種類ごとに計算や判断の操作をグルーピングしてひとつのモジュールにまとめます。
こうすることで、計算判断のロジックがどのモジュールに書いてあるかをわかりやすく整理できます。そして計算判断ロジックの変更の影響を特定のクラス(モジュール)内部に閉じ込めやすくなります。

手続き的なモジュール化

手続き的なモジュール化は、入力を受け取って結果を出力するまでの一連の処理の流れ(手続き)を一つのモジュールにまとめます。 「入力-処理-出力」という単位のモジュール分割です。

手続き的なモジュール分割はトップダウンのアプローチです。
全体を大きな「入力-処理-出力」のプログラムとして定義し、さらにそのモジュールを「入力-処理-出力」のサブモジュールに分割します。

手続き的なモジュールは、大きなデータクラスを受け取り大きなデータクラスを返す設計が多くなります。

計算や判断のロジックは、受け取ったデータクラスのデータ項目を処理する手順の一部として記述します。 型によるモジュール分割とは対照的に、異なる値の種類に対する計算判断のロジックが一つのモジュールに混在します。

手続き的なモジュール構造では、同じようなデータクラスを受け取れば、同じような計算判断のロジックが複数のモジュールに重複します。
その結果、ソフトウェアの変更はやっかいで危険になります。重複したコードはソフトウェアの発展性を阻害します。

関心の分離

モジュールに分解して組み立てるのは、複雑さを扱いやすくするためです。
しかし、やみくもにモジュールに分解すれば複雑さを単純化できるわけではありません。

わかりやすく扱いやすいモジュールに分解するための設計原則が関心の分離です。
複雑さとはさまざまな関心事が入り組んでいる状態です。関心の分離は、この入り組んだ状態を切り分けて整理するための設計原則です。

 ひとつのモジュールに異なる関心事が混在すると、どこに何が書いてあるかがわかりにくくなります。
モジュールとモジュールの組み合わせ方も、ぎこちなく入り組んできます。
変更をするときに、同じモジュールの別の関心事を記述した個所まで誤って変更してしまうかもしれません。
関心が混在したモジュールを組み合わせると、モジュールとモジュールの関係が複雑なります。あるモジュールの変更が、他のどんなモジュールに影響するか、簡単には把握できません。

では、どのような方針でモジュールを分割すれば、複雑なソフトウェアをわかりやすく整理できるでしょうか?

関心の4象限

f:id:masuda220:20200602094810j:plain
関心の4象限

この図は、関心の分離の基本方針を考えるために、関心事を4つの象限に分けたものです。 横軸では「入出力」と「計算・判断」に関心を分離します。 縦軸では「業務(ビジネス)の関心」と「実装の詳細」に関心を分離します。

入出力と計算・判断の分離

関心の分離のよくある失敗が、ひとつのモジュールに入出力の関心事と計算判断の関心事を混在させてしまうことです。

アプリケーションプログラムは、画面・データベース・通信など外部との入出力を扱う部分と、メモリとCPUを使って行う内部の計算・判断の処理を扱う部分に分離できます。
この二種類の関心事を明確に分離すれば、それぞれのモジュールの役割が明確になり、ひとつひとつのモジュールの記述は単純で扱いやすくなります。

手続き的なモジュール構造は、この入出力と計算・判断の分離がうまくいきません。 入出力の手続きの中に、計算・判断ロジックを埋め込んでしまうためです。

型によるモジュール構造では、計算・判断に使う値の種類、つまり型に注目してモジュールを分割します。
入出力の関心から切り離すことで、計算判断のロジックだけを独立したモジュールにわかりやすく記述できます。
計算・判断の関心事を別モジュールに分離できれば、入出力を扱うモジュールは入出力だけに専念できます。計算・判断ロジックが入らないため、入出力を扱うモジュールを単純化できます。

入出力の関心事と計算判断の関心事を別のモジュールに分離するのが、もっとも基本的な関心の分離です。

業務の関心と実装の詳細の分離

アプリケーションプログラムの目的は業務(ビジネス)の関心事に対して役に立つ機能を提供することです。
そして機能を提供するための手段として実装の詳細があります。

プログラムの多くの部分は実装の詳細の記述です。関心の分離を意識しないと実装の詳細の中に業務の関心事が暗黙的にかつ断片的に記述されがちです。
その結果、業務の関心事をソースコードから読み取りにくくなります。
ソフトウェアの変更要求は、業務の関心事の変更です。変更の理由となる業務の関心事がプログラムのどこにどう記述されているかがわかりにくければ、変更の対象個所を特定したり、変更の影響を把握するのがたいへんになります。

業務の関心事と実装の詳細にかかわる関心事を別のモジュールに分離すれば、それぞれのモジュールの役割が明確になりプログラムの見通しがよくなります。

業務の関心事を表現するモジュールは、実装の詳細から解放され、業務の関心事だけをわかりやすく表現できます。
パッケージ名・クラス名・メソッド名に業務で使われる言葉を積極的に使うことで、業務の関心事とプログラムの記述内容が直接的に対応します。

実装の詳細を記述するモジュールは、プログラミング言語や標準ライブラリに用意された型を使って、どうやってプログラムを実現するかの実装の関心事を記述します。
実装の詳細のモジュール群は、業務の関心事を表現したモジュール群から使われる基礎部品です。

業務の関心事を表現するモジュール群と実装の詳細を記述したモジュール群を分離して、業務の関心事のモジュール群が実装の詳細のモジュール群を使うという関係が明確であれば、どこに何が書いてあるかがわかりやすくなります。
プログラムを変更する時に、業務の関心事と直接対応する変更か、実装の詳細だけに関係する変更かを切り分けがしやすく、変更の影響を狭い範囲に閉じ込めやすくなります。

もっとも複雑な関心事(ビジネスロジック)の分離を徹底する

アプリケーションが複雑になる一番の原因は、4象限の右上の関心事です。 この象限はビジネスルールに基づく計算・判断のロジック、つまりビジネスロジックです。

ビジネスルールとは事業活動を進めるためのさまざまな決め事や制約条件です。
決め事や制約条件が複雑になればなるほど、ビジネスルールに基づく計算・判断のロジックが複雑になり、さまざまな条件分岐や例外的な処理の記述のためにプログラムが入り組んできます。

ビジネスルールの複雑さに起因したアプリケーションのこの複雑さを下げるために効果的なのが、ビジネスロジックを記述するモジュールを、他の3つの象限のモジュールから分離することです。
ビジネスロジックを分離すれば、ビジネスロジック以外のモジュール群は単純になり扱いやすくなります。

では、ビジネスロジックの複雑さそのものは、どうやって整理すればよいでしょうか?
ビジネスロジックの複雑さの整理に威力を発揮するのが、カプセル化と抽象化の2つの設計原則です。

カプセル化と抽象化

カプセル化と抽象化は、型によるモジュール構造でアプリケーションを組み立てるための二本柱です。
特に、複雑なビジネスロジックをわかりやすく記述するためには、カプセル化と抽象化の2つの設計原則が威力を発揮します。

カプセル化

カプセルの意味は「小さなケース」です。*1
カプセル化とは、小さな入れものに何かをギュッと詰め込むことです。

オブジェクト指向プログラミングで、小さな入れ物(クラス)の中にギュッと詰め込むのは、データとそのデータに対する操作です。

例えば、整数データを扱う操作は、加減乗除の4種類の計算と、等しい/等しくない、大きい/小さいという4種類の比較演算があります。
整数データを保持する場所と、その整数データを対象にした加減乗除と比較演算の8つの操作を一つのモジュールにまとめる。これがカプセル化の考え方です。

手続き的なモジュール化では、操作対象のデータは引数として外部から受け取ります。データを保持する場所はロジックを記述してあるモジュールとは別のモジュールです。 つまり、カプセル化をせず、データを保持する場所と、データを使った操作を記述する場所を分けるのが、手続き的なモジュール化の特徴です。

それに対し、オブジェクト指向プログラミングのモジュール化はカプセル化です。データを保持するモジュールに、そのデータを使う計算や判断の操作をいっしょに記述します。

ビジネスロジックカプセル化

カプセル化が威力を発揮するのは、ビジネスルールに基づく計算・判断のビジネスロジックを記述する時です。  

ビジネスロジックは、ビジネスで扱う値(金額・数量・百分率・日付・日数・期間・区分)を対象にした計算や判断のロジックです。
ビジネスで扱う値を使った計算や判断のロジックを、扱う値の種類ごとに一つのモジュールにまとめておけば、どこに何が書いてあるかをわかりやすく整理できます。

金額の足し算・引き算、単価に数量を掛ける計算、百分率を掛けたときの端数の丸め計算、日付の比較、日数の計算、期間内かどうかの判定、... 。こういう計算や判断ロジックを、扱う値の種類ごとに小さな入れ物(カプセル)にまとめてモジュール化します。
こうすることで金額や日付を計算したり判断したりするビジネスロジックがプログラムのあちこちに散らばらなくなります。
この結果、金額に関するロジックの変更が必要な時は、金額クラスだけを考えればよくなります。あちこちのモジュールを調べまわる必要はありません。変更箇所を一つのクラスに特定でき、変更の影響を狭い範囲に閉じ込めることができます。

値の種類ごとに、関連するデータとロジックをひとつのモジュールにまとめるカプセル化は変更を楽で安全にします

手続き的なプログラミングは、カプセル化の設計原則とは正反対のアプローチを採用します。値を保持するデータクラスとロジックを記述する機能クラスを分けます。
データクラスと機能クラスを分けると、あるデータを使う計算・判断のロジックがどの機能クラスに書いてあるのかを洗い出すのがたいへんになります。
一つの機能クラスに記述した判断ロジックを修正しても、同じデータクラスを使っている別の機能クラスの判断ロジックの変更が漏れていた、という事故が起きやすくなります。

抽象化

抽象化とは、さまざまな詳細の中から本質的な特徴だけを選択して取りだすことです。

人間の思考能力には限界があります。いちどに多くのことを考えると、わけがわからなくなります。
ある時点で考えるべきことを重要な本質だけに限定すれば、理解も容易になり、判断もしやすくなります。

モジュールを設計する時も、モジュールのさまざまな側面をいっしょに考えると混乱します。
役に立つモジュールを設計するために、モジュールを抽象化して検討します。
モジュールの本質的な特徴とは何か、そして、モジュールの特徴をどう定義すれば役に立つかを考えるための設計原則が抽象化です。

データ抽象

オブジェクト指向プログラミングのモジュール化はデータの(値の種類)に注目するアプローチです。
アプリケーションで扱うさまざまなデータを、整数・文字列・日付などの値の種類ごとに分類したものが型です。 型(値の種類)ごとにプログラムを分割し、その値を扱うための操作のグループをひとつのモジュールにまとめる(カプセル化する)のが型によるモジュール化です。

データを扱う時に、そのデータを扱う操作に焦点を合わせることをデータ抽象と呼びます。 データ抽象は、モジュールの本質的な特徴は値に対する操作である、という考え方です。 値に対する操作とは、例えば次のようなことです。

  • 整数型の値は、加減乗除・大小比較・等しい/等しくないの演算ができる
  • 真偽型(boolean型)の値は、等しい/等しくないを判定できるが、加減乗除も大小比較もできない
  • 日付型の値は、前後比較・等しい/等しくない・日数の加算・減算ができるが、日付同士の加減乗除はできない

値の操作をモジュールの本質的な特徴と考えるデータ抽象は、モジュールを使う側にとって何が重要かに焦点を合わせてモジュールを設計する、という考え方です。

モジュールをプログラムとして完成させるには、さまざまな詳細の検討が必要です。
しかし次のようなモジュール内部の詳細は、モジュールを使う側にとって本質的な特徴ではありません。

  • モジュールが内部に保持するデータの構造
  • そのデータを使った、計算や判断ロジックの詳細
  • そのモジュールが利用している他のモジュール

モジュールを使う側にとって重要なのは、その型(値の種類)ではどういう操作が利用できるか/できないか、です。モジュールを利用する時の外部の視点からのモジュールの特徴です。

エディタによってはコードの記述を支援する機能として、変数の直後にドット(.)をタイプすると、その変数が参照しているオブジェクトの操作の一覧を表示する機能があります。この操作の一覧がデータ抽象の具体例です。プログラムを書くときのモジュールの本質的な特徴は、そのモジュールで利用可能な操作です。

モジュールを型(値の種類)で分割し、その型が提供する操作に焦点を合わせてモジュールの特徴を定義するのがデータ抽象です。モジュール内部の具体的なデータ構造ではなく、データを操作に抽象化して考えるという設計のアプローチです。

データ抽象は、モジュールの分割方針を、手続きではなく(値の種類)に注目して行う、というアイデアから生まれました。*2 *3
データ抽象、つまり型によるモジュール化オブジェクト指向プログラミングの根底にある考え方です。

ビジネスロジックとデータ抽象

データ抽象の考え方が威力を発揮するのは、関心の4象限の右上の関心事、つまりビジネスロジックを表現するモジュールを設計する時です。

ビジネスルールの実体は、金額・数量・率・区分・日付・日数・期間などのビジネスで扱うデータを使った計算と判断の決め事です。
この計算判断のルールをプログラミング言語で記述したものがビジネスロジックです。

ビジネスルールは、複雑な事業活動を進めるためのさまざまな決め事や制約です。 ビジネスロジックは、このビジネスルールの複雑さをそのまま反映して複雑になります。
ビジネスロジックの複雑さを整理して扱いやすくする方法が、型(値の種類)ごとにモジュールを分割し、そのモジュールの本質的な特徴を、値に対して行う計算判断の操作として定義するデータ抽象の考え方です。

ビジネスで関心のあるデータを、プログラミング言語でどのように扱うかの詳細はビジネスロジックの本質的な特徴ではありません。
ビジネスロジックの本質的な特徴は、どういう値にたいしてどういう計算・判断の操作をしたいか/すべきかです。

例えば金額を扱うモジュールを考えてみましょう。
金額に対して行う操作には以下のものが考えられます。

  • 金額同士の加算と減算
  • 金額と整数の乗算
  • 金額と率の乗算
  • 金額と整数の除算
  • 金額が等しい/等しくないの判断
  • 金額どうしの大小の比較

型(値の種類)によるモジュール化(データ抽象)は、このようにビジネスルールに基づく計算判断を表現するために必要な金額の計算や判断の操作を定義して、ひとつのモジュールにまとめます。
そして値の種類ごとの部品(モジュール)を組み合わせて、より複雑なビジネスロジックを表現するモジュールを組み立てます。

こうすることで、どんな種類のデータに関心があり、そのデータに対してどんな計算や判断を必要としているかを体系的に整理できます。
データの種類に注目して、ビジネスの関心事としての操作に焦点をあわせるデータ抽象のアプローチが、ビジネスロジックを記述するプログラムのモジュール化に効果を発揮します。

手続き的なモジュール構造では、金額データを扱うあちこちのモジュールに計算や判断のロジックが重複しがちです。コードの重複は変更をやっかいで危険にする元凶です。

高凝集と疎結合

高凝集と疎結合はモジュールの質を高めるための設計原則です。

関心の分離・カプセル化・データ抽象の三つの設計原則は、モジュールをどう分割するかの指針です。
分割したモジュールの質を判定するための指針が高凝集疎結合の二つの設計原則です。

凝集度

凝集とは切っても切れない関係の要素の集まりです。

凝集度が低いモジュールは、さまざまな関心事が混在しているモジュールです。
関心事が混在すると、モジュールの意図の理解が難しくなります。
また、ある関心事についての変更の影響が、本来は関係のない部分にも影響し、変更がやっかいで危険になります。

モジュールに格納するデータと操作を一つの関心事に凝集させれば、わかりやすく扱いやすいモジュールになります。
そのモジュールの使いどころの判断と使い方が直感的になります。

どういうモジュールが凝集度が高いかを判断するのは簡単ではありません。ある見方では凝集度が高いと言えるが、別の見方では異なる関心事が混在しているとも言える、ということが起きるからです。

それに対して、明らかに凝集度が低いモジュールは、比較的簡単に判断できます。

次のようなモジュールは、さまざまな関心事が混在した凝集度の低いモジュールだと考えられます。

  • メソッドの数が多い
  • メソッドの引数が多い
  • フィールドの数が多い

メソッドの数が二桁になれば、そのモジュールの凝集度は低いと言えます。データを抽象化した時に、10も20も操作があるのは、複数の関心事が混在している明らかな兆候です。

例えば、文字列型を扱うStringクラスはさまざまな操作が用意されていて便利です。しかし、けして凝集度の高い設計ではありません。 文字列を扱うという広い関心事のモジュールだからです。

文字列の操作は、もっと小さなグループに分けて目的特化のモジュールに分割することが可能です。
文字列を分割するという関心事と、文字列にどんな内容が含まれているかを検査する関心事と、文字列をフォーマッティングしたいという関心事は、切っても切れないほど濃密な関係とは言えません。異なる関心事として別のモジュールに分離できそうです。
高凝集という設計原則を理解し実践できるスキルを上げるには、こういうモジュール分割の可能性を検討して、実際に分割の実験をして効果を検証してみる姿勢がたいせつです。

メソッドの引数が2つを超えたら、凝集度の低下がはじまっています。

オブジェクト指向プログラミングのモジュール(クラス)では、操作の対象の主役は、オブジェクト内部に保持しているデータです。
メソッドに渡す引数が多いということは、メソッドの内部のロジックは、オブジェクトが内部に持つデータよりも外部から渡されたデータに強く関心を持っている可能性があります。 外部から渡されるデータに強く関係するロジックは、そのデータを保持するクラスに移動したほうが、それぞれのクラスの凝集度が高くなります。

フィールドの数が2つを超えたら、凝集度は下がりはじめます。

クラスの凝集度を判断する一つの目安が、フィールドとメソッドとの関係性です。
クラスのすべてのメソッドが、すべてのフィールドを必ず使っていれば、そのクラスの凝集度は高いと言えます。
逆に、メソッドによって使っているフィールドが異なれば、異なる関心事が混在しているクラスです。
特定のフィールドだけと関係するメソッドは、そのフィールドだけを持つ別のクラスとして分離すべきです。
そうすることで、それぞれのクラスの凝集度があがります。

こうやって、凝集度をあげるために、フィールドとメソッドの関係を、切っても切れない関係だけに限定することで、それぞれのモジュールの役割が明確になります。そして、モジュールに対する変更の影響を、そのモジュールの内部に閉じ込めやすくなります。

オブジェクト指向プログラミングの基本プラクティスであるリファクタリングのパターンは、ほとんどが凝集度の低いモジュールを凝集度の高いモジュールに改善するためのパターンです。

結合度

結合度はモジュール間の関係性についての指針です。 モジュール間の関係性は、モジュールのインタフェース(外部に公開された仕様)から判断できます。

判断の目安は三つあります。

  • 小さなインタフェース
  • 少ないインタフェース
  • 理解しやすいインタフェース

小さなインタフェースとは、モジュール間でやりとりされる情報が少ないことです。
凝集度が下がる兆候として説明した引数の数が多くなると、モジュール間でやりとりされる情報が多くなり、それだけ密な結合になってしまいます。

モジュール間の結合の数が少ないほうが、全体の構造はシンプルになります。
モジュール数が同じでも、モジュール間の関係が少ないほどプログラムがわかりやすくなり、また、変更の影響範囲が狭くなります。

あるモジュールを使う時に知っておくべき知識が少ないほど、モジュールの用途が理解しやすく使いやすいモジュールになります。
また、モジュールの誤った使い方が減りプログラムの挙動が安定します。

隠された結合性の問題

表面的には疎結合に見えても、あるモジュールを変更すると、他のモジュールでも同じような変更をしなければいけないケースがあります。

同じような計算式や条件判断のロジックやあちこちのモジュールに重複している場合です。
まったく独立して無関係に見えるモジュールでも、このようなコードの重複があると、あるモジュールに対する変更に関連して、他のモジュールにも同じ変更が必要になります。

コードが重複した複数モジュールへの変更作業は、調べるのも、変更を適用するのも、変更の結果を確認するにも、時間がかかります。このような暗黙の関係性(隠された結合性)はソフトウェアの変更をやっかいで危険にする元凶です。

隠された結合性をなくして、より質の高い疎結合を実現するにはどうすればよいでしょうか?

定義の一点性

暗黙の関係性を取り除くための設計原則が定義の一点性(Single Point of Definition)です。*4

同じ計算式や同じ条件判断は、ひとつのモジュールだけで定義すればコードの重複を防げます。

型(値の種類)によるモジュール化(オブジェクト指向プログラミング)は、この定義の一点性を実現するためのわかりやすいアプローチです。
モジュールを値の種類で分割し、ある値に関係する計算判断を、ひとつモジュールだけで定義するからです。

手続き的なモジュール化の問題のひとつが、この定義の一点性を実現しにくいことです。
手続き的なモジュール構造でも、共通のサブルーチンモジュールを導入すれば、定義の一点性を向上できる可能性はあります。
しかし、入力と出力に注目しトップダウンで手続きモジュールを分割していくアプローチで開発すると、共通のサブルーチンが自然に生まれることがありません。むしろ、同じ計算式や判断条件が、別のモジュールに重複しやすくなります。

型によるモジュール化は、関係する計算判断のロジックを値の種類ごとに一か所で定義することを目的としたアプローチです。
オブジェクト指向プログラミングが、変更を楽で安全にしてソフトウェアの発展性を向上できるのは、型によるモジュール化のアプローチが定義の一点性を実現しやすいからです。

見た目が同じコード

プログラミング言語の記述能力は貧弱です。ですので、異なる意図の計算や判断も、プログラミング言語で書くと同じコードになってしまうことがあります。

見た目には同じコードであっても、プログラムの意図は異なるかもしれません。

定義の一点性というのは、同じ意図のコードを単一定義しようという原則です。 コードの見た目が似ているから共通コードにしようという設計原則ではないことに注意が必要です。

この点でも、型(値の種類)によるモジュール化は重要な設計指針です。
金額と数量は、整数の演算という観点では、内部は似たコードになります。
しかし、金額と数量はビジネスの観点では意味が異なる数値です。金額計算と数量計算は異なる計算です。金額を扱う計算判断と数量を扱う計算判断は、異なる型(値の種類)の操作として別のモジュールで扱うべきです。
そうすることで、関心が分離された凝集度の高いモジュールになります。

7つの設計原則の学び方

設計原則は、言葉として覚えるだけでは役に立ちません。
実際にソフトウェアを開発しながら経験的に、原則の意味と適用のしかたを学ぶことが大切です。

この記事で説明した設計原則を現場で実践するために参考になりそうな、コードの実装例・書籍・ネット上の情報を紹介しておきます。

コードの実装例

型によるモジュール化のアプローチで設計した具体例をgithubで公開しています。

図書館システムの実装例
蔵書の貸出制限ルールや貸出予約の状態管理のルールを、どのように実装するかの具体例です。

オブジェクト指向プログラミングでアプリケーションを開発する場合、オブジェクトと外部のデータ形式(画面・テーブル・JSON)とのマッピングが必要になります。
ドメインオブジェクトをどのように外部形式とマッピングするかの具体例です。
ドメインオブジェクトのマッピング

ドメインオブジェクト設計のガイドライン

この記事の設計原則の考え方をもとに、ビジネスロジックをコードで表現するための設計ガイドとサンプルコードです。

実践ガイドとして使える本

(拙著)現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法
この記事で書いた設計原則を現場で実践する時に参考になると思います。

リファクタリング 既存のコードを安全に改善する
第2版もでていますが、型によるモジュール化という観点からは、第1版のほうが役に立ちます。

RDRA2.0 ハンドブック 軽く柔軟で精度の高い要件定義のモデリング手法
要件定義、特にビジネスロジックの元になるビジネスルールの発見・整理のための実践的な手法です。

設計の考え方を理解するための本

この記事で紹介した設計の考え方をより深く学ぶための本を紹介します。

エリックエヴァンスのドメイン駆動設計
ビジネスロジックに焦点を合わせる重要性とその効果を著者の実践経験をもとに解説した古典的な名著です。

オブジェクト指向入門 第2版 原則・コンセプト
オブジェクト指向入門 第2版 方法論・実践
型によるモジュール化というオブジェクト指向プログラミングの考え方とそのメリットの詳細な解説です。
また契約による設計(Design by Contract)の考え方も、この本で詳しく解説されています。

ドメイン駆動設計とオブジェクト指向入門の二冊は、こちらの記事でも紹介しています。
masuda220.hatenablog.com

ソフトウェアの発展性を重視する設計の考え方は、以下の本が参考になります。

ソフトウェアアーキテクチャ構築の原理 第2版
ソフトウェア設計をさまざまな視点・観点から俯瞰的に解説した名著です。
特に 28章 発展性パースペクティブはこの記事の根底にある考え方と共通点が多い内容です。

*1:caps:入れ物 + ule:小さいという意味の接尾辞

*2:バーバラ・リスコフたちの研究 A History Of CLU

*3:カプセル化はリスコフがデータ抽象の実現アプローチとして提唱した概念です。情報隠蔽するのではなく、内部のコードを読むことを許容しつつ、内部に依存したプログラムは書かないようにするという考え方です。

*4:参照の一点性(Single Point of Reference)と呼ぶこともあります