PROMARI JOURNAL

SRP――一つの共有先の定義は、一つの理由で変わるか

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

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

丸い共有ボタンの裏側を掘る連載の第4回。SOLID の最初の原則 SRP を、共有先の名札カード1枚からプラグイン全体まで当てて確かめます。責任とは仕事の数ではなく、変更を頼んでくる人の数。Martin の車の修理のたとえから、実際の変更履歴を数える宿題まで、図22点で読み解きます。

PASS IT ON

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

1つ足したときの差分を、依頼主で読み直す

連載1.2では、架空の共有先を1つ足して、どのファイルに何行の差分が出るかを数えました。その結果を、今回は依頼主ごとに並べ直してみます。

図4-1 1つ足したときの差分を、依頼主で読み直す
図4-1 1つ足したときの差分を、依頼主で読み直す。実測(連載1.2の答え合わせ)。差分は依頼主ごとに1つのファイルへ分かれ、ブラウザ側のコードは0行でした。図4-1 1つ足したときの差分を、依頼主で読み直す。実測(連載1.2の答え合わせ)。差分は依頼主ごとに1つのファイルへ分かれ、ブラウザ側のコードは0行でした。

新しいSNSの名札カードに11行。サイトの設定のひな形に1行。テストの期待値に3行。生成器が作るブラウザ用の束は、作り直されました。そして、ブラウザ側のコード(web/src)の差分は0行でした。差分は、依頼主ごとに1つずつのファイルへ分かれて出ています。依頼主の境目で置き場所を分けておくと、1つの依頼は1つの場所で済む。1.2で見た「触らないコード」は、SRP の側から見ると、こう言い直せます。

どの依頼が、どのファイルに届くか

共有先を足すという1つの作業だけでなく、よくある依頼を7つ並べて、それぞれがどのファイルを書き換えるかを表にしてみました。列の頭には、そのファイルの持ち主の依頼主を置いています。

図4.1-1 どの依頼が、どのファイルに届くか
図4.1-1 どの依頼が、どのファイルに届くか。現在のしくみ(依頼の中身は説明例)。ほとんどの依頼は、1行に印が1つだけです。印が3つある「新しいSNSを足す」は、3人の依頼主の仕事が1つずつ集まったものです。図4.1-1 どの依頼が、どのファイルに届くか。現在のしくみ(依頼の中身は説明例)。ほとんどの依頼は、1行に印が1つだけです。印が3つある「新しいSNSを足す」は、3人の依頼主の仕事が1つずつ集まったものです。

ほとんどの依頼は、1行に印が1つだけです。X が共有URLを変えても、呼び名を変えても、書き換わるのは x.toml だけ。サイトが並びを変えても、色を変えても、書き換わるのは share_config.toml だけです。印が3つある「新しいSNSを足す」は、例外に見えますが、よく見ると印の色がすべて違います。新しい名札カードは SNS 各社の仕事、設定ファイルの1行はサイトの運営者の仕事、テストの期待値は保守する開発者の仕事です。1つの作業の中に3人の依頼主の仕事が1つずつ含まれていて、それぞれが自分の場所へ届いているのです。

この表は、自分のコードを見直すときにも使えます。よく来る依頼を縦に並べ、ファイルを横に並べて、印を付けてみてください。1つの依頼で印が同じ色の列に何個も付くなら、その依頼主の持ち物が散らばっています。1つの列に違う色の印が集まるなら、そのファイルに依頼主が何人もいます。

変更表の読み方を、二つの問いで試してみます。まず「SNSが共有URLを変えたら、誰がその変更を承認するか」。次に「サイトでそのボタンを使わないと決めたら、どこを直すか」。同じXの話でも、前者は共通の定義、後者はサイトの選択です。共有先の名前が同じというだけで、一つの変更理由へまとめないようにします。

反対に、SNSのURLと送るパラメータの名前が同じ仕様改定で変わったなら、その二つは一緒にレビューしたい項目です。URLだけ合っていても、項目名が古ければ正しく共有できないことがあるからです。項目を細かく分けた数より、関連する知識を同じ場所で確認できるかが役に立ちます。

こうして表へ理由を添えると、行の印が多い変更でも説明できます。新しい共有先を定義し、サイトで採用し、期待する表示をテストへ加えるなら、三つの場所を触ります。それぞれの役割が分かれていることと、一つの作業で三つを協調させることは矛盾しません。変更を一ファイルへ押し込むための表ではなく、協調する場所を見通すための表として使います。

この図を読むときは、最初に一行を横へ、次に一列を縦へ追うと分かりやすくなります。横へ見ると、一つの依頼がいくつの場所を必要とするかが見えます。縦へ見ると、一つのファイルへ何種類の理由が集まるかが見えます。印の合計だけを数えると、この二つの問題が混ざってしまいます。

たとえば、新しい共有先の追加には、定義、サイトでの採用、テストの期待値が必要です。印が三つあるからといって、三つを一つのファイルへ集めるのが改善とは限りません。公開する機能としては一つでも、何を定義するか、どこで使うか、何を確認するかは役割が違います。一つの作業が複数の責任をつないで完成することと、一つの部品に複数の判断を混ぜることを分けて読みます。

コードの中では、仕事の種類で分ける

名札カードは、依頼主でまとめた1枚です。では、コードの中でも1つのまま扱っているのでしょうか。実は、読み込んだところで2つに分けています。URLの決まりを持つ ShareDestination はドメイン層に、見せ方を持つ DestinationAppearance はアプリケーション層に置きました。

図4.2-1 1枚の名札カードは、読み込むときに2つに分かれる
図4.2-1 1枚の名札カードは、読み込むときに2つに分かれる。現在のしくみ。データは依頼主でまとめ、コードは仕事の種類で分けます。どちらも「同じ理由で変わるものを集める」です。図4.2-1 1枚の名札カードは、読み込むときに2つに分かれる。現在のしくみ。データは依頼主でまとめ、コードは仕事の種類で分けます。どちらも「同じ理由で変わるものを集める」です。

ShareDestination は、共有URLを組み立てるための値オブジェクトです。持っているのは key と、共有の仕方(action)と、共有画面のURL(endpoint)と、渡す引数(params)の4つだけで、表示名や色は知りません。

TypeScriptweb/src/domain/model/ShareDestination.ts(抜き出し)
export class ShareDestination {
  readonly key: string;
  readonly action: ShareAction;
  readonly #endpoint: string;
  readonly #params: Readonly<Record<string, ShareRequestField>>;
  // …
  shareUrl(request: ShareRequest): string {
    if (!this.#endpoint) return request.url;
    // params を並べ、空の値を外し、URL へつなげる
  }
}
TypeScriptweb/src/application/ShareButtonCatalog.ts(抜き出し)
export interface DestinationAppearance {
  readonly label: string;
  readonly color: string;
  readonly icon: string;
}

export class DisplayedShareDestination {
  readonly appearance: DestinationAppearance;
  readonly #destination: ShareDestination;
  // …
  shareUrl(request: ShareRequest): string {
    return this.#destination.shareUrl(request);
  }
}

反対に、DestinationAppearance が持つのは label・color・icon の3つだけで、URL の組み立て方は知りません。DisplayedShareDestination は、2つを束ねて共有欄へ渡すだけの薄い入れ物です。共有URLを作るときは、中の ShareDestination にそのまま頼んでいます。こう分けておくと、表示名や色をどう変えても、共有URLの組み立てには触れません。1ページ目の「色を直したら共有URLが壊れた」は、起こす危険を小さくできます。ただし、組み合わせた状態での検証は必要です。

データは依頼主でまとめ、コードは仕事の種類で分ける。一見、別々の決め方に見えます。けれど Martin の記事には、SRP の別の言い方として「同じ理由で変わるものを集め、違う理由で変わるものを分けよ(Gather together the things that change for the same reasons. Separate those things that change for different reasons.)」と書かれています。名札カードは「X が変えたら一緒に変わるもの」を集め、コードは「URLの決まりが変わったとき」と「見せ方が変わったとき」を分けています。どちらも同じ1つの問いに答えているのです。

ADR-0002 の判断の決め手と重ねる

前回の連載1.3の最後で、「共有先の定義がどんな理由で変わるのかを数え、今回の2件の記録に書いた『判断の決め手』とどう重なるかも見ていきます」と予告しました。その答え合わせです。ADR-0002 は、共有先を1つのJSONにまとめる案を捨て、共有先ごとの TOML にすると決めた記録でした。

図4.3-1 ADR-0002 の判断の決め手と重ねる
図4.3-1 ADR-0002 の判断の決め手と重ねる。実際の判断(ADR-0002)。「1ファイル足すだけ」という決め手は、1ファイルの依頼主を1社にすることと同じ形をしています。図4.3-1 ADR-0002 の判断の決め手と重ねる。実際の判断(ADR-0002)。「1ファイル足すだけ」という決め手は、1ファイルの依頼主を1社にすることと同じ形をしています。

ADR-0002 の判断の決め手の1つは、「1ファイル足すだけで済む形を保つ」でした。1つのJSONにまとめると、どのSNSの変更も、新しいSNSの追加も、同じ1つのファイルを書き換えることになります。依頼主が何社もいるファイルです。共有先ごとに分ければ、1ファイルの依頼主は1社だけになります。「1ファイル足すだけ」という決め手は、1ファイルの依頼主を1社にすることと同じ形をしています。当時、記録には SRP という言葉を書いていません。それでも、判断の決め手を書き残しておいたおかげで、あとから原則の目で読み直し、同じ形であることを確かめられました。

前回の宿題「理由を思い出せない判断を1つ書き起こす」は、1.3の宿題ラベルからコメントを残せます。書き起こした判断があれば、今回の SRP の問いも当ててみてください。

考えてみる:生成器は、名札カードも設定ファイルも読む。依頼主が2人いることにならない?

生成器(tools/config.py)は、2種類のファイルを読みますが、既存の形式の範囲で値を変えるだけなら、その中身に合わせてコードを書き換える必要はありません。X が URL を変えても、サイトが並び順を変えても、生成器のコードは1行も変わらず、出力だけが作り直されます。生成器を書き換えるのは、ファイルの書式そのものを変えたいとき、つまり保守する開発者の都合だけです。「読むファイルの数」ではなく「書き換えられる理由の数」で数えると、生成器の依頼主は1人です。

原稿と印刷物を、同じ変更として二重に数えない

もう一つ、差分の読み方で迷いやすいのが、生成器の出力です。名札カードを直すと、ブラウザへ渡す生成物も変わります。それを見て「一つの依頼が二つへ散った」と判断する前に、どちらを人が編集したのかを確かめましょう。

図4.4-1 原稿の変更が、印刷物へ伝わる
図4.4-1 原稿の変更が、印刷物へ伝わる。説明例(正本と生成物)。一つの判断から出力が増えても、依頼主が増えたわけではありません。鉛筆は正本にだけ置きます。図4.4-1 原稿の変更が、印刷物へ伝わる。説明例(正本と生成物)。一つの判断から出力が増えても、依頼主が増えたわけではありません。鉛筆は正本にだけ置きます。

図では、原稿の紙を印刷機へ入れると、配布用の紙が出てきます。人が考えて直すのは原稿で、印刷された結果は同じ判断から機械的に作られます。生成物の差分をレビューする必要はありますが、そこに新しい依頼主が一人増えたわけではありません。編集する正本と、そこから作られる出力の区別です。

設定の値を変えるだけなら、印刷機の歯車はそのままで済みます。反対に、今まで無かった項目の意味を追加するなら、印刷機が受け取る形式や、出力を使う側まで直すことがあります。そのときは「生成器は一切変わらない」と決めつけず、値の変更なのか、値を表す仕組みの変更なのかを分けて考えます。後者は次回のOCPでも大事になる境目です。

生成物を手で直して、見た目だけを先に合わせるのも避けたいところです。次に生成器を動かしたとき、手で入れた修正は原稿の値へ戻ってしまいます。図の鉛筆を原稿側だけに置いたのは、そのためです。「どのファイルに答えを書くか」が一つに決まっていると、依頼を受けた人が迷いにくくなります。

原稿と印刷物の図を、レビューの順番にも使ってみます。まず原稿に当たるTOMLの差分を読み、今回の依頼が値へ正しく表れたかを確認します。次に生成器を動かし、その出力を読みます。この順番なら、出力の文字列が変わった理由を、原稿のどの項目から説明できるかが分かります。

もし原稿の一項目しか変えていないのに、無関係な出力まで大きく変わったら、その差分は調べる価値があります。生成器の版、入力した設定、整形の違いなど、別の原因が混ざっているかもしれません。原稿と出力を分けるのは、出力の確認を省くためではありません。機械的に変わるはずの範囲を知って、想定外の変化を見つけるためです。

たとえば表示名の変更なら、期待する出力にも新しい表示名が現れるはずです。URLの対応表まで変わっていたら、同じ依頼から説明できません。この「依頼から説明できる差分か」という見方は、生成されたファイルの量が多いときにも使えます。行数に圧倒されたら、原稿の変更理由へ戻って一つずつ対応させてみましょう。

差分を数えるときは、手で変更した定義、手で変更した設定、機械が作り直した出力を分けてメモすると、判断の所在が追いやすくなります。ファイル数を小さく見せるための分類ではありません。何を直したために何が変わったのかを説明できれば、レビューする人は、原稿と印刷物の対応まで確かめられます。

COMMENTS
コメント…

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

PASS IT ON

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

この記事を書いた人

Takaomi Murasaki

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

公開記事 11 件

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

FROM INSIGHT TO IMPACT

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

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

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

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

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

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

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

ご相談後の流れ

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