ISP――コピーの代役に、いいねの保存まで求めない
作成: / 公開: / 内容更新:
執筆:tamito0201 / 掲載・運営:プロマリ
丸い共有ボタンの裏側を掘る連載の第7回。SOLID の4つ目の原則 ISP を、飛べないペンギンの例から、プラグインの5つの小さな口(ポート)とテストの代役まで読み進めます。全部入りのインターフェースにすると、コピーの代役に何が要るのか。作業コピーでの実験と図15点で確かめます。
LSP・SRP とのつながり
最後に、ISPを、前回までに読んだ原則とつなげておきます。
前回のLSPは、「同じインターフェースを満たす実装へ取り替えても、利用側が頼っていた約束が崩れないこと」を求める原則でした。1ページ目のペンギンの fly や、3ページ目の全部入りの代役のように、使わない項目に「例外を投げるだけ」の中身を書くと、その実装は約束を守れていない実装になります。ISP は、利用側が要らない約束をインターフェースから外しておくことで、そうした守れない約束が生まれる余地を先に消します。LSP が取り替えたあとの約束を見張るのに対して、ISP は約束をどこまで求めるかを決める、という分担です。
連載2.1のSRPは、「変更を頼んでくる人(依頼主)ごとに、置き場所を分ける」原則でした。ISP は、同じ見方をインターフェースの側から当てたものと言えます。SRP は「誰が変更を頼むか」で置き場所を分け、ISP は「誰がその能力を使うか」で約束を分けます。4ページ目で、いいねの保存を知らせの口に入れなかったのは、いいねの保存を頼むのも、使うのも、クリックの処理ではないからでした。
| 原則 | 見ているもの | このプラグインでの当て方 |
|---|---|---|
| SRP(2.1) | 変更を頼んでくる人 | 名札カードの依頼主を SNS 各社にそろえる |
| LSP(2.3) | 取り替えたあとも約束が守られるか | 動きの種類ごとに、URL を開く・コピーするの条件を分ける |
| ISP(2.4) | 利用側が使わない能力を求められていないか | 外の世界の能力を5つの口に分け、いいねの保存は口に入れない |
三つの原則を同じコピーの変更へ当ててみると、見る場所が違うことが分かります。SRPでは、誰の都合でその置き場所を変えるかを考えます。ISPでは、使う側にコピー以外の能力まで要求していないかを見ます。LSPでは、差し替えたコピーの部品が、書き込んでから完了する約束を守るかを確かめます。
小さく分けた口でも、中身が何もしなければLSPの問題は残ります。約束を守る全部入りの実装でも、コピーだけの利用側に無関係な変更を強いていればISPの問題は残ります。一つを守ればほかが自動で満たされるわけではありません。同じ図へ三枚の観点を重ねて、どの問題を解いている変更かをはっきりさせます。
ISP を確かめる3つの問い
ここまでの話を、コードを見直すときに使える3つの問いにまとめます。3ページ目の全部入りの形と、今のプラグインを、同じ問いで比べました。
| 問い | 全部入りの形(実験) | 今のプラグイン |
|---|---|---|
| 代役に、使わない項目の中身を書いているか | はい(コピーの代役に6項目) | いいえ(コピーの代役は write と fallback だけ) |
| 使わない能力の変更で、直すことになったか | はい(loadLikes を足すとコピーの代役が止まる) | いいえ(いいねの保存は口の外) |
| 例外を投げるだけ・空の実装があるか | はい(「この試験では使わない」) | いいえ |
3つとも「いいえ」なら、調べた範囲では、使わない能力による負担は見つかっていません。どれかが「はい」なら、5ページ目の判断の目安に進み、つなぐ手間と比べて分けるかを決めます。
ISP によくある3つの誤解
ISP を読むときに陥りやすい誤解を、3つ並べておきます。
| よくある誤解 | この記事での答え |
|---|---|
| メソッドは1つずつに分けるほどよい | 分ける単位は、利用側が一緒に必要とし、一緒に変わる能力。NativeShareGateway は2項目のまま |
| 分けた口をまとめ直すのは、いつも悪い | 利用側が一緒に使うものを束ねるのはよい(ShareGateways)。避けるのは、利用側が使わない能力まで必須にする形 |
| ISP は、実装するクラスの話 | 主役は利用側。使う側が何を求められているかで決める。実装する側の空のメソッドは、その結果として現れる合図 |
3つの誤解に共通しているのは、ISP を「インターフェースを小さくすること」だと読んでいることです。ISP が求めているのは、小さくすることそのものではなく、利用側が、使わない能力に頼らされないようにすることです。どこで分けるかは、実際に代役を書いたり、変更に巻き込まれたりした経験を数えて決めます。
レビューで「口が大きい」と言うときには、メソッドの数だけでなく、具体的な利用側を指してみてください。「この処理はwriteしか使わないのに、saveLikeも実装しなければ作れない」と言えば、何を外したいのかが伝わります。「八個あるから多い」だけでは、一緒に必要な八個なのか、無関係な仕事が混ざった八個なのかが分かりません。
分割案を見る側も、「分けた結果、どの変更に追従しなくて済むか」を聞くと評価しやすくなります。今回なら、いいねの読込機能を足しても、コピーの代役には項目を足さない、という具体的な答えがあります。効果を一つの将来の変更で説明できれば、ファイルが増える費用と比べて判断できます。
手を動かして確かめる
今回の練習は、手元のインターフェースで代役を1つ書いてみることです。インターフェースを1つと、それを使っている利用側を1つ選び、その利用側のテストのための代役を書きます。そして、代役のうち「使わない」「空」になった項目を数えます。例外を投げるだけの中身を探すなら、grep -rnE “not implemented|未実装” src test のように、手元のコードで使っている言い回しで検索すると見つけやすくなります。
数えた数が多いなら、その利用側が一緒に必要とする能力だけを持つ、小さな口を作れないかを考えます。ただし、5ページ目の3つ目の問い(つなぐ手間)も忘れずに比べてください。宿題:代役を1つ書いて、使わない項目を数える。数えた結果は、このラベルからコメントに残せます。
練習では、最初に既存のテスト用ヘルパーを見ず、小さな代役を自分で一つ書いてみると、余分な要求に気づきやすくなります。型の検査が足りないと言った項目を並べ、その利用側が実際に使うものへ印を付けます。残った項目は削除を決めたものではなく、別の口へ分ける候補です。
次に、その候補のうち一つを変更したら、どこを直すことになるかを調べます。代役、実装、組み立ての三か所を見れば、口の広さによる負担と、分割のための負担を同じ紙で比較できます。最後に、分ける案でも今の動作を保てるかを、LSPの契約の観点で確認します。数えて終わらず、次の変更がどこに届くかまで描ければ、練習は十分に具体的です。
ISP の定義は、Robert C. Martin が The C++ Report に書いたThe Interface Segregation Principle(1996・Internet Archive に残る写し)から、短い言い直しはSolid Relevance(2020)から引きました。「どういうものか・守っていない例と守っている例・守らないとどうなるか・守るための方法」という読み進め方と、鳥とペンギンの例は、NakuRei さんの Zenn の本『SOLID原則完全に理解した!になるための本』のインターフェース分離の原則の章とSOLID原則とはの章を参考に、この連載の TypeScript で書き直しました。
まとめと、次回
第7回のまとめです。ISP は、利用側に、使わない能力まで求めないようにインターフェースを分ける原則でした。飛べないペンギンに fly を書かせるような「例外を投げるだけ」の実装は、インターフェースが大きすぎる合図です。プラグインの実物では、外の世界の能力が5つの小さな口に分かれていて、コピーの代役は write だけで書けました。全部入りの1つにすると、コピーの代役に使わない6つの中身が要り、いいねの変更にまで巻き込まれることを、作業コピーの実験で確かめました。
| 問い | 答え | 確かめ方 |
|---|---|---|
| コピーの代役に何が要るか | write(と、必要なら fallback)だけ | application.test.ts の代役・Node 63件が通る |
| 全部入りにすると何が起きるか | 使わない6項目の中身が要り、いいねの変更で止まる | 作業コピーで tsc(TS2740・TS2741) |
| いいねの保存はどこにあるか | プラグインの口の外(ページ側) | promari-sns-share-like と setLikeState |
| どこまで分けるか | 一緒に必要とされ、一緒に変わる能力ごと | NativeShareGateway は2項目のまま |
分ける単位は、利用側が一緒に必要とする能力。使わない能力が代役や変更に現れたら分けどきで、分ける前には、つなぐ手間とも比べます。今回の実験で作った全部入りのインターフェースと、知らせの口への saveLike は、作業コピーで試しただけで、プラグインの本体には入れていません。
次回は連載2.5「DIP――ブラウザの機能を、ポートの向こうへ置く」です。今回は、ブラウザの機能を使うための口を、能力ごとに分けて見ました。次回は、そもそもなぜ内側のコードがブラウザの機能を直接呼ばず、口の向こうに置いているのかを、DIP(依存性逆転の原則)の目で確かめます。それでは、次回もお楽しみに!
連載の全体像は本編の連載目次へ。前回の2.3「LSP――Copyに「開くURL」を要求すると何が壊れるか」、2.1「SRP――一つの共有先の定義は、一つの理由で変わるか」とあわせて読むと、口の分け方が、どんな原則の上に立っているかが一続きで見えてきます。
プロマリでは、Webサイトの制作に加えて、共有や計測のような「裏側のしくみ」の設計・導入、それらを運用できる人材の育成に取り組んでいます。テストしやすい部品の分け方をチームでそろえたい方も、お気軽にご相談ください。
記事で読んだ口と代役を、実際のリポジトリで確かめるための資料です。





この記事全体の感想・質問・設計へのコメント