後方互換性と和解せよ

# 後方互換性ってなに？

みんな大好き信頼の塊 Wikipedia さんの「[互換性](https://ja.wikipedia.org/wiki/互換性)」の記事によると、後方互換性は以下のように定義されています。

> 古いシステム向けのデータなどが新しいシステムでも全て使用できること。

ソフトウェアの文脈では「ソフトウェアのバージョンをアップしても、それに依存する既存のソフトウェアが正常に動作すること」と読み替えられる場面が多いかと思います。

# Goと後方互換性

Go言語は伝統的に後方互換性を大事にしてきた言語です。それは [Go 自体](https://go.dev/doc/go1compat)についてもそうですが Go を使って書かれたソフトウェアについても Go Modules の導入時に後方互換性について[具体的な指針](https://go.dev/blog/module-compatibility)が打ち出されたりしたこともありました。最近であれば Rob Pike が Go 1.18 について「[標準ライブラリの内部実装は変えずにリリースしよう](https://github.com/golang/go/issues/48918)」と提言したことが話題になったりもしました。Go 1.18 からは Generics が導入されるため、sort などの一部モジュールにおいてGenericsを使った再実装が可能になります。理論上は後方互換性を維持したまま再実装が可能なはずなのですが、全部に一度に手を付けてしまうとミスが発生してしまうかもしれないと Rob Pike は理由付けしました。私はこれには大賛成で、どのみち機能的に変化が起きないのであれば一旦は今のままにして、Go の Generics が成熟したタイミングで再実装するのでも遅くないと考えています。

また、私の知る限りではコミュニティの中でも後方互換性はとても大事にされていて、後方互換性が保証されない場合にはメジャーバージョンを挙げたり名前を変えたりして既存のソフトウェアを壊さないように配慮する文化があります。これは私が Go のコミュニティが好きな理由の一つでもあります。しかし、2018年1月4日にそんな Go 界隈に激震が走る大事件が起こりました。

# 「全てが壊れた」

2018年1月4日、go.uuid という Go コミュニティ内で半ば *de facto* になっていた UUID ライブラリにある Issue が投稿されました。go.uuid は記事執筆時点で既知のものだけで[18,716ものプロジェクト](https://pkg.go.dev/github.com/satori/go.uuid?tab=importedby)に利用されている超大御所ライブラリです。当時私はとある個人プロジェクトで go.uuid を使っていたのですが、go.uuid が原因でビルドがコケていたので「どうしたんだろう？」と思い GitHub の Issue 一覧を覗き込んで以下の Issue を発見しました。

## [Everything is broken](https://github.com/satori/go.uuid/issues/67)

### 原文

> After this sudden change, everything in my code will NOT work. Not even build with docker
> 
> All I get is this `multiple-value uuid.NewV4() in single-value context`


### 和訳

> この突然の変更以降、全てのコードが**動かなくなった**。Dockerでビルドしてもダメ。
>
> `multiple-value uuid.NewV4() in single-value context` としか出ない。

これは私が過去に見てきた中で一番好きな Issue です（悪い意味で）。まず**「全てが壊れた」**というタイトルの時点で最高ですよね。しかも年明け早々というタイミングも相まって一層混沌の様相を呈していました。ただこちらは duplicate として比較的早いタイミングで閉じられてしまったので詳細に関しては以下を参照した方が良いです（実際私も当時こちらで議論に参加していました）。

## [Breaking API Change](https://github.com/satori/go.uuid/issues/66)

### 原文

> [0ef6afb](https://github.com/satori/go.uuid/commit/0ef6afb2f6cdd6cdaeee3885a95099c63f18fc8c) breaks the API that's been stable for years. Why is this important enough to change with no warning, despite the community standard to be avoiding breaking API changes without some kind of versioning system? The original issue indicates some sort of deprecation warning was considered, but seems to never have been implemented. If users are going to have to rug pulled out from under them with no warning, why should we use this library?

### 和訳

> [0ef6afb](https://github.com/satori/go.uuid/commit/0ef6afb2f6cdd6cdaeee3885a95099c63f18fc8c)がずっと安定してたAPIを破壊した。コミュニティとしてバージョニングせずに破壊的なAPIの変更をしないというのが標準的なのに、それでも事前の警告もなしに変更するほどに重要なの？元の issue では何かしらの deprecation を出すことも考慮されてたみたいだけど結局実装されなかったみたいだし。なんの警告もなしにいきなりユーザの足元をすくうライブラリを使う意味ってある？

この元凶となったのがこのレポジトリのオーナーが直接 master に push した以下の commit でした。

## [Return enthropy errors from UUID generation.](https://github.com/satori/go.uuid/commit/0ef6afb2f6cdd6cdaeee3885a95099c63f18fc8c)

Commit Message の誤字はご愛嬌として、内容としては[乱数生成器のエントロピーが十分でない状態でエラーを上げる](https://github.com/satori/go.uuid/issues/24)というものでした。問題提起自体は非常に合理的な内容なのです。しかし、その実装方法に問題がありました。

Goには関数のオーバーロードのような機能がないため、同等の関数を異なる型シグネチャで実装するためには名前を変える必要があります。そのため、関数の型シグネチャが変わる仕様変更を導入する場合は既存の関数は触らずに新しく関数を切ることが通例となっています。上述の issue から読み取れる通り、go.uuid はこれを行わずにもともとあった関数の返り値の型を `UUID` から `(UUID, error)` に変更してしまったのです。当時は Go Modules もまだなかった頃でバージョニングも甘かったためデフォルトである main ブランチ追従で依存を解決をしていました。しかも go dep や glide などで使えるバージョンタグなども貼っていなかったため、新しくビルドすると突然 UUID 生成の関数が返り値の不整合を起こしビルドできないという状況になってしまったのです。

このような広範に影響のある変更を 1. 予告なく 2. PRを出さず 3. master に直接 push してしまった。この三拍子が揃ってしまったことで大きな批判と混乱を招く結果となってしまいました。この一件に遭遇して以来、私は個人プロジェクトでも後方互換性を強く意識した開発をするようになりました。この記事を読まれている皆さんも、たとえ Go がメインで使用している言語でなくても、後方互換性という側面にぜひ気を使っていただきたいと思います。