mcpField
G
go-refactoring

go-refactoring

github.com/eduardo-sl開発83100 回インストール2 週間前に更新
リポジトリ

go-refactoring について

go-refactoring は、Go 開発者やコーディングエージェント向けのリファクタリング手順です。動作を変えない小さな変更を検証しながら進め、リスク確認と切り戻しの指針も示します。

主な機能

  1. 01リスク別の手順ガイド
  2. 02ビルドとテストの確認点
  3. 03安全な名前変更と抽出
  4. 04パッケージと API の移行パターン
  5. 05機械的な書き換えツールの指針

活用シーン

  1. 01パッケージ分割の計画
  2. 02Go シンボルの安全な名前変更
  3. 03公開 API の変更
  4. 04サブシステムの段階的な置き換え

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-refactoring

インストール後、Claude Code で/go-refactoringを入力して読み込まれたか確認します。

同梱ファイルreferences/bulk-rewrites.md

詳細

Go のリファクタリングワークフロー

リファクタリングで変更するのは構造であり、動作ではありません。完了の定義は機械的です。各ステップの前後で同じテストが成功し、各ステップを単独で元に戻せるほど小さく保ちます。

必要に応じて読み込む詳細な参考資料:

  • references/bulk-rewrites.md — gofmt -r、//go:fix inline、eg、gopatch、カスタムアナライザーと、その後に実行する検証について説明しています。

以下の概要だけでは不十分な場合に限り、参考ファイルを読んでください。

0. リスク、チェックポイント、実施を避けるべき場合

リスク変換開始前に必要なこと
低gopls による名前変更、変数の抽出/インライン化、インポートの整理ビルドとテストが成功すること
中関数またはメソッドの抽出、パッケージをまたぐ呼び出しのインライン化、パラメーターの追加/削除変更するすべての呼び出し箇所をカバーするテスト
高パッケージをまたぐ型の移動、パッケージの分割または統合、循環依存の解消、エクスポートされた API の変更十分な安全策と、開発者の承認

パッケージをまたぐ移動、エクスポートされた API の変更、削除、またはテストのないコードの変更を行う前に、開発者の承認を得てください。コードが安定していて、予定している変更がその箇所に及ばない場合、重要なのにテストがない場合、または具体的な次の変更が容易にならない場合は、そもそもリファクタリングが必要かどうかを問い直してください。

1. ループ

  1. ベースライン: 何かに手を加える前に、go build ./... && go test ./... が成功している必要があります。対象コードのテストが不足している場合は、まず特性化テストを書いてください。動作が間違っているように見えても、現在の動作を固定します。
  2. 以下の一覧から変換を1つ実行します。
  3. 検証: ビルド、テスト、go vet ./... を実行します。
  4. コミット。 リファクタリングのコミットに動作変更を絶対に混ぜないでください。レビュー担当者は refactor: コミットならざっと確認できますが、両方が混在したコミットは慎重に精査する必要があります。
  5. 繰り返します。

ステップ3が失敗し、1分以内に修正方法が分からない場合は、途中まで適用された変換のデバッグを続けず、ステップを元に戻してください。

2. 名前変更 — ツールに任せる

# Preferred: gopls (understands types, interfaces, embedding)
gopls rename -w internal/service/user.go:#offset newName

# Module path or package import path changes:
# update go.mod, then rewrite imports mechanically
find . -name '*.go' -exec sed -i 's|github.com/acme/old|github.com/acme/new|g' {} +
go build ./...   # the compiler is the reviewer

名前を変更したら、構造体タグ、テンプレート、SQL、リフレクションで読み取られるあらゆる箇所で古い名前を grep してください。gopls が確認するのはコンパイルであり、これらの箇所ではありません。フィールドの名前を変更すると、json/db タグとの対応が知らないうちにずれることがあります。

公開済みライブラリーのエクスポートされた識別子は、非推奨化期間を設けずに名前を変更してはいけません。新しい名前を追加し、古い名前に // Deprecated: use NewName. を付けて、次のメジャーバージョンで削除してください。

3. パッケージの抽出

コンパイラーで検証できるステップで、巨大なパッケージからコードを移動します。

  1. 新しいパッケージを作成し、型を1つとそのメソッドを移動します(gopls または切り取り/貼り付けを使用)。それ以外のものはそのままにします。
  2. 古いパッケージに型エイリアスを追加し、何も壊れないようにします。 type User = user.User(定義ではなく、= を使ったエイリアス)。
  3. ビルドします。インポート側を新しいパスへ段階的に移行し、各段階でビルドします。
  4. インポート側が残っていなければ、エイリアスを削除します。

これにより、呼び出し元がどれだけ多くても、すべてのコミットでビルドが成功する状態を保てます。

4. 循環依存の解消

パッケージ a → b と b → a はコンパイルできません。循環依存に近い状態は、巨大なパッケージとして現れることがあります。解決策は3つあります。優先度の高い順に示します。

  1. 共通のコアを抽出する: 実際には a と b の両方がある型に依存しているなら、その型を何もインポートしない第3のパッケージ c に移動します。
  2. インターフェースで依存関係を逆転する: store が service をコールバックする場合、コールバック用インターフェースを store(利用側)で定義し、service に実装させます。コンパイル時に依存の矢印が反転します。
  3. 統合する: 互いを抜きにして説明できない2つのパッケージは、最初から1つのパッケージだったということです。

5. 関数のシグネチャを安全に変更する

呼び出し元が多いエクスポート関数の場合:

// Step 1 — add the new form alongside the old
func (s *Service) ProcessCtx(ctx context.Context, id string) error { ... }

// Step 2 — old form delegates; mark deprecated
// Deprecated: use ProcessCtx.
func (s *Service) Process(id string) error {
    return s.ProcessCtx(context.Background(), id)
}

// Step 3 — migrate callers batch by batch, building each batch
// Step 4 — delete the old form (same module) or keep until next major (library)

Go 1.26 以降では、非推奨のラッパーの上に //go:fix inline を追加します。そうすると、ステップ3で go fix ./... を実行できます。今すぐ自分のモジュールで実行できるほか、下流の各モジュールでも次に go fix を実行するときに適用されます。

単一モジュール内では、コンパイラーがすべての呼び出し元を特定できる場合、シグネチャを一度に変更する方法を優先してください。シグネチャを変更してから、ビルドエラーを追って修正します。コンパイラーが TODO リストを列挙してくれます。

6. サブシステムのストラングラー移行

1 つの PR では置き換えが大きすぎるサブシステム(古いストア、レガシークライアントなど)を置き換える場合:

  1. 呼び出し側が実際に必要とする、コンシューマー側のインターフェースを定義します。
  2. OLD の実装にそのインターフェースを満たさせ、呼び出し側をインターフェース経由にします。
  3. 同じインターフェースの背後に新しい実装を構築し、両方の実装を 1 つの共有適合性テストスイートでテストします。
  4. コンポジションルートで接続を切り替えます(1 行、1 コミットで、簡単に元に戻せます)。リスクに応じてフィーチャーフラグを使います。
  5. 古い実装は独立したコミットで削除します。

7. 一括で行う機械的な書き換え

同じ変更を多数の箇所に適用する場合は、変更を一度だけ記述し、ツールに適用させます。構文式には gofmt -r、独自 API の呼び出し側には //go:fix inline と go fix、型を考慮した式の書き換えには eg、文やインポートには gopatch、繰り返し実行する移行には go/analysis の修正ツールを使います。ツールによる書き換えはレビューと再実行が可能ですが、40 箇所を手作業で編集する方法ではどちらもできません。例:references/bulk-rewrites.md。

8. リファクタリングではないもの

  • 「ついでに」行うバグ修正 — 前後いずれかの独立したコミットにします。
  • 位置指定リテラルで使われている構造体フィールドの並べ替え、呼び出し側が照合する公開エラー文字列の変更、JSON タグの変更 — これらはリファクタリングを装った動作変更です。
  • テストなしの書き換え。先に動作を特定できないのであれば、リファクタリングではなく、賭けです。

検証チェックリスト

  1. 最初の変更を行う前に、ベースラインのビルドとテストがすべて成功している
  2. カバレッジが不足していた箇所に特性テストを追加している
  3. 各コミットは 1 つの変換のみである。refactor: コミットには動作変更を含めない
  4. 識別子の名前変更には gopls/コンパイラを使い、検索と置換は使わない
  5. パッケージの切り出しでは型エイリアスを使い、作業途中もビルドを成功させる
  6. 新たな依存関係の循環がない(go build ./... で確認する)
  7. 公開 API の変更は非推奨化のサイクルに従う
  8. go vet ./... と全テストスイートが、最後だけでなく各コミットで成功する
  9. git log が安全で元に戻せる手順の連続として読める
  10. リスクの高い手順は、着手前に開発者の承認を得る
  11. 名前変更のたびに、構造体タグ、テンプレート、SQL を grep で確認する

コメント