「eg tool」で探しているのが、Goの公式ツール群 golang.org/x/tools に入っている eg コマンドなら、この記事が対象です。egは、変換前と変換後のコード例(e.g.)を2つの関数で書くと、型を見たうえで一致する式をまとめて書き換えるコマンドラインツールです。ローラー工具の「EGシリーズ」や工具管理ソフトの「EG TraceTool」など、同じ綴りの別製品とは関係ありません。
本記事のコマンドと出力は、Go 1.27.2・x/tools v0.52.0(2026-10-10公開)・gopls v0.24.0 で確かめたものです。2026年のGoでは go fix が作り直され、API移行の多くは //go:fix inline で書けるようになりました。egを使う場面とそうでない場面も、その前提で整理します。
まとめ:egは「型を見る一括置換」、API移行はgo fix inlineが先
- 入手:
go install golang.org/x/tools/cmd/eg@latest。配布バイナリは無く、ソースからビルドして入れる。x/tools v0.52.0 は go.mod でgo 1.26.0を要求する。 - 書き方:同じ型の
beforeとafterを書いたテンプレートを-tで渡す。本記事では両関数とも式1つの基本形を扱う。 - 手順:
-wなしで出力を確認し、-wで書き戻し、goimportsで不要なimportを消す。 - 強み:識別子を型で照合するので、同じ名前の別物を書き換えない。
gofmt -rでは誤置換してビルドが壊れる例でも、egは正しい箇所だけを変える。 - 使い分け:自分のパッケージの非推奨関数を呼び出し側から消すなら、Go 1.26以降は
//go:fix inlineとgo fixを先に検討する。識別子の改名はgopls rename(gorenameは2024年に削除済み)。egが残るのは、宣言にディレクティブが無い式のパターン置換。
eg toolの正体:golang.org/x/tools/cmd/eg
egはGoチームが管理する golang.org/x/tools モジュールのコマンドで、ソースの冒頭コメントは「The eg command performs example-based refactoring.」です。名前はラテン語の e.g.(exempli gratia=例えば)から来ており、正規表現ではなく「例」で変換を指定することを表しています。仕様の本体は refactor/eg パッケージのHelp にあり、eg -help でも同じ文が出ます。
Help には、着想元として gofmt -r とJavaのRefasterが挙がっています。2014年からあるツールですが放置はされておらず、2025-09-29のコミットでビルドタグ指定の -tags が追加され(x/tools v0.38.0 に収録)、照合を担う refactor/eg にも2026-10-12に修正が入っています(v0.52.0の公開後なので、次の版から含まれます)。
egのインストール手順と必要なGoバージョン
egは単体の配布ファイルが無く、Goのツールチェーンでビルドして入れます。Goが未導入ならGo言語とは|1.27系の導入からHTTP APIを動かすまでの実装入門で先に環境を作ってください。
go install golang.org/x/tools/cmd/eg@latest
# チームで版をそろえるなら固定する
go install golang.org/x/tools/cmd/[email protected]
バイナリは GOBIN の指定先に入り、未設定なら GOPATH の先頭ディレクトリ内の bin に入ります。macOSでGOPATHも未設定の場合、その既定値は $HOME/go/bin です。Go 1.27.2(macOS x86_64)では8秒ほどで入り、引数なしで eg を実行すると使い方の一覧が出ました。どの版を入れたかは go version -m $(which eg) の mod golang.org/x/tools v0.52.0 の行で確かめられます。
注意点はGoの版です。x/tools v0.52.0 の go.mod は go 1.26.0 なので、それより古いGoで @latest を入れると、既定の GOTOOLCHAIN=auto ではGo 1.26以上のツールチェーンが自動で取得されます。自動取得を禁じている環境では失敗するので、GOTOOLCHAINとは|Go Toolchainでバージョンを自動切り替えする仕組みと設定方法で設定を確認してください。
テンプレートを書く:before/after関数の決まり
変換は、同じ型の before と after を定義したGoファイルで指定します。Helpでは両関数を1文で書く基本形が説明されています。ただし、v0.52.0の実装ではbeforeは1文に制限される一方、afterには末尾のreturn文または式文に先行する文も記述できます(使いどころは制約の章で扱います)。ここでは両方を1文にした基本形を示します。beforeの式がパターン、afterの式が置換後になります。テンプレートのパッケージ名は任意です。以下の2つの例は、それぞれerrnew.goとfatal.goとして対象モジュールの外に保存してください。実行例の -t には、保存先への相対パスまたは絶対パスを指定します。
package P
import (
"errors"
"fmt"
)
func before(s string) error { return fmt.Errorf("%s", s) }
func after(s string) error { return errors.New(s) }
戻り値のない呼び出し用の式文テンプレート
log.Fatalf のように結果を使わない呼び出しは、return を付けずに式文で書きます。
package P
import "log"
func before(msg string) { log.Fatalf("%s", msg) }
func after(msg string) { log.Fatal(msg) }
beforeとafterの型が違うと実行前に止まります。func(i int) と func(i int64) にしたテンプレートでは「eg: before func(i int) string and after func(i int64) string functions have different signatures」と出て、何も書き換えませんでした。
テンプレート引数のワイルドカード照合
関数の引数は、その型に代入できる任意の式に一致します。上の例の s は "forbidden: "+msg のような式全体も受け止めます。同じ引数がパターンに2回出てくる場合は、入力でも同じ式でなければ一致しません。
比較演算子の違いによるテンプレートの分割
egは式の形を照合するので、意味が同じでも書き方の違う式は拾いません。strings.Index(s, sub) != -1 を strings.Contains(s, sub) にするテンプレートを当てたところ、!= -1 の2箇所は置換され、同じ判定の strings.Index(s, "x") >= 0 は残りました。>= 0 や > -1 も直すなら、それぞれのテンプレートを用意して順に実行します。
egを実行する:確認、-wで書き戻し、goimports
まず -w を付けずに実行します。書き換え後のファイル全体が標準出力に出て、ファイルごとに一致件数の行が付きます。
$ eg -t errnew.go ./...
=== /tmp/eg3400/lab/mod/app/app.go (2 matches)
package app
...
差分が意図どおりなら -w を付けて書き戻します。不要になったimportが残ると、ビルドが失敗します。上のテンプレートで fmt.Errorf がすべて消えたファイルは、次のエラーで止まりました。
$ eg -t errnew.go -w ./...
$ go build ./...
app/app.go:5:2: "fmt" imported and not used
$ goimports -w app/app.go
$ go build ./... # 成功
egは必要なimportを足しますが、要らなくなったimportは消しません。Helpにも「Run ‘goimports’ on the modified file for now.」とあり、goimports(go install golang.org/x/tools/cmd/goimports@latest)までを1セットにします。
| フラグ | 働き |
|---|---|
-t file.go |
テンプレートの指定(必須) |
-w |
元ファイルへ書き戻す(既定は標準出力) |
-transitive |
依存先も対象にする指定(v0.52.0では動作に注意) |
-tags a,b |
ビルドタグを指定(v0.38.0以降) |
-beforeedit cmd |
編集前に実行するコマンド。{}がファイル名 |
-v |
照合の詳細を表示 |
-help |
テンプレートの仕様と制約を表示 |
-transitive は注意が要ります。v0.52.0の cmd/eg/eg.go は、このフラグで依存先まで含めた一覧(all)を作りますが、書き換えのループは引数で指定したパッケージ(pkgs)しか回しません。依存先も直したいときは、そのパッケージを引数に並べて指定してください。なお、テストファイル(_test.go)も解析対象に含まれます。
-tags を付けない実行では、今のOSとアーキテクチャでビルド対象になるファイルだけが解析されます。OSやCPU別のファイルを対象にする場合は、GOOS・GOARCH を切り替えて実行してください。独自のビルドタグを追加する場合は -tags を使います。
gofmt -rとの違い:型と識別子の照合
egを使う一番の理由は、名前が同じだけの別物を書き換えないことです。次のコードには、標準パッケージの log.Fatalf と、ローカル変数 log(独自型 myLogger)の Fatalf が混ざっています。
// real.go
func Real(m string) {
log.Fatalf("%s", m) // 標準パッケージ log
}
// app.go
func Shadow(m string) {
log := myLogger{} // Fatalf だけを持つ独自型
log.Fatalf("%s", m)
}
同じ変換を2つの方法で当てた結果です。
| 方法 | real.go | app.go(独自型) | go build |
|---|---|---|---|
| eg(上のfatal.go) | 置換 | そのまま | 成功 |
| gofmt -r | 置換 | 誤って置換 | 失敗 |
gofmt -r 'log.Fatalf("%s", a) -> log.Fatal(a)' は構文だけを見るため、独自型の呼び出しまで書き換え、「log.Fatal undefined (type myLogger has no field or method Fatal)」でビルドが通らなくなりました。Helpの説明どおり、egは識別子を「同じオブジェクトを指すときだけ一致」として扱います。別名importやドットimportがあっても照合は正しく働きます。
ビルドが失敗するなら気づけますが、独自型に同名の別メソッドがあれば、ビルドが通ったまま意味の違うコードになります。対象の名前が別のパッケージや型でも使われているコードベースでは、gofmt -r ではなくegを選んでください。ソースを実行せずに型と構造を読む仕組みは静的解析とは?検出できる欠陥と静的解析ツールの選び方|Coverity・cppcheckを比較で扱う静的解析と同じ土台です。
egの制約:式単位と副作用
Helpの「LIMITATIONS」節が挙げる制約のうち、作業前に知っておくべきものを並べます。
- 式を別の式に置き換えることしかできない:if文や代入、複数の文にまたがる書き換えは表せない。関数リテラルを含むパターンは一致しない。
- 型をまとめて書けない:「任意の整数型」のような指定はできず、置換の前後で型が変わる変換も不可。
- 構造体リテラルの2つの書き方:
T{x}とT{K: x}を1つのテンプレートで両方拾うことはできない。 - 型の検査は浅い:Helpは、定数1を2に変えるだけでも配列の型や添字で型エラーが起こりうるとし、検査は「very superficial」だと書いている。
- 置換後の識別子:照合はスコープを理解するが、置換は理解しない。入力側に別名import・ドットimport・パッケージ名を隠すローカル変数があると、置換後の識別子が正しくならないことがある。テンプレート内のドットimportは禁止。
afterでの引数複製と副作用の重複
Helpは、置換後に同じ引数が複数回出ると式が複製され、副作用が変わりうると注意しています。x * 2 を x + x にするテンプレートを当てると、next() * 2 が next() + next() に書き換わり、カウンタを進める next() が2回呼ばれるコードになりました。afterで引数を2回使うテンプレートは、引数に関数呼び出しが来ない箇所だけに当てるか、置換後の差分を1箇所ずつ読んでください。
複製を避ける書き方もあります。Helpには書かれていませんが、2018年のコミット以降、afterだけは最後のreturn文の前に文を置けます。引数をいったん変数に受けるテンプレートにすると、呼び出しは1回のままでした。
func before(x int) int { return x * 2 }
func after(x int) int {
y := x
return y + y
}
// 適用後(v0.52.0の出力そのまま)
func Double() int {
y :=
next()
return y + y
}
代入文は一致した式を含む文の直前に入ります。出力の改行は崩れたままで、gofmt も直さないため、手で整えてください。
関数の引数削除に伴う段階的な移行手順
関数の引数そのものを変えるとき、Helpは次の手順を勧めています。最後の引数を消すなら、いったんその引数を可変長にして、新旧どちらの呼び出し方もコンパイルできる状態にしてから、egで呼び出し側の引数を消し、最後に可変長の引数を手で削ります。最後以外の引数を変えるなら、新しい名前の関数を作り、egで呼び出しを移し、元の関数の宣言を直し、egで名前を戻し、新しい関数を消す、の5段階です。Go 1.26以降なら、この作業の多くは次の章の //go:fix inline で1回にまとまります。
Go 1.26以降の使い分け:go fix inline・gopls rename・eg
2026-02-10公開のGo 1.26で go fix が作り直されました。Go 1.26リリースノートは、新しい go fix を「the home of Go’s modernizers」とし、ユーザーが //go:fix inline で自分のAPI移行を自動化できるソースレベルのインライナーを含むと書いています。従来の修正は、すべて役目を終えたとして削除されました。
go:fix inlineによる非推奨関数の置き換え
関数の本体を新しいAPIの呼び出しにして //go:fix inline を付けると、go fix が呼び出し側をその本体へ展開します。
// Deprecated: strings.Contains を使う。
//
//go:fix inline
func HasSub(s, sub string) bool { return strings.Contains(s, sub) }
//go:fix inline
func Join2(a, b string) string { return JoinSep(a, b, "-") }
go fix -diff ./... を実行すると、別パッケージの old.HasSub("golang", "go") が strings.Contains("golang", "go") に、old.Join2(next(), "y") が old.JoinSep(next(), "y", "-") に変わる差分が出て、strings のimportも足されました。差分がある間は終了コード1を返すので、CIで「直し忘れの呼び出しが残っていないか」の検査にも使えます。go fix -inline ./... で、このアナライザーだけを適用できます。
egとの違いは副作用の扱いです。x + x を返す Twice に //go:fix inline を付けると、Twice(3) は 3 + 3 に展開され、Twice(next()) は書き換えずに残りました。egが next() を2つに複製した場面で、インライナーは動作が変わる展開を見送ります。Go公式ブログのインライナー解説(2026-03-10)も、引数の評価順と副作用を守ることを設計の中心に置き、任意の書き換えができる gofmt -r は「need to be watched closely」と対比しています。
go fixの解説記事(2026-02-17)によれば、生成されたファイルに触れる修正は捨てられます。また go fix も1回の実行で解析するのは1つのビルド構成だけなので、OSやCPU別のファイルが多いプロジェクトは GOOS・GOARCH を変えて複数回実行します。Go 1.27で増えた修正の内容はGo 1.27の新機能・変更点まとめ|Goの最新バージョンは1.27、確認・更新方法と移行の注意点を参照してください。
識別子の改名はgopls rename
以前は gorename が改名の定番でしたが、2024-09-20のコミットで削除され、x/tools v0.26.0(2024-10-04)以降には入っていません。x/tools v0.52.0 に残る実装には「This logic has not worked properly since the advent of Go modules」「Use gopls instead」と書かれています。gopls v0.24.0 では、gopls rename -d old/old.go:15:6 JoinWith で宣言と別パッケージの呼び出しがまとめて改名されました(-w で書き戻し)。コメント中の同じ語は変わらないので、ドキュメントコメントは手で直します。
egによるコーディング規約と外部API呼び出しの統一
検証に使った元コード(fmt.Errorf("%s", s) と strings.Index(s, sub) != -1)に go fix -diff ./... を当てても、提案は1件も出ませんでした。標準の修正は決まったイディオムしか扱いません。//go:fix inline は関数だけでなく定数や型にも付けられ、外部パッケージの作者が付けたディレクティブも効きますが、どれも宣言側にディレクティブがあることが前提です。チームのコーディング規約に合わせた式の書き換えや、作者がディレクティブを付けていない外部APIの呼び出し方の統一のように、宣言に紐づかない式のパターンがegの守備範囲です。
| やりたいこと | 第一候補 | 型を見るか |
|---|---|---|
| 自作の非推奨関数を呼び出し側から消す | go:fix inline と go fix | 見る |
| 新しい言語機能・標準APIへの書き換え | go fix(標準の修正) | 見る |
| 識別子の改名 | gopls rename | 見る |
| 任意の式パターンの一括置換 | eg | 見る |
| 1パッケージ内の構文だけの置換 | gofmt -r | 見ない |
どの手段でも、書き換えた後はビルドとテストで確かめてから取り込みます。変更をCIの品質ゲートに組み込む手順はコード品質とは?測る指標と品質ゲートでCIに組み込む手順、書き換えをいつ計画に入れるかはリファクタリングとは?24の観点とタイミング・進め方で扱っています。長く運用しているGoのシステムで、こうした移行を継続して回す体制が社内に無い場合は、保守運用・内製化支援で相談を受け付けています。
よくある質問
「eg tool」はどこからダウンロードしますか?
配布用のインストーラーやzipはありません。Goを入れたうえで go install golang.org/x/tools/cmd/eg@latest を実行すると、ソースが取得されてビルドされます。x/tools v0.52.0 はGo 1.26以上を要求します。
egの名前の由来は?「e.g.」の意味は?
e.g. はラテン語 exempli gratia の略で「例えば」の意味です。egは変換を「例」で指定するツールなので、この略語が名前になっています。
「eg.go」とは何ですか?
egのソースファイル名です。コマンドの入口が cmd/eg/eg.go、テンプレートの仕様(Help)と照合の本体が refactor/eg/eg.go にあります。
「eg software」「eg tools」は別のソフトですか?
Goの文脈ならこのegコマンドを指します。ただし英語圏では、ecorollのローラー工具「EGシリーズ」や工具管理ソフト「EG TraceTool」など無関係の製品も同じ綴りで出てくるので、検索するときは「go eg」「golang eg」と書くと絞り込めます。
egは今も使えますか?
使えます。2026-10時点の x/tools v0.52.0 にも収録され、Go 1.27.2 でビルドと実行ができました。ただし自作APIの移行は //go:fix inline、改名は gopls rename の方が安全なので、egはそれらで書けないパターン置換に使います。