Dev Study
← 境界の設計
レッスン 6 / 10·約 9 分·第14期 境界の設計

公開した瞬間、相手のコードが自分の仕様になる

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

前回までで、分けると何を払うことになるかを見ました。今回は手前の話で、分けたかどうかに関係なく外に出した時点で何が起きるのかを扱います。

まず結論

内部の関数を自由に変えられるのは呼び出し元を全部書き換えられるから、公開したAPIを変えられないのは呼び出し元が誰かを知らないからです。

境目は「v1と名乗った瞬間」ではなく「外に出した瞬間」です。Semantic Versioning はこれを規範として明文化し、間違えて壊した場合でも出したものに手を入れることを認めていません。

? 読む前に予測

あるライブラリの 1.4.0 を公開した翌日、そこに壊れた変更が混ざっていたと気づきました。仕様上、取るべき手はどれでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

変えられなくなるのは、難しくなるからではない

関数の引数を1つ増やす作業を考えます。自分のリポジトリの中だけで使われている関数なら数分です。引数を増やし、呼び出し箇所をたどって全部直し、型検査で直し漏れが無いことを確かめる——それで完了です。

同じ変更を、公開したライブラリの関数に行うとどうなるか。**作業そのものは同じ数分で、難しくなったわけではありません。**変わったのは、呼び出し箇所を全部たどるという手順が実行できなくなったことです。公開したAPIを変えられないのは、呼んでいるのが誰かを列挙する手立てが無いからです。

だから境目は「外に出したかどうか」であって、版番号でも社内/社外でもありません。列挙できない呼び出し元が1つでもあれば、そこから先は変えられない側です。

具体実物

仕様が、最初の条文に「公開」を置いている

これは教材側の解釈ではありません。Semantic Versioning 2.0.0 は RFC 2119 を引用する規範文書で、最初の条文がこうです。

Semantic Versioning 2.0.0(規範本体の第1項。原文HTMLに項番アンカーは無く、番号は並び順による)設定ファイル
Software using Semantic Versioning MUST declare a public API.
This API could be declared in the code itself or exist strictly
in documentation. However it is done, it SHOULD be precise and
comprehensive.

番号の規則より先に、公開する範囲を宣言せよという順序です。守る対象が決まっていなければ番号の規則が意味を持たないからです。別の条文には Version 1.0.0 defines the public API とあります。1.0.0 は「完成した」の印ではなく、以後の番号が何に対する変化を表すかを固定する基準点です。

もう一つ、本稿の芯にあたる条文があります。

Semantic Versioning 2.0.0(公開済みの版について)設定ファイル
Once a versioned package has been released, the contents of
that version MUST NOT be modified. Any modifications MUST be
released as a new version.

例外の条件がありません。しかも仕様は、最も差し替えたくなる場面を FAQ で先回りして閉じています。「壊れた変更を誤って minor として出した場合」に対し、新しい版を出して直せと答えたうえで Even under this circumstance, it is unacceptable to modify versioned releases. と書いています。事故でも後ろへは戻れず、前へ進んで直すことだけが残ります。

✓ ここまでの確認

内部の関数の引数は自由に変えられるのに、公開したAPIの引数は変えられません。この差を生んでいるのは何ですか。

抽象原理

では、約束を書けば守れる範囲が決まるのか

ここまでで対処が見えた気がします。公開範囲を宣言し、書いたことだけを守り、書いていないことは自由に変える——閉じそうに見えます。

ところがこの閉じ方には、規格ではなく観察として報告されている綻びがあります。hyrumslaw.com の著者はこう要約しています。

hyrumslaw.com(規格でも査読論文でもなく、著者個人のサイトの記述。日付表示は無い)設定ファイル
With a sufficient number of users of an API, it does not matter
what you promise in the contract: all observable behaviors of
your system will be depended on by somebody.

突き詰めるとこうなる、として挙げられているのが Given enough use, there is no such thing as a private implementation です。例が分かりやすい。応答の速さは何も保証していないのに、利用者は実装が出している速さを期待するようになる、と。速さは約束の外側ですが、外から観測はできます。観測できるものは依存されうる。

具体実物

名前を変えるだけで壊れる、という形

暗黙の依存は掴みどころがない話に聞こえますが、規格が名指しで「これは破壊的変更だ」と書いているはっきりした形もあります。

RFC 9457 はエラー表現の形式を定める文書で、エラーの種類を type という URI で識別します。§3.1.1 は、解決できない URI から後で解決可能な URI へ切り替える場合、それは

RFC 9457 §3.1.1(Standards Track, July 2023)設定ファイル
creating a new identity for the problem type and
thus introducing a breaking change.

にあたる、としています。中身は何も変わらず、同じエラーです。**識別子の綴りを変えただけで破壊的変更になります。**利用者側のコードがその綴りと自分の分岐を突き合わせているからで、誰がどの綴りを見ているかは出した側から分かりません。

✓ ここまでの確認

「明文化した約束の範囲さえ守っていれば、利用者を壊すことはない」という理解の評価はどれですか。

⇄ 2つを見比べる
具体例 A

公開したAPIの、ある版

版を公開すると利用者は自分の都合で取り込みます。誰が取り込んだかの一覧は出した側に届きません。差し替えは仕様が認めていません。

壊れた版に気づいたあと表
やりたいこと 残る手
その版を直す できない。新しい版を出す
取り込みを止める できない。利用者へ知らせる
誰が使ったか知る できない
具体例 B

利用者の手元にキャッシュされた応答

TTL を付けて返した応答は利用者の側に残ります。誰がどれを持つかの一覧は返した側にありません。渡ったものを消す手段もありません。

誤った応答を返したあと表
やりたいこと 残る手
渡した分を直す できない。次の取得を待つ
保持を止める できない。期限切れを待つ
誰が持つか知る できない

公開した版と、利用者の手元に残った応答。道具も規格も別ですが、出した側にできることの形が同じです。何が同じでしょうか。

「間違えた」と気づいた直後に、それぞれ何ができないかを並べてみてください。

抽象原理

図で見る:境目はどこにあるか

同じ変更が、境目のどちら側にあるかで別物になる
引数を1つ増やす作業内容は同じ
呼び出し元を列挙できるか
できる手元のリポジトリ内
できない外に出したあと
直し漏れはどこで出るか
自分の検査出す前に分かる
利用者の側出したあと
間違えたときに残る手
直して出し直す取り消せる
新しい版を出す取り消しは無い
仕様が明文で禁じている
  • ・分かれ目は版番号でも社内/社外でもなく、呼び出し元を列挙できるか

右列で起きているのは、検査の場所が自分の手元から利用者の側へ移ったということです。移ったあとは、壊したかどうかを出す前に知る手立てがありません。だから「どの変更が破壊的か」を事前に判定する必要が生まれます。**判定表が要るのは、判定しないと分からなくなったからです。**判定そのものは既存のAPI設計クイズ群が扱います。

この章のまとめ

  • 変えられなくなるのは変更が難しいからではなく、呼び出し元を列挙できないから。
  • 境目は版番号でも社内/社外でもなく、直し漏れを自分の検査で見つけられる範囲。
  • Semantic Versioning の最初の条文は MUST declare a public API。公開範囲の宣言が番号の規則より前にある。
  • 同じ仕様が公開済みの版の中身を変えてはならないと書き、FAQ で事故すら例外にしていない。
  • 明文の約束の外側も依存されうる(hyrumslaw.com の著者の観察。規格ではない)。
  • 識別子は綴りを変えるだけで壊れる(RFC 9457 §3.1.1 が breaking change と明記)。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

出した版に手を入れることについて、Semantic Versioning が書いているのはどれですか。

用語集(4語)
public API(公開された範囲)
外部の利用者に提供すると宣言した部分。宣言はコード中でも文書中でもよいが、宣言そのものは必須とされている。
Semantic Versioning
MAJOR.MINOR.PATCH の3数字で版を表す取り決め。RFC 2119 を引用して MUST を使う規範文書であり、単なる慣習ではない。
暗黙の依存
明文の約束には無いのに、利用者が実際の実装の挙動に頼っている状態。応答の速さなどが典型。規格の用語ではない。
破壊的変更(breaking change)
既存の利用者側の書き換えを必要とする変更。判定は既存のAPI設計クイズ群が扱う。