世界を動かす技術を、日本語で。

Goコードをモダナイズするための「go fix」の活用

概要

  • Go 1.26で go fixサブコマンドが全面的に刷新
  • go fixは最新の言語・ライブラリ機能を活用したコード改善を自動化
  • 主要な使い方・インフラ構造・今後の進化 について解説。
  • モダナイザーや自己定義分析ツール の役割を紹介。
  • 複数回の実行や競合解決のポイント も説明。

Go 1.26のgo fixコマンド概要

  • Go 1.26からgo fixサブコマンドが全面的に再設計、アルゴリズムの刷新。

  • go fixは、コードの最新化やベストプラクティス適用の自動化 を目指す。

  • コマンド実行例:go fix ./...でカレントディレクトリ配下の全パッケージを修正。

  • 生成ファイルには適用せず、生成ロジック自体の修正を推奨。

  • ツールチェーン更新時にはgo fixの実行を推奨、クリーンなGit状態での利用が望ましい。

    • 変更内容の事前確認は-diffフラグで可能。
    • 利用可能なfixer一覧取得:go tool fix help
    • 特定アナライザーの詳細:go tool fix help <analyzer名>
  • 個別アナライザーの有効/無効化-any-any=false等のフラグ指定。

  • ビルド構成ごとに動作、複数プラットフォーム対応ならGOARCH/GOOSを切り替えて複数回実行。

モダナイザーの役割と進化

  • Go 1.18のジェネリクス導入以降、ライブラリや言語仕様の進化が加速

  • maps.Keys等の新機能活用で、従来の冗長なループを簡潔に表現可能

  • LLM等のAIツールは古い書き方を出力しがち、最新イディオム普及にはコードベースの刷新が不可欠。

  • goplsやgo fixに多数のモダナイザーを搭載、代表例:

    • minmax :if文をmin/max関数に置換。
    • rangeint :3項for文をrange-over-intに変換。
    • stringscut :Indexとスライスをstrings.Cutに置換。
  • 新機能追加時はモダナイザー同時開発が標準化、今後も拡充予定。

Go 1.26の新機能:new(expr)とその自動変換

  • Go 1.26からnew関数が任意の値を初期値としてポインタ生成可能に進化
    • 例:ptr := new("go1.26")
  • これまで必須だったnewInt等のヘルパー関数が不要、コードの簡素化。
  • newexprフィクサー が自動でヘルパー関数をnew(expr)へ変換、呼び出し元も一括修正。
  • go.modやbuildタグでバージョン指定が必要、適用範囲の制御。
  • 未使用ヘルパー関数の検出はdeadcodeコマンドで補助

シナジー効果と複数回実行の重要性

  • 一つのモダナイザー適用で他の適用余地が生まれる場合あり(シナジー効果)
    • 例:minmax適用後、さらにmin適用が可能。
  • 複数アナライザーによる連携修正 で効率化と最適化。
    • 例:stringsbuilder適用後、さらにfmt.Fprintfへの変換が提案されるケース。
  • go fixは複数回実行して「収束」させるのが推奨、通常2回程度で十分。

競合解決とマージ

  • go fixは一つのファイル内で多数の修正を三者間マージで統合
  • 競合発生時は該当修正をスキップし、警告を出力、再実行を推奨。
  • 意味的競合 (例:変数が未使用になる等)は手動対応が必要。
  • 未使用importの自動削除機能 も搭載、一般的な競合は自動処理。
  • 重大な意味的競合はコンパイルエラーで発覚、見落としは困難。

Go分析基盤の進化と自己定義ツール

  • go vetやgo fixは独自アルゴリズム群(チェッカー・フィクサー)を搭載
  • 自己定義の分析・修正ツール開発が可能、組織独自のコーディング規約やベストプラクティスの自動化支援。
  • 今後はstaticcheck等の外部アナライザーもgo fixに統合予定、適用範囲の拡大。

まとめ

  • Go 1.26のgo fixは大幅刷新で、最新イディオム適用や保守性向上に寄与
  • モダナイザーや個別アナライザーの活用で、組織・個人のコードベースを効率的に最新化
  • 複数回実行や競合解決の理解が、より安全で効果的な自動修正の鍵
  • 自己定義ツールや外部アナライザーとの連携で、今後も進化が期待されるGoの分析・修正基盤

Hackerたちの意見

この部分がすごく好きだったな:2024年12月、LLMコーディングアシスタントが盛り上がっている中で、こういったツールがトレーニングに使われたGoコードのスタイルに似たGoコードを生成する傾向があることに気づいたんだ。新しい、より良い表現方法があるのにね。あまり明らかじゃないけど、同じツールが「Go 1.25の最新のイディオムを常に使え」と指示されても、新しい方法を使うのを拒否することが多かった。場合によっては、機能を使うように明示的に指示されても、その機能が存在しないと否定することもあった。[...] 将来のモデルが最新のイディオムでトレーニングされるようにするためには、これらのイディオムがトレーニングデータに反映されている必要がある。つまり、オープンソースのGoコードのグローバルコーパスにね。

それ、めっちゃよくあるからイライラするよね。Goは後方互換性があるから、全てのコードはコンパイルできるけど、見た目が全然違うんだよね。Pythonでも同じ問題があるけど、APIの変更が実際に壊れる原因になるから厄介。だから、Goはコード生成に関してはかなり優れてると思う。言語の安定性は他に比べられないし、標準ライブラリも多くのユースケースをサポートできる強力なツールだからね。

LLMの使用は、均質で平凡なコードを生むことになるだろうね。

PHPも昔、Stackoverflowの古い悪いアドバイスを一掃する努力をしたことがあるんだ(例えば、magic_quotesを推奨する投稿とか)。LLMはちょっと違う問題を引き起こすね。というのも、一度悪いアドバイスがモデルに入ったら、ほぼ消えないから。理論的には、アドバイスの質をテストするのは簡単だけど、どうやってその結論に至ったのかを理解して、将来のモデルのために修正するのは難しい。モデルのトレーナーが自分のRCモデルをいろんなコミュニティに提出して、特定のトピックについて嘘をついていないか確認することは考えにくいから、次世代の準備をする必要があるし、元々トレーニングされた悪いソースを特定できたことを期待するしかないんだ。

C++のコードでもそれは感じるね。でも「修正」するのは簡単じゃないと思う。そう思ってるけど、もっと「モダン」なC++コードが公開されることを期待してるよ。

自分の経験から言うと、Goの並行処理コードは特にひどいことが多いね。ほとんどがチュートリアルみたいな内容で、過度に単純化されてて、エラー処理やエッジケースの扱いが全然足りなくて、使うのが危険なレベルなんだよね…でも、シンプルだからってレビューを通過しちゃうんだよね。Goの並行処理は簡単だ!って思われてるし。で、レビューで問題を指摘すると、著者がそれをLLMに戻して、見た目はそのケースを処理してるように見えるコードが追加される…その過程で微妙なデータレースや珍しいデッドロックを引き起こすんだよね。ほぼ毎回そうなる。全てのモデルで。

LLMを使ってコードを書くアイデアは、もうやめにしたいな。改善するためじゃなくて。なんでみんなロブ・パイクの言うことを聞かないの?この技術は私たちにとって良くないよ。ソフトウェアや世界全体にとって汚点だと思うけど、みんなが雑なものを求めるのは分かる。大衆は雑なものを求めてるから。

こういうツールがあるからこそ、Go言語は本当に素晴らしい言語なんだよね。rangeintの追加を見逃してたけど、go fixを使えばその改善が無料で手に入る!Goチームには本当に感謝だね。

他の言語を使いたいと思う場面はたくさんあったけど、Goのツールがあまりにも優れているから、結局Goで書くことになっちゃう。ビルドのテストやリンティング、そして驚くべきコンパイル速度を超えるのは難しいよね。

:=を使ったforループを検索して、手動で修正したよ。いくつかの形式のforループを見つけたし、多いところはregexpを使った。リデザイン後、このツールはめっちゃクールだね。

ソースコードをモダンにするために修正できるツールは本当にクールだと思う。JavaだとOpenRewriteが思い浮かぶけど、他の言語では特に思いつかないな。最近OpenRewriteについて知ったけど、ずっとJavaを書いてるのにね。Goは好きじゃないけど、こういうツールが言語に組み込まれているのは、言語の人気や成熟度にとって大きなことだと認めるよ。他の言語はビルドツールやテストフレームワークについて、こんなに意見を持ってないからね。新しい言語が出てくるにつれて、Goから学ぶことが多いと思う。

Javaや.NETのIDEは、何年も前からこの機能を持ってるよね。Eclipseが最も使われていた頃から、Checkstyleや他の似たようなプラグインのヒントがあったし。

Pythonにはpyupgrade経由でいくつかのこれがあって、ruffにも含まれてるよ: https://docs.astral.sh/ruff/rules/#pyupgrade-up

eslintはもう10年前から--fixを持ってるから、これは新しいことじゃないよね。

CoccinelleはC用で、Linuxカーネルの開発者たちに何十年も使われてきたものだよ。2009年の記事があるから、見てみてね: https://lwn.net/Articles/315686 それに、C#やJava、他の多くの言語のIDEツールもあるし、JetBrainsのIDEは何百万行ものコードに対して大規模なリファクタリングやコード修正ができるんだ(俺はいつも使ってるよ)。新しい言語機能に自動的にコードをアップグレードすることもできるしね。隣のコメントはちょっと「間違ってる」かな。数年じゃなくて、何十年も前から使われてるんだよ。それに、JetBrainsには「構造的検索と置換」って機能があって、言語の構文を考慮に入れてるから、テキストエディタや擬似IDE(例えばvscode)で見るような単なるテキスト以上のレベルで動作するんだ: https://www.jetbrains.com/help/idea/structural-search-and-re... https://www.jetbrains.com/help/idea/tutorial-work-with-struc... 現代の.NETでは、C#コンパイラにRoslynアナライザーが組み込まれていて、関連するコード修正もよくあるけど、AFAIK、IDEからしか操作できないんだ。これについてのチュートリアルがあるよ: https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/t...

でも他の言語については、すぐに思いつくものはないな。「cargo clippy --fix」はRust用で、基本的にはリンターと統合されてるけど、すべてのリントを修正するわけじゃないんだ。

誰か、こんな風にTypeScriptのコードベースを変換した経験ある人いる?TypeScriptのLSPサーバーはあまり強力じゃなくて、関数から位置引数を削除するような基本的なこともサポートしてないんだ(呼び出し元も全部)。jscodeshiftはこれに使えるかな?もしかしたらClaudeと組み合わせて?

セルフサービスの分析ツールのアプローチが、これの一番過小評価されてる部分だと思う。自分のコードベースに合わせたカスタムフィクサーを書けるのは、本当に助かる。私たちは大きなGoのモノレポを管理していて、内部APIの移行はいつもgrep+sedの冒険になる。半分は誰かがエッジケースを見逃すし、もう半分はsedパターンがマルチラインで壊れる。Goの型システムを理解しているAST対応のリライターがあるのは、regexハックに比べて大きなアップグレードだよ。-diffプレビューオプションもCIで実用的にしてくれる。go fix -diffを実行して、出力が空でなければ失敗する。それだけで、多くのカスタムリンターを置き換えられるかも。

Goとその長年の慣習やツールは、私のエージェント的なコーディングにとって大きな助けになってる。go run main.goがアプリの開発環境を立ち上げるための慣習で、複数の作業ツリーや中央の設定管理、事前移行されたデータベースなどをサポートしてる。これで、アプリの多くのバージョンを同時に開発・テストするのが簡単で早い。共有ツールについては、https://github.com/housecat-inc/cheetahを見てみて。もちろん、go generatego buildgo testgo vetは、いつも素早い開発とテストのループの一部だよ。go fixも加えるのが楽しみ。