PROMARI JOURNAL

OCP――共有先を一つ足したときの差分を追う

作成: / 公開: / 内容更新:

執筆:tamito0201 / 掲載・運営:プロマリ

丸い共有ボタンの裏側を掘る連載の第5回。SOLID の2つ目の原則 OCP を、本物の SNS(Bluesky)の追加を通じて確かめます。名札カード1枚で済む変化と、共通処理のコードを変更することになる変化。差分を1行ずつ追いかけ、閉じる向きは変化の来やすさで選ぶことを、図22点で読み解きます。

PASS IT ON

ひとつの発見を、次の会話へ。

何に対して閉じているのか

2ページ目と3ページ目では、Bluesky を足す作業を2つの段階に分けて行いました。ここで、その結果をまとめて眺めてみます。すると、「共有先を足す」と一口に言っても、どんな変化かによって、直す場所が大きく変わることが分かります。OCPの目で、変化を3つの段階に並べました。

図4-1 変化の大きさで、直す場所が変わる
図4-1 変化の大きさで、直す場所が変わる。実測(段階1・2)と数えた見込み(段階3)。共通処理が閉じているのは、想定した種類の変化に対してだけです。想定の外の変化が来たら、共通処理のコードを変更することになります。図4-1 変化の大きさで、直す場所が変わる。実測(段階1・2)と数えた見込み(段階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 なら…」のように、動きの種類を名指しで比べている場所です。

図4.1-1 動きの種類で分かれている8か所
図4.1-1 動きの種類で分かれている8か所。現在のしくみ(数えた結果)。動きの種類は、共通処理のあちこちで分岐に使われています。ここは「足すための口」を作っていない場所です。図4.1-1 動きの種類で分かれている8か所。現在のしくみ(数えた結果)。動きの種類は、共通処理のあちこちで分岐に使われています。ここは「足すための口」を作っていない場所です。

こうした分岐は、全部で8か所ありました。共通処理の中では、ルールを置くドメイン層に3か所、処理の手順を置くアプリケーション層に2か所、画面の表示を受け持つ見た目の層に2か所です。それに、名札カードを読む生成器にも1か所あります。たとえば、ボタンが押されたときに何をするかを決める部分は、次のように書かれています。

TypeScriptweb/src/domain/service/ShareActionPolicy.ts(抜き出し)
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 の組み立てだけを取り出すと、たとえば次のような書き方です。

TypeScript仮の例:SNS の名前で分岐して 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の「もし、分岐で書いていたら」でも詳しく見ました。

閉じる向きは、選ぶもの

すべての変化に対して「書き換えずに済む」ようにすることは、できません。では、どの変化に備えればよいのでしょうか。答えは、変化の来やすさで選ぶ、です。

図4.3-1 閉じる向きは、選ぶもの
図4.3-1 閉じる向きは、選ぶもの。考え方。よく来る変化に対して閉じ、めったに来ない変化は、来たときに直します。閉じる向きは、変化の来やすさで選びます。図4.3-1 閉じる向きは、選ぶもの。考え方。よく来る変化に対して閉じ、めったに来ない変化は、来たときに直します。閉じる向きは、変化の来やすさで選びます。

このプラグインで、いちばんよく来る変化は「新しい SNS を足したい」です。だから、その変化に備えて、名札カードという差し込み口を用意しました。一方、新しい動きのような、めったに来ない変化には備えていません。来たときに共通処理の8か所を書き換えれば十分だからです。よく来る変化の側にだけ差し込み口を開け、めったに来ない変化は来たときに直します。どの変化に備えるかを選ぶこと。これが、OCP を実際の設計に使うということです。

考えてみる:動きの種類にも、名札カードのような差し込み口を作ればよいのでは?

作ることはできます。たとえば「動きカード」を用意して、ボタンを押したときの処理を外から足せるようにする形です。けれど、動きはブラウザの機能(新しい画面を開く・クリップボードに書き込む・端末の共有メニューを呼ぶ)と1対1で結びついていて、新しい動きが増えるのは、ブラウザに新しい共有の機能が加わったときくらいです。めったに来ない変化のために差し込み口を作ると、書き方の決まり・検査・作り直しの手順が増えるのに、それを使う日はほとんど来ません。来たときに8か所を直すほうが、全体としての手間は小さいのです。

COMMENTS
コメント…

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

PASS IT ON

この気づきを、誰かにも。

この記事を書いた人

Takaomi Murasaki

Promari SNS Shareの開発を通して、Webの仕組みや設計の選び方を紹介しています。動く実装と、その判断に至るまでを記事に残しています。

公開記事 10 件

プロフィールを見る
THANK YOU FOR READING.すべての記事へ ↗

FROM INSIGHT TO IMPACT

「できたらいいな」を、
動く仕組みに。

記事で見つけたヒントを、あなたの事業へ。
新しいサービスも、手間のかかる業務も。
いまの課題から、つくるべきものを一緒に考えます。

開発・AI・研修の実績を見る

まだ、仕様書はいりません。

「何から始める?」から、ご一緒に。

課題がまとまっていなくても大丈夫。テーマを選ぶと相談文をご用意します。
連絡先の必須入力は、お名前とメールアドレスだけ。

まずは課題の整理から相談する

ご相談後の流れ

  1. 01 内容を確認
  2. 02 メールでご連絡
  3. 03 課題・進め方をご相談
送信だけで契約やお申込みが確定することはありません。