はじめに
TIG(Technology Innovation Group)の真野です。
Go 1.26ブログ連載 の3日目は、go fix コマンドのアップデートについて解説します。
go fix の出発点
リリース内容へ入る前に、go fix そのものについて解説します。
まず、Go 1.26で新しく go fix コマンドが追加されたわけではありません。コマンド自体は2011年4月15日に公開されたIntroducing Gofixというブログで紹介され、その翌月リリースのr57 で追加されました。つまり、誕生から15年近く経過する由緒ある(?)ツールとも言えます。
それにも関わらず、 go fix コマンドはあまり有名で無いと思います。なぜでしょうか。
まず、Go言語が1.0に到達したのは、2012年3月28日で、go fix コマンドが紹介されたのはその1年前です。現在と大きく異なり、正式版に向けて週次ベースでリリースしており、破壊的な仕様変更も行われていた時期です。その中には http や os といった利用頻度が高いパッケージのAPI変更も含まれるため、開発者としては追随が大変でした。
go fix の出自は、こうしたAPIの破壊的変更にともなる既存コードの書き換えすることです(Goチーム公式からバージョンアップに伴う移行ツールが提供されていたという訳で、ホスピタリティが凄さを感じます)。もちろん、バイナリを書き換えるわけではないので再ビルドが必要ですが、利用者視点では手間という面で、このツールがあるのと無いでは大きな差でしょう。Go開発チームとしても、 go fix という変換ツールがあるこそ、API変更のコストに囚われすぎずより良い言語にすることが集中できた(意訳)という訳で、当時はとても重要な位置づけでした。
バージョン1.0以降の go fix
一方で、Goはバージョンが1.0に到達してからは破壊的な変更が行われなくなりました。
これ自体は良いことですが、go fix の存在感は低下しました。今となっては不要では?みたいな声を聞いたこともあります。実際、1.0以降のリリースノートで go fix の更新は、context の書き換えくらいでした(他にも見落としていたらすいません)。
これ以降は更新が無かったため、ほとんどの開発者の意識から外れていたのではないでしょうか。 go fmt や go vet は広く活用されていたのに、不憫な子。
Go 1.26での go fix
さて、Go 1.26です。
リリースノートやIssueであるcmd/go: fix: apply fixes from modernizers, inline, and other analyzers #71859 を読むと、以下の点が更新されました。
go fixの内部実装がgolang.org/x/tools/go/analysisという、go vetやgoplsが利用しているのと同じフレームワークを使うようになった。型情報や変数のスコープなどを理解して安全な書き換えが可能になるとのこと- 従来の機能は廃止された
- 新しい文法や書き方に自動的に書き換える(モダン化する)ツールになった
特に3点目はさらっと書いていますが重要です。標準パッケージのAPIで破壊的変更を修正するツールから、コンパイル上はエラーにならないけど今となっては古く、非推奨になった書き方を変更するツールになりました。先程紹介した、context の書き換えに近いことがメインになると考えると、AIが生成した古いコードを変換したりにも便利そうです。
実際に何を書き換えてくれるのか
go fix で追加されるモダン化処理ですが、GoのLanguage Serverである gopls で使われていた実装が利用できるようです。モダン化とインライン化という2大機能があります。
- モダン化(#75266)
- https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize
- Go 1.26時点で24個機能がある
- 詳細は後述
- インライン化(#75267)
- https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline
- 単一機能
//go:fix inlineディレクティブを関数に付与することで、その関数の利用者のコードを自動的にインライン展開して書き換えることができる
ここから先は、実際の動作を見ていきます。
動かしてみる
モダン化からは以下の4つを動かしてみます。
- rangeint:
for i := 0; i < 10; i++をfor i := range 10に - minmax:
if a > b { m = a }をm = max(a, b)に - any:
interface{}をanyに - slicessort:
sort.Sliceを Go 1.21で追加されたslices.Sortに
さらにインライン化を試すという流れを考えています。
環境情報
1.26rc2 で動かします。RCバージョンの場合、go.mod でGoバージョンを1.26にしておく必要があります。
$ go version |
1. モダン化
ちょっと古くさいファイルを用意します。
package main |
go fix コマンドを実行します。
# go fixの実行 |
差分です。おー、これがモダン化..!!
$ git diff |
go vet と似ている使い勝手で、最新のGoの流儀に合わせてくれるのは嬉しい感じがします。 -diff コマンドで差分を出すこともできるので、CIでの使い勝手も良いと思います。
2. インライン化
従来、ライブラリ提供側の視点で、関数の非推奨化はできましたが、あくまで非推奨と伝えるだけで一括で変換などは行えませんでした。インライン化はそれを支援する方法です。
package lib |
package main |
go fix ./... を実行すると、main.go が以下のように書き換えられます。
$ git diff |
ライブラリ提供者側の視点としては、公開した関数を変更する場合に機械的にマイグレーションする手段ができたということで、心理的に余裕が生まれるのではないでしょうか。
ちなみにですが、従来の // Deprecated のコメントとの併用も可能です。
+// Deprecated: Use NewFunc instead. |
呼び出し側は次のように取り消し線などで、非推奨であることがフィードバックされます。
基本的には、 //go:fix inline を追加するときは、 // Deprecated もセットで運用することになるのかなと予測します。
golangci-lintでも –fix オプションがあるけど使い分けは?
golangci-lint run --fix などとすれば、Golangci-lintもコードの置換も行ってくれます。
リンター一覧で Autofix タグがついているものがその対象です。
モダン化はいくつか重複している機能もありそうですが、詳しく見ていません。おそらく、衝突するような機能はgolangci-lint 側で無効化/修正されると思いますので(根拠はなく予想です)、まずは go fix と golangci-lint run --fix の両方を実行してみて試すのが良いのではないかと思いました。
インライン化は該当の機能はないのでこちらについては go fix の利用が必須になるかと思いした。
さいごに
コードレビューなどでより新しい書き方をsuggestするというのは、あまり創造的では無いと思っていました。これが go fix でかなり省略されるということで、AIが古いコードを出してきても矯正できる点は良いと思います(go fix で直してまた古いコードに書き換えられたりはあるかもですが)。
とりあえず、 go fmt、go vet、go fix の3点セットで使っていこうと思いました。