コンテンツへスキップ

セマンティックバージョニング(SemVer)の考え方 — バージョン番号がなぜ3つの数字なのか

ソフトウェアのバージョン番号を見ると、多くの場合「1.6.11」のように数字が3つ並んでいる。これは思いつきの命名ではなく、セマンティックバージョニング(Semantic Versioning、通称SemVer)という広く使われている命名規約に基づいている。ここでは、なぜ数字が3つ必要なのか、そしてこの規約を実際にコードへ落とし込むときにどんな設計判断が発生するかを整理する。

MAJOR.MINOR.PATCH という3つの意味

SemVerでは、バージョン番号を MAJOR.MINOR.PATCH(例: 1.6.11)の形で表し、それぞれの数字が異なる意味を持つ。

  • MAJOR(第1位): 互換性のない変更を行ったときに上げる。既存の使い方が壊れる可能性がある変更
  • MINOR(第2位): 後方互換性を保ったまま機能を追加したときに上げる。既存の使い方はそのまま動く
  • PATCH(第3位): 後方互換性を保ったままバグを修正したときに上げる。挙動の破壊を伴わない

補足: 後方互換性とは、新しいバージョンでも古いバージョン向けに書かれたコードや操作方法がそのまま動作すること。互換性が壊れる変更は、利用者にとって「これまでと同じように使ったら動かなくなった」という影響を持つ。

重要なのは、この3つの数字が単なる連番ではなく、それぞれの桁が上がることに固有の意味を持たされている点である。バージョン番号を見ただけで、「今回の更新は安全に上げてよさそうか、それとも動作確認が必要そうか」をある程度予測できるようにする、という目的がある。

なぜ1つの数字の連番では足りないのか

仮にバージョン番号が単純な連番(v1, v2, v3, …)だったとする。この場合、番号を1つ見ただけでは、それが軽微な修正なのか、大きな仕様変更なのかを利用者側は判断できない。結果として、利用者は毎回リリースノートを読んで初めて「これは上げても大丈夫か」を判断することになり、番号自体が持つ情報量はゼロに近くなる。

MAJOR.MINOR.PATCHの3分割は、この「番号自体に意味を持たせる」ための工夫である。PATCHだけが上がっている更新であれば、通常は安心してすぐに適用してよいと判断できる。MAJORが上がっていれば、更新前に変更内容を確認したほうがよい、という目安になる。番号の構造そのものがコミュニケーションの手段になっている。

バージョン比較を「数値」で行う理由

このアプリでも version.pyVERSION = "1.6.11" という形で現在のバージョンを保持しており、起動時にサーバー側の最新バージョン情報と比較して更新の有無を判定している。この比較処理には、SemVerを扱う上でよく見落とされる落とし穴がある。

def _is_newer(remote_version, current_version):
    """セマンティックバージョニング(X.Y.Z)で比較"""
    try:
        remote = tuple(int(x) for x in remote_version.strip().split('.'))
        current = tuple(int(x) for x in current_version.strip().split('.'))
        return remote > current
    except (ValueError, AttributeError):
        return False

もしここで文字列としてそのまま比較("1.10.0" > "1.9.0")してしまうと、Pythonの文字列比較は先頭の文字から辞書順(1文字ずつのコード値順)に見ていくため、"1.10.0""1.9.0" より小さいと判定されてしまう。1文字目の 1 は同じだが、2文字目で .109 を比べる形になり、09 より小さいためだ。数値としては 1.10.0 のほうが新しいにもかかわらず、文字列比較では逆の結果が出てしまう。

これを避けるため、_is_newer では各バージョンをドットで分割し、それぞれの区切りを int() で整数に変換したうえで、タプル同士として比較している。タプルの比較は要素を先頭から順に数値として評価するため、(1, 10, 0) > (1, 9, 0) は正しく True になる。「MAJOR.MINOR.PATCHという3つの数字」という構造を尊重し、各桁を独立した数値として扱って初めて、バージョン番号本来の大小関係が成立する。文字列としての見た目の順序と、意味としての新旧の順序は、一致しない場合があるという点が、この設計の核心にある。

バージョン番号は「1箇所」に住んでいない

もう一つ、SemVerを実運用に落とし込むときに直面する課題がある。バージョン番号は多くの場合、コード側の定義ファイルだけでなく、インストーラーの設定ファイル、配布サーバー上のメタ情報、ダウンロードリンクのファイル名など、複数箇所に分散して記述される。このアプリでも、version.py のほかに、インストーラー生成用の設定ファイルや、配布用のバージョン情報ファイル、ダウンロードページのリンク文字列など、複数のファイルにバージョン番号の文字列が個別に埋め込まれている。

これらを手作業でそろえて更新しようとすると、どれか1箇所を更新し忘れるリスクが常につきまとう。実際、過去にインストーラー側の設定だけ更新が漏れ、新しいバージョンのはずが古い番号のままインストーラーが生成されてしまう事故が起きたことがある。この教訓を踏まえ、対象ファイルを一覧化して一括更新するスクリプトを用意し、あわせて「現在バージョンとして正とする定義(version.py)」と「各ファイルに書かれた番号」が一致しているかを検証する --check モードも実装した。

python tools/bump_version.py 1.6.11 2026-06-11   # 一括バンプ
python tools/bump_version.py --check              # 整合性のみ検証(不一致ならexit 1)

--check はビルド処理の事前ゲートとして組み込んであり、いずれかのファイルのバージョン番号が version.py の値とずれていると、そこでビルド自体を止める。人手による確認に頼らず、機械的に「番号が全部そろっているか」だけを毎回検証する形にしている。

まとめ

論点 ポイント
MAJOR.MINOR.PATCHの3分割 番号そのものに「壊れる変更か/安全な追加か/単なる修正か」という情報を持たせる
単純な連番との違い 連番は情報量がゼロに近く、都度リリースノートの確認が必要になる
文字列比較の落とし穴 辞書順比較では "1.10.0" < "1.9.0" になってしまう
タプル化して数値比較 各桁を int() に変換してタプル比較することで正しい大小関係になる
番号の多重管理 複数ファイルに分散するバージョン番号は、一括更新+整合性検証で人手のミスを機械的に防ぐ

セマンティックバージョニングは、単に「バージョン番号の付け方のルール」ではなく、番号そのものを利用者への伝達手段として設計する考え方である。そして、その考え方を実際のコードで扱うには、数字としての比較の正しさや、複数ファイルへの分散管理といった、規約の外側にある実装上の注意点にも目を向ける必要がある。