なぜセマンティックバージョニングが必要になったか
バージョン番号を見るだけで壊れる更新かどうか判断でき、依存地獄から抜け出せる。MAJOR.MINOR.PATCHが互換性の約束になった経緯を、SemVer提唱から左pad事件まで辿る。
- 同じ版番号で非互換変更が混ざると更新可否を機械判定できず、依存地獄になる。SemVerはMAJOR.MINOR.PATCHへ互換性の意味を固定し、番号の増え方から安全性を判断可能にした。
- MAJORは非互換API変更、MINORは後方互換な機能追加、PATCHは後方互換な修正を表す。npm・Cargo・RubyGemsなどは^や~で許容する更新範囲を宣言できる。
- 2016年の左pad事件は、11行のパッケージ削除がBabelやReactのビルドを連鎖的に壊した。バージョンは互換性の「約束」であり、依存側はその約束を範囲指定で表明し、公開側は約束を破る変更にMAJORを払う——これが得た教訓。
結論(なぜそうなったのか)
横にスクロール
セマンティックバージョニング(SemVer)が生まれたのは、バージョン番号を人間の気分ではなく互換性の契約にするためです。1.4.2 から 1.5.0 に上げても壊れない、2.0.0 になったら壊れるかもしれない——番号の増え方そのものが「この更新は安全か」を機械的に答えるようにする。これがSemVerの一点突破の狙いでした。
背景には「依存地獄(dependency hell)」があります。多数のライブラリが互いを参照し合う近代的な開発では、あるパッケージが同じバージョン範囲のまま中身の互換性を壊すと、それに依存するすべてが連鎖的に壊れます。番号に意味がなければ、更新して良いか安全に判断する術がなく、開発者は依存を固定するか、壊れるのを覚悟で上げるかの二択を迫られていました。
当時の状況と競合(何が問題だったか)
2000年代後半、パッケージエコシステムは急拡大しました。しかしバージョン番号の付け方は各プロジェクトの慣習任せで、X.Y.Z の3つの数字が何を意味するかに合意がありませんでした。よくある付け方は次のように混在していました。
| 流儀 | 番号の決め方 | 依存側の問題 |
|---|---|---|
| マーケティング型 | 大きな節目やブランド戦略で上げる(例 大型リリースを機に一気に増やす) | 番号から互換性を推測できない |
| 日付・連番型 | リリースごとに単調増加させるだけ | 破壊的変更かバグ修正か区別不能 |
| 気分型 | 作者の主観で「大きい変更だから」上げる | 何が壊れるか作者しか知らない |
この曖昧さが依存地獄を生みます。依存側が「バグ修正だけ取り込みたい」と思っても、番号にPATCHの概念がなければ安全な範囲を指定できません。かといって全依存をピン留め(完全固定)すると、今度はセキュリティ修正すら取り込めず、間接依存のバージョンが衝突して解決不能になる。厳密に固定しても、緩く上げても、どちらも地獄という板挟みが問題の核心でした。
ライブラリAがCの 1.x を要求し、ライブラリBがCの 2.x を要求すると、AとBを同時に使うプロジェクトはCのバージョンを1つに決められません。番号に互換性の意味がないと、1.x と 2.x の間で本当に何が壊れるのか誰も断言できず、解決ツールも「安全な妥協点」を計算できませんでした。
決定打・経緯(何が勝敗を分けたか)
転機は2010年、GitHub共同創業者でGravatarの作者でもあるTom Preston-Wernerが、それまで暗黙知だった慣習を明文の仕様として書き下したことです。彼はこれを「Semantic Versioning」と名付け、semver.org で公開しました。仕様はGitHub上でバージョン管理され、1.0.0が2011年、現行の2.0.0が2013年6月に確定します。
仕様の核心は、MAJOR.MINOR.PATCH の各桁に厳密な意味を割り当てたことです。番号を上げるルールは次のように定義されました。
バージョン MAJOR.MINOR.PATCH に対し、次のときに増やす:
MAJOR : 後方互換でないAPI変更をしたとき
MINOR : 後方互換な機能を追加したとき
PATCH : 後方互換なバグ修正をしたとき
さらに 0.y.z(メジャーバージョン0)は「初期開発中で、いつでも何でも変わりうる」と位置づけ、1.0.0 を「公開APIを定義した」宣言と定めました。これにより「まだ約束しない段階」と「約束を始めた段階」が番号で区別できます。
勝敗を分けたのは、仕様そのものの厳密さよりもエコシステムでの採用でした。とりわけnpm(JavaScript)がSemVerを解決アルゴリズムの前提に据えたことが決定的です。npmは依存を範囲で書けるようにし、SemVerの意味論を使って「互換とみなせる最新版」を自動選択しました。この範囲指定に使うのがキャレット(^)とチルダ(~)です。
| 記法 | 意味 | 展開される範囲 | 更新する桁 |
|---|---|---|---|
| `^1.2.3` | 左端の非ゼロ桁を保つ | `>=1.2.3 <2.0.0` | MINORとPATCH |
| `~1.2.3` | MINORを固定しPATCHのみ | `>=1.2.3 <1.3.0` | PATCHのみ |
| `^0.2.3` | 0系はMINORが破壊指標 | `>=0.2.3 <0.3.0` | PATCHのみ |
| `^0.0.3` | 0.0系は全桁が破壊指標 | `>=0.0.3 <0.0.4` | 更新なし |
キャレットの妙は「左端の非ゼロ桁を動かさない」という一貫規則にあります。1.x では最上位のMAJORを固定するので機能追加まで拾い、0.2.x ではMINORが実質のMAJOR扱いになるためPATCHしか拾わない。これはSemVerの「0系はまだ約束していない」思想と噛み合っており、^ ひとつで「安全な更新だけ自動追従」を宣言できるようになりました。RubyGems(悲観的バージョン制約 ~>)やRust/Cargo(既定でキャレット的挙動)も同種の意味論を採り、SemVerは事実上の業界標準になりました。
^ は「作者のMINOR/PATCHの約束を信じる」、~ は「PATCHだけ信じMINORは自分で確認する」という信頼度の表明です。lockファイル(package-lock.json 等)で実際に解決された版を固定すれば、範囲で意図を、ロックで再現性を両立できます。範囲=許容ポリシー、ロック=確定結果、と役割を分けて考えると混乱しません。
今への影響と教訓
SemVerが機能する前提は、公開側が約束を守ることです。この約束が破れるとどうなるかを最も鮮烈に示したのが、2016年3月22日の左pad事件でした。作者Azer Koçulu氏が、kik というパッケージ名の商標を巡るnpm社との対立の末、自作パッケージをすべて公開停止(unpublish)します。その中に、文字列を左詰めするだけの11行の小さなパッケージ left-pad が含まれていました。
この極小パッケージは、BabelやReactをはじめ無数のライブラリが間接依存していました。npmの依存解決は範囲指定に従って left-pad を取りに行きますが、レジストリから消えたため取得に失敗し、世界中のビルドが連鎖的に壊れました。npm社は数時間後にパッケージを手動で復元し、以後「公開から24時間経過し、かつ他プロジェクトが依存しているパッケージは削除不可」という規則を導入します。
左pad事件はSemVerの番号付けの失敗ではなく、依存の可用性とエコシステムのガバナンスの問題でした。ただし教訓は地続きです。バージョンが互換性の約束であるなら、その約束の対象(公開済みの版)は勝手に消えてはならない。範囲指定は「互換な最新版が存在し続ける」ことを暗黙に前提にしており、レジストリ側の不変性(immutability)が伴って初めて意味を持ちます。
現在のSemVerの効き方は次の通りです。依存解決ツールは番号の意味論を使って、無数のパッケージの範囲制約を同時に満たす組み合わせを計算します。CIは ^ 更新を自動で取り込み、MAJORが上がったものだけ人間のレビューに回す運用が定着しました。破壊的変更を出す側は、たとえ小さな変更でもMAJORを1つ払う覚悟が求められます。ここから一般化できる原則は明快です。
- 互換性は約束として番号で表現する: 「どれくらい変えたか」ではなく「利用者との約束を破ったか」で桁を選ぶ。破ったならMAJOR、これがSemVerの本質。
- 依存側は約束を範囲で表明する:
^や~は「作者のどの約束まで信じるか」の宣言であり、lockファイルで再現性を別途担保する。 - 標準は仕様ではなくエコシステムで勝つ: SemVerが普及したのは文章が良かったからではなく、パッケージマネージャが解決アルゴリズムに組み込んだから。
- 約束の土台(不変性・可用性)ごと守る: 番号の意味論は、公開済みの版が消えない前提の上にだけ成立する。
パッケージが依存を解決する仕組みや実行環境の内部はプログラミングを、CIで更新を安全に取り込む変更管理・自動化の実務はDevOpsを参照してください。
- MAJOR=非互換なAPI変更、MINOR=後方互換な機能追加、PATCH=後方互換なバグ修正、という桁の意味を正確に。
0.y.zは初期開発で無保証、1.0.0が公開APIの確定宣言、という段階の違い。^1.2.3は>=1.2.3 <2.0.0、~1.2.3は>=1.2.3 <1.3.0。0系ではキャレットが保守的になる(^0.2.3は>=0.2.3 <0.3.0)。- 左pad事件(2016年)は削除起因の可用性問題で、npmはunpublish制限を導入。SemVerの番号付けそのものの失敗ではない点。
一段で言うと
SemVerは、バージョン番号を作者の主観から利用者への契約へと変えた合意です。MAJORで「約束を破った」と宣言し、MINOR/PATCHで「破っていない」と保証する。依存側は ^・~ でその約束をどこまで信じるかを表明し、ツールが組み合わせを機械的に解く。左pad事件が示したのは、この約束は公開済みの版が消えない土台の上でしか成り立たないという続きの教訓でした。互換性を数字で約束する——依存地獄を出口へ導いたのは、この一点の発明です。
なぜ?の記事ガイド
なぜセマンティックバージョニングが必要になったかを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
SemVer
比較で見る軸
難易度: advanced / カテゴリ: なぜ? / タグ数: 6
導入後に効く点
MAJORは非互換API変更、MINORは後方互換な機能追加、PATCHは後方互換な修正を表す。npm・Cargo・RubyGemsなどは^や~で許容する更新範囲を宣言できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- なぜ?
- タグ数
- 6
判断チェックリスト
- 自社の用途が「SemVer / バージョニング」に近いか確認する。
- 強みである「同じ版番号で非互換変更が混ざると更新可否を機械判定できず、依存地獄になる。SemVerはMAJOR.MINOR.PATCHへ互換性の意味を固定し、番号の増え方から安全性を判断可能にした。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。