- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
前回までで、分けると何を払うことになるかを見ました。今回は手前の話で、分けたかどうかに関係なく外に出した時点で何が起きるのかを扱います。
まず結論
内部の関数を自由に変えられるのは呼び出し元を全部書き換えられるから、公開したAPIを変えられないのは呼び出し元が誰かを知らないからです。
境目は「v1と名乗った瞬間」ではなく「外に出した瞬間」です。Semantic Versioning はこれを規範として明文化し、間違えて壊した場合でも出したものに手を入れることを認めていません。
あるライブラリの 1.4.0 を公開した翌日、そこに壊れた変更が混ざっていたと気づきました。仕様上、取るべき手はどれでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
変えられなくなるのは、難しくなるからではない
関数の引数を1つ増やす作業を考えます。自分のリポジトリの中だけで使われている関数なら数分です。引数を増やし、呼び出し箇所をたどって全部直し、型検査で直し漏れが無いことを確かめる——それで完了です。
同じ変更を、公開したライブラリの関数に行うとどうなるか。**作業そのものは同じ数分で、難しくなったわけではありません。**変わったのは、呼び出し箇所を全部たどるという手順が実行できなくなったことです。公開したAPIを変えられないのは、呼んでいるのが誰かを列挙する手立てが無いからです。
だから境目は「外に出したかどうか」であって、版番号でも社内/社外でもありません。列挙できない呼び出し元が1つでもあれば、そこから先は変えられない側です。
仕様が、最初の条文に「公開」を置いている
これは教材側の解釈ではありません。Semantic Versioning 2.0.0 は RFC 2119 を引用する規範文書で、最初の条文がこうです。
Software using Semantic Versioning MUST declare a public API.This API could be declared in the code itself or exist strictlyin documentation. However it is done, it SHOULD be precise andcomprehensive.
番号の規則より先に、公開する範囲を宣言せよという順序です。守る対象が決まっていなければ番号の規則が意味を持たないからです。別の条文には Version 1.0.0 defines the public API とあります。1.0.0 は「完成した」の印ではなく、以後の番号が何に対する変化を表すかを固定する基準点です。
もう一つ、本稿の芯にあたる条文があります。
Once a versioned package has been released, the contents ofthat version MUST NOT be modified. Any modifications MUST bereleased as a new version.
例外の条件がありません。しかも仕様は、最も差し替えたくなる場面を FAQ で先回りして閉じています。「壊れた変更を誤って minor として出した場合」に対し、新しい版を出して直せと答えたうえで Even under this circumstance, it is unacceptable to modify versioned releases. と書いています。事故でも後ろへは戻れず、前へ進んで直すことだけが残ります。
内部の関数の引数は自由に変えられるのに、公開したAPIの引数は変えられません。この差を生んでいるのは何ですか。
では、約束を書けば守れる範囲が決まるのか
ここまでで対処が見えた気がします。公開範囲を宣言し、書いたことだけを守り、書いていないことは自由に変える——閉じそうに見えます。
ところがこの閉じ方には、規格ではなく観察として報告されている綻びがあります。hyrumslaw.com の著者はこう要約しています。
With a sufficient number of users of an API, it does not matterwhat you promise in the contract: all observable behaviors ofyour 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 へ切り替える場合、それは
creating a new identity for the problem type andthus introducing a breaking change.
にあたる、としています。中身は何も変わらず、同じエラーです。**識別子の綴りを変えただけで破壊的変更になります。**利用者側のコードがその綴りと自分の分岐を突き合わせているからで、誰がどの綴りを見ているかは出した側から分かりません。
「明文化した約束の範囲さえ守っていれば、利用者を壊すことはない」という理解の評価はどれですか。
公開したAPIの、ある版
版を公開すると利用者は自分の都合で取り込みます。誰が取り込んだかの一覧は出した側に届きません。差し替えは仕様が認めていません。
やりたいこと 残る手その版を直す できない。新しい版を出す取り込みを止める できない。利用者へ知らせる誰が使ったか知る できない
利用者の手元にキャッシュされた応答
TTL を付けて返した応答は利用者の側に残ります。誰がどれを持つかの一覧は返した側にありません。渡ったものを消す手段もありません。
やりたいこと 残る手渡した分を直す できない。次の取得を待つ保持を止める できない。期限切れを待つ誰が持つか知る できない
公開した版と、利用者の手元に残った応答。道具も規格も別ですが、出した側にできることの形が同じです。何が同じでしょうか。
「間違えた」と気づいた直後に、それぞれ何ができないかを並べてみてください。
図で見る:境目はどこにあるか
- ・分かれ目は版番号でも社内/社外でもなく、呼び出し元を列挙できるか
右列で起きているのは、検査の場所が自分の手元から利用者の側へ移ったということです。移ったあとは、壊したかどうかを出す前に知る手立てがありません。だから「どの変更が破壊的か」を事前に判定する必要が生まれます。**判定表が要るのは、判定しないと分からなくなったからです。**判定そのものは既存のAPI設計クイズ群が扱います。
この章のまとめ
- 変えられなくなるのは変更が難しいからではなく、呼び出し元を列挙できないから。
- 境目は版番号でも社内/社外でもなく、直し漏れを自分の検査で見つけられる範囲。
- Semantic Versioning の最初の条文は
MUST declare a public API。公開範囲の宣言が番号の規則より前にある。 - 同じ仕様が公開済みの版の中身を変えてはならないと書き、FAQ で事故すら例外にしていない。
- 明文の約束の外側も依存されうる(hyrumslaw.com の著者の観察。規格ではない)。
- 識別子は綴りを変えるだけで壊れる(RFC 9457 §3.1.1 が breaking change と明記)。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
出した版に手を入れることについて、Semantic Versioning が書いているのはどれですか。
用語集(4語)
- public API(公開された範囲)
- 外部の利用者に提供すると宣言した部分。宣言はコード中でも文書中でもよいが、宣言そのものは必須とされている。
- Semantic Versioning
- MAJOR.MINOR.PATCH の3数字で版を表す取り決め。RFC 2119 を引用して MUST を使う規範文書であり、単なる慣習ではない。
- 暗黙の依存
- 明文の約束には無いのに、利用者が実際の実装の挙動に頼っている状態。応答の速さなどが典型。規格の用語ではない。
- 破壊的変更(breaking change)
- 既存の利用者側の書き換えを必要とする変更。判定は既存のAPI設計クイズ群が扱う。