PROMARI JOURNAL

LSP――Copyに「開くURL」を要求すると何が壊れるか

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

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

丸い共有ボタンの裏側を掘る連載の第6回。SOLID の3つ目の原則 LSP を、長方形と正方形の例と、共有ボタンの実物で確かめます。コピーのボタンに「開く URL」を求める書き換えを実際に作ると、型検査は通り、テストが5件落ちました。事前条件・事後条件・不変条件の3つの約束を、図15点で読み解きます。

PASS IT ON

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

LSP と OCP:違いを隠すか、見て選ぶか

ここまでで、LSPが求める3つの約束と、それを破ると何が起きるかを見てきました。最後のページでは、前回のOCPとのつながりから始めます。

Martin は、1996年の記事で、LSP と OCP を結びつけています。LSP を守らない関数は、親の型を受け取りながら、実は子の型をすべて知っていなければならない。そうなると、新しい子が増えるたびに、その関数を書き換えることになる。だから、LSP を破ると OCP も破ることになる、という説明です。4ページ目の書き換えた処理は、まさにこの形でした。開く共有先しか想定していなかったので、コピーの共有先が入ってきたら、処理を書き換えるしかありません。

図6-1 違いを隠すか、札を見て選ぶか
図6-1 違いを隠すか、札を見て選ぶか。考え方。どちらも取り替えに強い形です。何がよく増えるか(共有先か、実行方法か)で選びます。図6-1 違いを隠すか、札を見て選ぶか。考え方。どちらも取り替えに強い形です。何がよく増えるか(共有先か、実行方法か)で選びます。

違いのある部品を、取り替えても壊れない形にする方法は、大きく2つあります。1つは、違いを部品の中に隠すことです。共有先ごとに「押されたら何をするか」を実行するメソッドを持たせ、使う側は、どの共有先かを知らずにそのメソッドを呼ぶ。同じ呼び方で中身の違う部品を動かせる、この性質をポリモーフィズムと呼びます。もう1つは、違いを札として見せて、1か所で見て選ぶことです。このプラグインは、後者を選んでいます。共有先は action という札を持ち、ShareActionPolicy.decide が1か所でその札を見て、実行方法を選びます。

このプラグインが後者を選んだのには理由があります。共有先は名札カード、つまりデータとして書かれていて、コードを持たないからです。名札カードにコードを持たせると、共有先を足すたびにコードを書くことになり、前回の OCP で確かめた「名札カードを1枚足すだけ」が崩れます。代わりに、実行方法の数は増えにくいと考え、札で分けることにしました。札で分ける形では、実行方法を足すときに decide と #perform を開くことになります。前回数えた「動きの種類で分かれている8か所」が、その代償です。

札で分ける代わりに、札の書き忘れは型検査で止める。#perform の最後にある default の分岐は、残った decision を never という型の変数に入れています。never は「ここには何も来ない」という意味の型です。新しい実行方法を足して分岐を書き忘れると、ここに値が残るので、型検査が止まります。LSP の違反は型検査で見つからないと書きましたが、「分岐の書き忘れ」のように、型に書ける約束は、型で守ることもできます。

やりすぎと、LSP の限界

LSP は大切な原則ですが、当てはめすぎると、かえってコードが読みにくくなります。気をつけたい点を3つ挙げます。

1つ目は、取り替えないものにまで、共通の約束を作らないことです。LSP が役に立つのは、同じ場所で、中身の違う部品を取り替えて使うときです。取り替える予定のない部品のために、先回りして共通の約束を作ると、読む場所が増えるだけです。

2つ目は、共通の約束を大きくしすぎないことです。長方形と正方形の例では、共通の約束を「面積を答えられる」だけに絞ったので、どちらも守れました。もし「幅と高さを別々に変えられる」まで約束に入れていたら、正方形は守れません。約束を大きくするほど、守れる部品は減ります。使う側が本当に頼っていることだけを約束に入れるのが、ちょうどよい大きさです。この「約束の大きさ」の話は、次回の ISP でくわしく扱います。

3つ目は、LSP を守っていることを、型検査だけで確かめたつもりにならないことです。この記事の実験では、正方形も、書き換えたクリックの処理も、約束を破った代役も、すべて型検査を通りました。型が確かめるのは形の合い方で、振る舞いの約束は、テストで確かめる必要があります。一方で、テストも、書いた約束しか確かめられません。4ページ目で違反が見つかったのは、コピーの約束がテストに書いてあったからです。書いていない約束は、テストでも見つかりません。

考えてみる:共有先を「開く」「コピー」「端末の共有」の3つのクラスに分けて、それぞれに実行のメソッドを持たせたら、LSP はもっと守りやすくなる?

使う側が共通のメソッドだけを呼ぶ形にすれば、使う側は共有先の違いを知らずに済むので、守りやすくはなります。けれど、このプラグインでは共有先を名札カードというデータで書いているので、クラスに分けるなら、名札カードとクラスを結びつける仕組みが別に要ります。実行方法が増えにくいなら、札で分けて1か所で選ぶほうが、読む場所も少なく済みます。どちらを選ぶかは、何がよく増えるか(共有先か、実行方法か)で決めます。

LSP を確かめる4つの問い

ここまでの話を、コードの変更を見直すときに使える4つの問いにまとめます。同じ4つの問いで、このプラグインの今のクリックの処理と、4ページ目で書き換えた処理を採点してみました。

図8-1 LSP を確かめる4つの問い
図8-1 LSP を確かめる4つの問い。確かめ方の提案。4つとも「はい」なら、取り替えても使う側は困りません。約束がテストに書いてあったから、書き換えの違反を捕まえられました。図8-1 LSP を確かめる4つの問い。確かめ方の提案。4つとも「はい」なら、取り替えても使う側は困りません。約束がテストに書いてあったから、書き換えの違反を捕まえられました。

問いは次の4つです。使う側は、並びの中のどの部品にも通用する約束だけに頼っているか。取り替えた部品は、元より狭い入力しか受け付けない、ということがないか。取り替えた部品は、元より少ない結果しか約束しない、ということがないか。約束は、テストに書いてあるか。4つとも「はい」なら、取り替えても使う側は困りません。今のクリックの処理は4つとも「はい」、書き換えた処理は「はい」が1つだけでした。書き換えた処理でも、約束はテストに書いてあったので、そこだけは「はい」です。だから違反を捕まえられました。

手を動かして確かめる

今回の練習は、手元の代役を1つ選んで、約束を守っているかを確かめることです。テストで使っている代役(本物の代わりに渡しているオブジェクト)を1つ選んでください。そして、本物の部品の事前条件・事後条件・不変条件を、それぞれ1行ずつ書き出します。

図9-1 この回の宿題:手元の代役の約束を確かめる
図9-1 この回の宿題:手元の代役の約束を確かめる。確かめ方の提案。本物の3つの約束を書き出し、代役が1つも弱めていないかを確かめます。図9-1 この回の宿題:手元の代役の約束を確かめる。確かめ方の提案。本物の3つの約束を書き出し、代役が1つも弱めていないかを確かめます。

書き出したら、代役がその3つを弱めていないかを、1行ずつ見ていきます。本物より狭い入力しか受け付けていないか。本物が約束している結果を、代役が省いていないか。できれば、5ページ目の clipboardContract のように、約束を1つのテストにまとめ、本物と代役の両方に流してみてください。本物がブラウザの中でしか動かないときは、代役どうし(たとえば、成功する代役と、失敗する代役)に同じテストを流すだけでも、約束の書き忘れが見つかります。宿題:手元の代役を1つ選び、本物の3つの約束を弱めていないか確かめる。確かめた結果は、このラベルからコメントに残せます。

LSP の元になった定義は、Barbara Liskov の講演「Data Abstraction and Hierarchy」(1987年の講演・SIGPLAN Notices 23巻5号、1988年)です。部分型の振る舞いの考え方は、Liskov と Wing の論文A Behavioral Notion of Subtyping(1994)で読めます。長方形と正方形の例と、事前条件・事後条件の決まりは、Robert C. Martin のThe Liskov Substitution Principle(1996・Web Archive の写し)にあります。原則の見方(どういうものか・守らないとどうなるか・守るための方法)は、NakuRei さんの本『SOLID原則完全に理解した!になるための本』のリスコフの置換原則とSOLID原則とはの章を参考にし、例のコードはこの記事のために TypeScript で書き直しました。

まとめと、次回

第6回のまとめです。LSP は、同じ約束を満たす部品へ取り替えても、使う側が頼っていた条件が崩れないことを求める原則でした。約束は、事前条件(受け付ける入力)・事後条件(約束する結果)・不変条件(いつも成り立つこと)の3つに分けて考えます。取り替えた部品は、事前条件を強めず、事後条件を弱めず、不変条件を崩さない。長方形と正方形の例でも、共有ボタンの実物でも、崩れたのは型ではなく、この約束でした。

この回で確かめたこと
問い答え確かめ方
shareUrl は、どの共有先でも開く先かいいえ。コピーと端末の共有では記事の URL を返すShareDestination の1行目(endpoint が空なら記事の URL)
action を見ずに開くと何が壊れるかコピーと端末の共有が、何もせずに follow を返す一時作業コピーで書き換え、テスト63件中5件が落ちた
型検査は違反を見つけるか見つけない(終了コード0)正方形・書き換えた処理・3つの代役のどれも通った
代役の約束はどう確かめるか同じ約束のテストを、本物にも代役にも流す契約テストで、約束を破った代役だけが落ちた
名札カードの組み合わせは守られているかendpoint と params は検査あり。action と endpoint は検査なし崩した名札カードを生成器に読ませた

取り替えられるかどうかは、形ではなく約束で決まる。このプラグインは、「共有 URL を開けば共有できる」という、コピーには守れない約束を立てず、action を見て実行方法を選ぶことで、取り替えても壊れない形にしていました。そして、その約束がテストに書いてあったから、約束を破った書き換えを、実行する前に捕まえられました。

なお、この記事で試した書き換え・代役・契約テスト・名札カードは、すべて一時作業コピーの中だけのもので、プラグインの本体には入れていません。action と endpoint の組み合わせの検査も、提案にとどめています。

次回は連載2.4「ISP――コピーの代役に、いいねの保存まで求めない」です。今回は、代役が約束を弱めないことを見ました。次回は、その約束そのものの大きさを見ます。コピーの代役を作るだけなのに、いいねの保存やアクセス解析の知らせまで求められたら、何が困るのか。約束を、使う側が一緒に必要とする能力ごとに分ける ISP(インターフェース分離の原則)を、一緒に見ていきましょう。それでは、次回もお楽しみに!

連載の全体像は本編の連載目次へ。前回の2.2「OCP――共有先を一つ足したときの差分を追う」、前々回の2.1「SRP――一つの共有先の定義は、一つの理由で変わるか」とあわせて読むと、名札カードとクリックの処理が、どんな約束の上に立っているかが一続きで見えてきます。

プロマリでは、Webサイトの制作に加えて、共有や計測のような「裏側のしくみ」の設計・導入、それらを運用できる人材の育成に取り組んでいます。部品を取り替えても壊れない作りをチームでそろえたい方も、お気軽にご相談ください。

Web制作・計測・人材育成のご相談はこちら

COMMENTS
コメント…

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

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 課題・進め方をご相談
送信だけで契約やお申込みが確定することはありません。