SemVerキャンセル界隈
SemVerをやめろ™
SemVerは一見するとすばらしいアイデアのように思えるので色々なところで使われているようですが、よく考えてみると非常に脆弱な仕組みであり、これに従って依存ライブラリーのバージョンを自動で上げたりするのは無謀と言わざるを得ません。
問題点その0: 自分のバージョニング能力を信頼しなければならない
まずSemVerへの入門者が必ず直面する課題として、いま行うリリースでどのバージョン番号を上げればよいのかを判断するということがあります。この判断にかかる認知負荷は、筆者の経験上、いくらリリースを重ねてもほとんど減りません[1]。より残念なのは、どれだけその判断を頑張ったところで、それが正しい判断かどうかを保証する方法がないということです。いくら慣れていても時には誤って破壊的変更をマイナーアップデートに含めてしまうこともあります。原則的には何も誤っていなくても結果的には破壊的変更と呼ばれることもあります。
より踏み込んだ話をします。あなたの開発したライブラリーに不具合のような挙動があったとして、それを修正するリリースはメジャーリリースでしょうか、パッチリリースでしょうか? 普通に考えてパッチリリースだと思われるかもしれませんが、仮に、下流のソフトウェアの一つがその不具合挙動に依存したコードを書いていたと知ったら? 仮に、下流のほぼ全てのソフトウェアがそうしていたと知ったら? もしくは全然知ることはできないけれど実際にはそうだったとしたら? ライブラリー開発者は、リリースのたびにこういった利用状況を頭に入れておかなくてはなりません。もちろんそのような状況において、開発者が断固として「これはライブラリー単体で見たときに不具合だからパッチリリースする」とするのは(ライセンスで保証なしとされている限りにおいて)正しいですし、「不具合依存が少ないからパッチリリースする」や「不具合依存が多いからメジャーリリースする」とするのも当然正しいです。開発者がその判断に自信を持てるのならどれも正解です。しかしそのような自信家の開発者がどれだけいるでしょうか。
ある開発者がソフトウェアにどれだけすばらしい変更を加えたと思っても、それを適切にバージョニングできるという絶対的な自信がその人になければ、SemVerのもとでリリースすることには慎重にならざるを得ません[2]。SemVerのせいで、ライブラリー開発者はリリースのたびに自分が世界のどこかのソフトウェアのビルドを破綻させるかもしれないという恐怖に耐えることを強いられています[3]。
擬似解決策: 自分のバージョニング能力を信頼しない
メジャーバージョンだけを上げるようにすれば済む話ではあります。自分のライブラリーの全ての利用状況を把握できる人などこの世に存在しない[4]のですから、メジャーバージョンに寄せるのが「より良い」選択です。
お気づきかもしれませんが、より良いからといって常にメジャーリリースしかしないようになるのであれば、それはもはやSemVerを活用しているとは言えません。
問題点その1: 他人のバージョニング能力を信頼しなければならない
問題点その0で挙げたような誤ったバージョニングが依存関係のどこかで一箇所でも発生すれば、SemVerに従って自動アップデートをかけるとビルドが通らなくなるか実行時にバグが発生します。
SemVerに基づくシステムを支えているのは、人間間の信頼という非常に脆弱なものです。もちろん、そもそも他人の作ったライブラリーを利用するということ自体がある種の信頼に基づいているわけですが、ここで意識していただきたいのは、ソフトウェアのすばらしさやその開発者の開発能力とバージョニング能力とは別のものであるという点です。あるライブラリーがどれだけすばらしかったとしても、アップデートの際に壊れる危険は常にあります。
擬似解決策: 他人のバージョニング能力を信頼しない
全ての依存関係を特定のバージョンに固定すれば解決します。
しかし、やはりこれではSemVerを使う意義はほぼ無くなります。
SemVerの意義
ここまで、SemVerに基づくシステム(パッケージマネージャーなど)の問題点を見てきました。SemVerを機械的に「当てにする」ことは大変危ういということがわかると思います。それでは、SemVerの意義とは一体何なのでしょうか?
SemVerによるバージョン番号はあくまで、開発者がソフトウェアのあるバージョンに対してそう設定したというだけの、人間に対するヒントでしかありません。少なくとも単純にコミットIDをバージョン番号として使うよりは、「どちらが新しいのか」という情報と、「この変更は破壊的/機能追加/バグ修正であると開発者が判断した」という情報を含んでいます。このヒントを信じるか信じないかは利用側に任せられています。筆者のように人間のバージョニング能力を信頼できない悲観者にとってはほぼ無意味ですが、信頼できるという人もいるでしょうし、そのような人たちにとっては意味があると思います[5]。
実のところ、冒頭の一文のニュアンスに反して、筆者は本気でこの世からSemVerを駆逐するべきだと考えているわけではありません。そもそもそれは不可能でしょうし、SemVerが開発者を疲弊[6]させずに本当に適切に運用されている世界があるならそれはそれでいいと思います。
筆者としては、世のパッケージマネージャーが依存関係管理に用いるバージョン番号は最初から最後までGitのコミットID(もしくはパッケージのコンテントハッシュ)で、SemVerはむしろ人間用のパッケージ名やデスクリプションの一部程度の扱いでよかったのではと思ってます。その方が実情に即している(人間用のヒントでしかないという事実を反映している)ので。
解決策: SemVerをやめる
今までの擬似解決策はSemVer下でどうにかする方法でしたが、可能ならSemVerをやめる方向に人類は進むべきだと思います。一見すると後退と思われるかもしれませんが、やり方を大きく変える必要があります。もう少し正確に言うと、パッケージ単位での自動アップデート機構[7]やバージョン範囲指定による重複除去機構などをやめるということです。少なくとも、新たにパッケージエコシステムを構築するときに、SemVerをシステム的に取り入れるべきかどうかは考え直す必要があると思います。
筆者はそもそもパッケージという粒度でのリリースという概念から一旦離れるべきという立場で、パブリックAPI(エクスポートされているシンボル)の一つ一つにバージョンをつけてリリースする方が理に適っていると考えます。
パッケージ単位でのバージョニング下では色々な不便があります。例えば、あるパッケージのほんの一部分にのみ破壊的変更を加えるメジャーリリースがあったとすると、その部分を利用していない(がそのことをまだ知らない)下流のソフトウェア開発者は、そのメジャーリリース以降の、利用部分のバグ修正などの恩恵を簡単には(そのパッケージのメジャーアップデート内容の精読と動作検証なしには)受け取れないということになります[8]。パッケージではなくAPIごとに別々にナンバリングされていれば、このような問題は起こり得ません。値レベルのバージョンを使って依存関係を形式的に検証できるようにする版多相のような仕組みも考えられています。
また、パッケージ単位でのバージョン範囲指定による重複除去の代わりに、Unisonのような構文木単位でのコンテントハッシングによる重複除去機構を入れると全てが解決します。
現行主流のエコシステムをこのように改良することはほぼ不可能かもしれませんが、未来のプログラミング言語では解決していてほしいところです[9]。
草草
-
軽減する方法として一つあるのは、可能な限りリリースに含まれる変更を少なくすることです。変更が少なくなればなるほど影響も小さくなるのでバージョニングの判断も楽になります。 ↩︎
-
ここで「リリースというのはそもそも慎重になるべきものだろう」と考える方はSemVerに毒されています。順序が逆で、SemVerが開発者をそうしなければならない状況に追い込んでいるのです。秘密鍵などをリリースに含めてしまわないかどうかなどは、SemVerとは別の問題です。 ↩︎
-
筆者はリリースのたびにこのことを憂慮して、「すばらしい変更をリリースするというのにこんなに気が重くなるのは何かが間違っている」と考えて、SemVerが原因なのかもしれないと思い始めてこのような記事を書くに至りました。 ↩︎
-
霊能者なら可能かもしれません。 ↩︎
-
というかそのような人たちの方が多いからこそここまで使われているのだと思うのですが。 ↩︎
-
筆者は
Cargo.toml
の"1.0.0"
のようなバージョン指定が実際には"^1.0.0"
を意味しているせいでだいぶ疲弊したことがあります。このこと自体は表面的にはCargoの問題点ですが、そもそもバージョン間の互換性という概念自体がSemVerの問題点です。 ↩︎ -
さすがに、CIで充分な検証を行わずにDependabotのPRを自動マージまでしている開発者はいないかもしれませんが、この「充分な」という判断もまた考えもので、結局は自分の判断能力を信じることになります。 ↩︎
-
バックポートまでしてくれる面倒見のいいパッケージなら希望はありますが、メジャーリリースは往々にして大規模なリファクタリングなどを挿んでいたりして、なかなかバックポートまでできる体力のある開発者はいないでしょう。 ↩︎
-
筆者もプログラミング言語を開発していて、パッケージエコシステムをどうするかという思索の中でこのような考えに至りました。 ↩︎
Discussion