案件が多忙過ぎて久々の更新となってしまいました汗
まあそんなことは置いておいて、今回はインターフェイス分離の原則について記述します。
注、以下の図解中の命名は、一旦説明用に意味のない名前にしましたが、
本来はその概念の意図がわかる名前にしなくてはいけません。
背景
今回扱う設計思想は、インターフェイス分離原則。
私は勝手に、目的の分離原則と呼んでます。
ミノ駆動本などでは、単一目的と呼ばれていたりしますね。
で、ここ数か月 ビジネスアーキテクチャの設計方針や方法論を確立し、それをクライアント側に認知させるって内容の案件にアサインされてました。
そこでわたしはUAFというエンタープライズアーキテクチャフレームワークぽいものを使っていたのですが、
これがですね、学習コストは高いものの、本当に設計ノウハウがめっちゃ詰まったものだったんです!!
その中で具体から抽象化する際に、具体1つに対して抽象は複数あり得ます。
その複数切り出される抽象を適切に切り出すのが、このインターフェイス分離原則の思想です。
では、早速本題に入りますね。
インターフェイス分離とは?
一言でいえば、【異なる目的のものは分離せよ】てことです。
DDDとかの【境界つけられたコンテキスト】ってものをお読みの方は、スッと入りやすいかと思います。
これだけでは、は???てなりますよね。
てなわけで、私の大好きな思想のこの目的分離原則を丁寧に解説していきます。
たった1つの目的名になるまで分離せよ
こんな感じです。
あえてロジックを【活動】という表現を使いました。
活動aとbのセットで、ある目的が達成される。その目的にInterXと命名しましょうっていう。
もしも仮にこれが、活動a~dが1つに纏まってしまっていたらどうなるでしょう?
下図のような感じになってしまってたらどうでしょう?
本来分離されているのが望ましい、InterXとInterYが1つに纏まってしまってる時です。
分離されていないときの問題点①
まず具象クラスのAやBに注目してみましょうか。
Bはもともとの分離されてるverのInterXもYも両方ともimplementsしてましたよね?
てことは、活動a~dすべてを具象クラスBは、持っている implementsしていますのでBは問題ないです。
しかしながら、Aはどうでしょう?
InterXしか実現していないわけだから、活動aとbしか本来持っていないです。
無理やり、活動cとdを持たせて空実装とかにしてる人いたりしませんか?
それは非常にまずい設計です。
仮にAに活動c、dを持たせたいなら、目的のInterXorY? のもつ活動cやdの事前・事後条件をAでは満たしていないと
リスコフの置換原則に反します。(リスコフは別記事で説明)
そのため、implementsしているとは言えません。
てなわけで、矛盾が起きてしまうわけですね。
分離されていないときの問題点②
さらにもともと分離しているverでは、
活動aとbのセットである1つの目的、InterXという目的を達成できている。
活動cとdのセットである1つの目的、InterYという目的を達成できている。
という状態でしたが、
このようにすべてまとまっていると、interfaceに対して Xていう目的名付けていいのか?
それともYていう目的名付けていいのかわかりませんよね?
こんな感じで、目的名があやふやな場合、それは複数の目的をinterfaceが担ってしまっている状態です。
これでは、仮にinterfaceに変更が入った際に、
下位の実現手段である具象クラスを最悪すべて変更しないといけないってことですね。
(この図では、具象クラスが2つしかないからあまり恩恵は感じにくいですが。)
また、絶対に気を付けてほしいことがあります。
異なる目的に関する活動を混ぜてはいけない
InterYという目的を達成するための活動の1つである、活動cがなぜかInterXに存在するっていう状況です。
これでは、InterYという目的は 活動dのみでは実現しえないのでダメだし、
それにInterXの方も、活動aとbだけで達成できてるのに、なぜか無関係な活動cが混ざってんだけど💦
ていう状況になります。
この考え方は、ある意味【単一責務の原則】と似ていますね!!
あちらは、「無関係なデータを1つにまとめるな!」ていう思想でしたが、
こちらは「無関係な活動を1つにまとめるな!」ていう感じです。
重複している活動があったら注意せよ
上図のように活動の重複、全く同じ概念の活動がある場合です。
この場合、コンテキストが適切に分離できているかを真っ先に疑いましょう。
適切に分離されていれば、こんな風になることはまずありません。
適切なコンテキストの分離については、長くなるので次の記事で記載します。
こんな感じで重複してるといけない理由は以下のようなことがあるからです。
MECEという不足なくダブりなくっていう思想がベースにある方にとっては「そんなん当たり前じゃん」てなるものです。
目的Yを達成するのに必要な活動に変更が入ったとしましょう。
具体的には、活動cの事前・事後条件が変化したとします。
その際に、Xの方にある活動cは?? 考慮漏れで解析し忘れてしまったりしかねないですよね?
たまたま【目的Yの方の活動cの方だけ、事前事後条件を変更しなくてはいけなくなって、
Xの方の活動cは、事前事後条件はそのまんまなんだ】ていうことであれば、
Xの方とYの方とでは、活動cの意味が全く異なるということになるからいいですけども、
いずれにしてもそれはそれで問題があります。
だって、
「え、、Xの方にもなぜか活動cあるんだけども、これは変えるの?それとも変えないの?」ていうことが
他のinterfaceにもあったりしたら最悪全部のinterafceに対して、
細かい仕様の確認しなおすの??てなりますし。
だから、意味合いが異なるのならYの方の活動cの命名を変えて、Xの活動cと被らないようにすべきですね。
ここで【目的Yの方の活動cの方だけ、事前事後条件を変更しなくてはいけない】
て知らなくて、間違って「活動cを変更するのか!! じゃあ目的Xの方も変えないといけないな! てことは具象Aも
それに伴って影響が出るか!!」て正しく解析してくれていたとしても、
前提の認識があってなくて全然想定とは違っちゃうてことになります。
もしも仮に、目的XとYを達成するには、共に活動eが必要である という場合には、こうすべきです。
別にこれは、活動が1つになるまで分離せよって意味ではないですからね?
初学者のうちは、勝手な妄想で「活動が1つになるまで関数型interfaceみたいに分離すればいいんだ」
みたいに勘違いする方がいますが、それは完全に間違いです。
もとのこの活動cが重複している上図と見比べてみてください。
何が嬉しいかというと、仮に【活動cに変更が入ります】てなった際に、
重複してる場合に比べて変更箇所の特定や影響範囲の特定がしやすくなってると思いませんか?
だって実際には以下のように見えてるんですよ?
・重複ありver
・重複なしver
蓋開けてみないと、変更箇所がどこだかわからない💦 てならないためにオブジェクト思考で設計してるんですよね?
なのに、重複あって蓋あけないとどこにあるのか解析不可能って、オブジェクト思考生かせてないじゃないですか。
重複なしverなら迷わずに、こんな風になります。
開発者「活動cがあるのはどこだろう?」
→ 開発者「InterZにあるのか! てことはそれをimplementsしてるAにもBにも影響が出ることになるから仕様の確認をしよう!」
→ クライアント「いえいえ、変更が入ってほしいのはBだけの方の活動cなんです」
→ 開発者「あら、そうなんですね! てことはこの追加仕様によって、A用の活動cとB用の活動cともしかしたら概念を分けなくてはいけないかもしれない。その確認のために事前・事後条件のヒアリングでチェックをしよう!!」
てな感じで、特定も変更による影響箇所の解析も容易だし、確認すべき仕様もポンポンって出てきます。
これが重複ありでは真逆になり得ます。
1度設計して終わりではない!
では、気を取り直して最初の適切に分離されてるverに戻りましょう。
ここで、追加仕様変更が起きたとしましょう。
その追加仕様の内容は、以下のとおりであるとします。
【目的Yを達成するには、活動cとdとeが必要である】という内容です。
こんだけ分かりやすく書いてれば、そりゃ
「InterYの方に活動eを追加し、それによる影響は、Aは受けずにBのみ受ける」
とすぐにわかりますよね?
でもですね、実際にはこんな風にシンプルにいかないんです。
・コンテキストの境界が最初わからない
・粒度を合わせて分割するって初回のヒアリングからわかるとは限らない
といった理由があるからです。
そう、顧客へヒアリングを繰り返し、修正しながらコンテキストの境界が見えてくるっていう。
だから適切にインターフェイス分離していても、それが正確とはいえないんです。
修正が入ることは当たり前。
だからこそ、その時の修正箇所を特定しやすいように、
MECEのダブりなくって思想に従い、同じ意味の活動を重複して複数のinterfaceに置いてはならない
ってことなんですね。
似ていても意味が異なるなら、異なるってわかるように名前を設計しなおさなくてはいけない。
トップダウンとボトムアップの折衷案
これは設計に限った話ではないですが、
最上位の目的から徐々に詳細化していくトップダウンの考え方、
コードレベルの詳細からリバースエンジニアリングみたいに抽象化して修正していくボトムアップ式
両軸からのアプローチが非常に重要です。
だから私はいつも頭の中に、無意識レベルで砂時計を思い描いています。
上から下に重力に従い落ちる砂(トップダウン)と、下から上に重力に逆らいあがってく砂(ボトムアップ)。
それらが合わさったところ(緑部分)に価値のある設計があると考えています。
名前設計のためのループバックチェック
これはいろんなとこで使えるチェック技法です。
・メソッド名のチェック
・クラス名のチェック
などにも使える技法です。
わたしがインターフェイスの名前設計でやってる手法は、
まず、粒度の揃った活動のまとまりに対して、たった1つの目的名を付けられるか?
(このとき複数であるようなら、複数の目的が混在してる可能性ありと推測できる)
ていうチェックをします。
次にこの目的interfaceを満たすためには、こんな活動とこんな活動が必要だよねっていう
上から下へのチェックをします。
ていう感じで、抽象から具体、具体から抽象へと両軸のチェックをします。
鋭い方は気づきましたよね?
そう!! さっきやったばかりのトップダウンとボトムアップの折衷方式の名前設計verです!!
このように折衷方式は、いろんな場面で出てくる大事な考え方です。
まとめ&感想
今回はinterface分離、改め 目的の分離原則 について触れてきました。
この考え方は、なにもソフトウェア領域だけに使える思想ではありません。
自分のビジョンのアーキテクチャを考える際にも、
いま私が担当している、ビジネスアーキテクチャを考える際にも使える大切な思想です。
設計とは本当に長い道のりです。
VUCAの時代で、本当に迅速に設計の構造を変えなくてはいけなくなってくることが予想されます。
しかしながら、最初の骨格を創ることは大変でも、非常にやりがいがあります。
全体の骨格を0ベースで考えて、設計方針を決めているわけですから。
次回の記事では、今回触れられなかったコンテキストに境界をつけて分割していくことで
適切なサイズのインターフェイスを定義するための考え方について記述します。