LEDが点灯するネットワークスイッチのポート接写
技術解説

Go 1.26のgo fix inlineで非推奨API移行が壊れる落とし穴と確認手順

目次を見る

Go言語で書かれた基盤ツールやIaC(Infrastructure as Codeの略、コード化したインフラ定義)の自動化スクリプトを保守しているSRE(Site Reliability Engineeringの略、サービスの信頼性を運用で担保する役割)の方に向けた内容です。Go 1.26で刷新されたgo fixコマンドと、//go:fix inlineという新しいディレクティブ(コンパイラやツールに対する指示コメント)を扱います。

非推奨APIのマイグレーション(移行作業)は、インフラ運用チームにとって地味に重い負債です。たとえば社内共通ライブラリの関数にDeprecatedコメントを付けても、呼び出し側のコードが何十箇所もあると、誰も手を付けないまま数年が経過します。Go 1.26はこの構造そのものに手を入れました。

何が起きるか

Go 1.26(2026年2月リリース)でgo fixコマンドが全面的に作り直されました。従来のgo fixは古い書き換えルール(fixer)の集合体でしたが、それらはすべて廃止され、go vet(静的解析でコードの問題を検出するツール)と同じ解析基盤の上に再構築されています。

影響が大きいのは//go:fix inlineという新しいディレクティブです。これは関数のコメントに1行書くだけで、その関数を呼んでいる全呼び出し元を、新しいAPIの呼び出しに自動で書き換えられる仕組みです。

公式ブログでの例はioutil.ReadFileです。この関数はGo 1.16以降、内部でos.ReadFileを呼ぶだけの薄いラッパーになっていました。Deprecatedコメントは何年も付いていましたが、リンター(静的解析で警告を出すツール)が文句を言うだけで、実際のコード書き換えには繋がっていませんでした。//go:fix inlineはこの「警告はあるが移行が進まない」状態を解消するために作られています。

落とし穴はここからです。go fixを実行すると、対象の関数呼び出しが自動的に書き換わります。CI(継続的インテグレーション)パイプラインの中で誰かがgo fixを実行し、その差分をレビューなしでマージすると、意図しない大量の変更がデプロイパイプラインに流れ込む可能性があります。さらに厄介なのは、ディレクティブの書き方次第でインライン化(関数本体を呼び出し元に展開すること)の結果が変わる点です。

なぜ起きるか

原因を段階的に分解します。

1つ目は、インライナー(関数本体を呼び出し元に展開する解析器)が「関数の中身をそのまま呼び出し元に貼り付ける」という単純な動作をする点です。もし非推奨にした旧関数が、新APIへの薄い委譲ではなく、旧関数独自のロジックを抱えたままだと、インライン化後も旧ロジックのコピーが各所に散らばります。これは「移行」ではなく「複製」です。

2つ目は、go fixがgo vetと同じ解析フレームワーク上に構築されているため、プロジェクトのgo.modに書かれたGoバージョンの指定に挙動が依存する点です。go.modのgoディレクティブが1.26未満だと、新しいgo fixの恩恵をフルに受けられない場合があります。

3つ目は、ディレクティブ自体が「書き手の意図通りに書かれているか」をツールが検証しきれない点です。//go:fix inlineは構文的に正しければ適用されますが、ラッパー関数が本当に新APIへの単純な委譲になっているかどうかの判断は、書き手の責任です。IaCの文脈で言えば、Terraformのmoduleを非推奨にして新moduleへの移行を促す際、中身の互換性検証を人間が怠るのと似た構造の問題です。

自分のプロジェクトが該当するか確認する

まず手元のGoのバージョンを確認します。

go version

出力がgo1.26未満であれば、今回の挙動変更はまだ影響しません。次にgo.modのgoディレクティブを確認します。

cat go.mod | grep '^go '

ここが1.26以上になっていないと、新しいgo fixの解析が有効になりません。さらに、社内ライブラリやSDKの中にDeprecated:コメントが付いた関数がどれだけあるか、既存のコード資産を棚卸しします。

grep -rn "Deprecated:" --include="*.go" .

該当箇所が多いプロジェクトほど、go fix導入時の影響範囲が大きくなります。CI設定ファイル(.github/workflowsや.gitlab-ci.ymlなど)にgo vetやgo fixを呼ぶジョブがすでにあるかどうかも確認しておくと、どこに新しい挙動が入り込むかが見えてきます。

対策の手順

1. go toolでアナライザーの一覧を確認する

go fixがどんな書き換えを行えるのか、まず一覧を出します。

go tool fix help
go tool fix help inline

これでanyインターフェースへの置き換えやmin/max関数へのif-else簡略化など、標準で提供されるmodernizer(コードを新しい書き方に近代化するアナライザー)の種類が分かります。

2. 非推奨関数を薄いラッパーに書き直す

//go:fix inlineを付ける前に、旧関数の中身を新APIへの単純な委譲に書き換えます。旧関数が独自ロジックを持ったままインライン化すると、呼び出し元にロジックのコピーが撒き散らされるため、必ずラッパー化してからディレクティブを付けます。

3. クリーンなgitツリーでプレビューする

公式ブログでも推奨されている手順ですが、go fixを実行する前に作業ツリーをコミット済みのクリーンな状態にしておきます。こうすることで、生成される差分が「go fixによる変更のみ」であることをgit diffで確認できます。

git status
git diff --stat

4. CI上でのレビューフローを設計する

SREの観点では、go fixの実行結果を自動マージしないことが肝心です。CIジョブとしては以下のような分離がおすすめです。

  • go fixの提案内容をPR(プルリクエスト)として自動生成するジョブ
  • 人間がレビューしてマージするジョブ
  • マージ後に既存のテストスイートとSLO(Service Level Objectiveの略、可用性やレイテンシの目標値)に影響するベンチマークを流すジョブ

この3段階を分けておくと、大量の自動書き換えが本番環境の挙動に直結するリスクを下げられます。

5. 段階的ロールアウトで検証する

インフラ系コードベースでは、go fix適用後のビルド成果物を、カナリアリリース(一部のトラフィックだけに新バージョンを流す手法)やステージング環境で先に検証します。特にリクエストのフォーマット処理やログ出力周りの非推奨関数を書き換えた場合、文字列の整形結果がわずかに変わることがあるため、オブザーバビリティ基盤(メトリクス・ログ・トレースで状態を可視化する仕組み)のダッシュボードで出力差分を確認しておくと安心です。

//go:fix inlineは非推奨関数を薄いラッパーにしてから付けること。ロジックを抱えたまま付けると呼び出し元にコードが複製されます。

確認しておきたいこと

Go 1.26のgo fix刷新は、非推奨APIの移行を自動化する強力な仕組みです。ただし自動化だからこそ、適用前の設計を丁寧に行う必要があります。

  • go versionとgo.modのgoディレクティブで1.26以上かを確認する
  • grep -rn "Deprecated:"で社内コードの非推奨関数を棚卸しする
  • //go:fix inlineを付ける前に関数を新APIへの薄い委譲に書き直す
  • go fixの実行結果は自動マージせず、PRレビューとテスト・SLO検証を挟む

この4点を押さえておけば、go fix導入によって想定外のコード変更が本番環境に紛れ込むリスクを抑えながら、長年放置されがちな非推奨API移行を前に進められます。

参考

Deprecate a Go Function and Let go fix Migrate Every Caller With //go:fix inline

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。