OCP――共有先を一つ足したときの差分を追う
作成: / 公開: / 内容更新:
執筆:tamito0201 / 掲載・運営:プロマリ
丸い共有ボタンの裏側を掘る連載の第5回。SOLID の2つ目の原則 OCP を、本物の SNS(Bluesky)の追加を通じて確かめます。名札カード1枚で済む変化と、共通処理のコードを変更することになる変化。差分を1行ずつ追いかけ、閉じる向きは変化の来やすさで選ぶことを、図22点で読み解きます。
何に対して閉じているのか
2ページ目と3ページ目では、Bluesky を足す作業を2つの段階に分けて行いました。ここで、その結果をまとめて眺めてみます。すると、「共有先を足す」と一口に言っても、どんな変化かによって、直す場所が大きく変わることが分かります。OCPの目で、変化を3つの段階に並べました。
段階1は、X や LINE と同じやり方で表せる新しい SNS です。名札カードを1枚書き、設定に1行足すだけで済み、共通処理のコード(web/src)は1行も変えませんでした。段階2は、今ある6つの値では足りない共有先です。Bluesky がそうで、「タイトル+記事の URL」という新しい値のために、共通処理(ドメイン層)の3ファイルを書き換えました。
段階3は、ボタンを押したときの「動き」そのものが新しい共有先です。今のプラグインの動きは、名札カードの action に書く3つだけです。open は共有画面を開く、copy は記事の URL をコピーする、native はスマートフォンの共有メニューを出す、という動きです。たとえば「その場で QR コードを表示する」のように、4つ目の動きが要る共有先がこれに当たります。段階3は実際には作らず、どこを直すことになるかを数えました。
| 段階 | どんな変化か | 直した場所(実測) |
|---|---|---|
| 段階1 | 新しい SNS(今ある動き・今ある値) | 名札カード1枚(10行)+設定1行+在庫表3行。web/src は0行 |
| 段階2 | 新しい値(共有文+記事の URL) | ドメイン層3ファイル(+7/−1)+生成器1行+説明書1行+テスト1件 |
| 段階3 | 新しい動き | 動きの種類で分かれている8か所(実装はせず、数えた見込み) |
表を見ると、段階が上がるほど直す場所が増えていきます。つまり、共通処理が「閉じている」、言いかえると「書き換えずに済む」のは、あらかじめ想定した種類の変化に対してだけなのです。段階1は想定の内側にありました。段階2と段階3は想定の外側にあり、来たら共通処理を書き換えることになります。OCP は「どんな変化にも閉じている」ことではなく、「どの変化に対して閉じるかを決めている」ことなのです。
動きの種類で分かれている8か所
段階3を実装しなかった代わりに、3つの動き(open・copy・native)がコードのどこで使い分けられているかを数えました。数えたのは、コードの中で「もし動きが copy なら…」のように、動きの種類を名指しで比べている場所です。
こうした分岐は、全部で8か所ありました。共通処理の中では、ルールを置くドメイン層に3か所、処理の手順を置くアプリケーション層に2か所、画面の表示を受け持つ見た目の層に2か所です。それに、名札カードを読む生成器にも1か所あります。たとえば、ボタンが押されたときに何をするかを決める部分は、次のように書かれています。
static decide({ action, popup }: { action: ShareAction; popup: unknown }): ShareActionDecision {
return action === ShareAction.Copy ? 'copy'
: action === ShareAction.Native ? 'native'
: popup ? 'popup' : 'follow';
}上から順に読みます。action(名札カードに書いた動き)が copy なら、’copy’(URL をコピーする)を返します。そうでなく native なら、’native’(スマートフォンの共有メニューを出す)を返します。どちらでもなければ open なので、小窓で開く設定(popup)なら ‘popup’、そうでなければ ‘follow’(ふつうのリンクとして開く)を返します。
新しい4つ目の動きを足すなら、この場所を含む8か所すべてに、「その動きのときはどうするか」を書き足すことになります。動きについては、名札カードのような差し込み口を作っていないのです。
これは作りの手抜きではありません。3つの動きは、それぞれブラウザの機能(新しい画面を開く・クリップボードに書き込む・端末の共有メニューを呼び出す)と1対1で結びついています。新しい動きが必要になるのは、ブラウザに新しい共有の機能が加わったときくらいで、めったに起きません。めったに起きない変化のために差し込み口を作っても、5ページ目で見る「差し込み口を持つための手間」だけが残ってしまいます。
もし、SNS の名前で分岐していたら
8か所と聞くと、多く感じるかもしれません。そこで、もしこのプラグインが「SNS の名前」で分岐していたら、どうなっていたかを比べてみます。「X ならこの URL、LINE ならこの URL、Facebook なら…」という分岐が、URL の組み立て・ボタンの色・ボタンに出す文字・アイコンのそれぞれに書かれている作りです。
URL の組み立てだけを取り出すと、たとえば次のような書き方です。
function shareUrl(sns: string, page: { url: string; title: string }): string {
const u = encodeURIComponent(page.url), t = encodeURIComponent(page.title);
if (sns === 'x') return `https://twitter.com/intent/tweet?url=${u}&text=${t}`;
if (sns === 'line') return `https://social-plugins.line.me/lineit/share?url=${u}&text=${t}`;
if (sns === 'facebook') return `https://www.facebook.com/sharer/sharer.php?u=${u}`;
// Bluesky を足すなら、ここに1行足すことになる
throw new Error(`知らない SNS です: ${sns}`);
}1行に1つの SNS があり、SNS の名前(sns)で比べて、それぞれの URL を返しています。短くて分かりやすい書き方ですが、SNS が1つ増えるたびに、この関数そのものを開いて1行足すことになります。1行足すだけでも、書き間違えれば、すでに動いている X や LINE の行まで壊すかもしれません。だから、足すたびに X や LINE も確かめ直す必要が出てきます。
この形は、OCP を説明するときの定番の例でもあります。よく使われるのは、会員のランク(ゴールド・シルバーなど)ごとにポイントの倍率を if で分ける例です。定番の直し方は、ランクごとにクラスを分けて「倍率を返す」という共通の約束(インターフェース)を持たせ、使う側はその約束だけを呼ぶ形にすることです。新しいランクは、新しいクラスを1つ足すだけで済みます。このように、変わる部分を差し替えられる部品として外に出す形は、Strategy パターンと呼ばれます。このプラグインの名札カードは、クラスの代わりに設定ファイルを使って、同じ形を作っているのです。
この作りだと、Bluesky を1つ足すたびに、そのすべての分岐に「Bluesky ならこう」を書き足すことになります。新しい SNS は、新しい動きよりずっと頻繁に増えます。そのたびに共通処理を書き換えることになってしまいます。
このプラグインは、よく増えるもの(SNS)の違いを分岐から追い出し、名札カードの値として外に出しました。そのうえで、めったに増えないもの(動き)の分岐だけを共通処理に残しています。分岐を全部なくしたのではなく、どの分岐を共通処理に残すかを選んだのです。分岐で書いていた場合の姿は、連載1.2の「もし、分岐で書いていたら」でも詳しく見ました。
閉じる向きは、選ぶもの
すべての変化に対して「書き換えずに済む」ようにすることは、できません。では、どの変化に備えればよいのでしょうか。答えは、変化の来やすさで選ぶ、です。
このプラグインで、いちばんよく来る変化は「新しい SNS を足したい」です。だから、その変化に備えて、名札カードという差し込み口を用意しました。一方、新しい動きのような、めったに来ない変化には備えていません。来たときに共通処理の8か所を書き換えれば十分だからです。よく来る変化の側にだけ差し込み口を開け、めったに来ない変化は来たときに直します。どの変化に備えるかを選ぶこと。これが、OCP を実際の設計に使うということです。
考えてみる:動きの種類にも、名札カードのような差し込み口を作ればよいのでは?
作ることはできます。たとえば「動きカード」を用意して、ボタンを押したときの処理を外から足せるようにする形です。けれど、動きはブラウザの機能(新しい画面を開く・クリップボードに書き込む・端末の共有メニューを呼ぶ)と1対1で結びついていて、新しい動きが増えるのは、ブラウザに新しい共有の機能が加わったときくらいです。めったに来ない変化のために差し込み口を作ると、書き方の決まり・検査・作り直しの手順が増えるのに、それを使う日はほとんど来ません。来たときに8か所を直すほうが、全体としての手間は小さいのです。









このページ(4ページ目)の感想・質問・設計へのコメント