
こんにちは。よっしーです(^^)
本日は、Go言語の依存関係の管理ついて解説しています。
背景
Goでアプリケーションを開発していると、必ずと言っていいほど外部パッケージに依存することになります。HTTPルーターやデータベースドライバ、ロギングライブラリなど、車輪の再発明を避けて開発を効率化するために、私たちは日常的にこれらの外部モジュールを利用しています。
しかし、依存関係の管理は「最初に導入したら終わり」というわけにはいきません。セキュリティパッチのリリース、新機能の追加、破壊的変更への対応など、時間の経過とともに依存パッケージのアップグレードや置き換えが必要になってきます。また、複数の開発者が関わるプロジェクトでは、全員が同じバージョンの依存関係を使用できるよう、一貫性を保つことも重要です。
本記事では、Goが提供する依存関係管理ツールを使って、外部依存関係を取り込みながらもアプリケーションの安全性を保つ方法について解説します。公式ドキュメントの内容を日本語で紹介しながら、実際の開発現場で役立つ依存関係管理のベストプラクティスをお伝えしていきます。
依存関係の削除
コードがモジュール内のパッケージをもはや使用しなくなった場合、そのモジュールを依存関係として追跡するのを停止できます。
すべての未使用モジュールの追跡を停止するには、go mod tidyコマンドを実行します。このコマンドは、モジュール内のパッケージをビルドするために必要な不足している依存関係も追加する場合があります。
$ go mod tidy
特定の依存関係を削除するには、go getコマンドを使用し、モジュールのモジュールパスを指定して@noneを追加します。以下の例を参照してください:
$ go get example.com/theirmodule@none
go getコマンドは、削除されたモジュールに依存する他の依存関係もダウングレードまたは削除します。
解説
依存関係削除の概念
依存関係の削除は、使わなくなった道具を片付けるようなものです:
| 道具の整理 | 依存関係の削除 |
|---|---|
| 使わない工具を確認 | 未使用パッケージを特定 |
| 工具箱から取り出す | go.modから削除 |
| 関連部品も整理 | 間接的依存関係も削除 |
| 必要なものは残す | 必要な依存関係は保持 |
| 定期的に整理 | go mod tidyで定期整理 |
go mod tidyによる自動削除
基本的な使い方
最も推奨される方法:
# すべての未使用依存関係を削除
go mod tidy
# 詳細情報を表示
go mod tidy -v
実践例: リファクタリング後の整理
シナリオ: 古いコードを削除して、パッケージが不要になった
# ===== リファクタリング前 =====
# go.mod
module example.com/myapp
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/old/logger v1.0.0 # 古いロガー
github.com/deprecated/helper v2.1.0 # 非推奨ヘルパー
github.com/new/logger v2.0.0 # 新しいロガー
)
# main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/old/logger" // 古いロガーを使用
"github.com/deprecated/helper" // 非推奨ヘルパーを使用
)
func main() {
logger.Info("Starting app")
helper.DoSomething()
r := gin.Default()
r.Run()
}
# ===== リファクタリング実行 =====
# 新しいロガーに移行
cat > main.go << 'EOF'
package main
import (
"github.com/gin-gonic/gin"
"github.com/new/logger" // 新しいロガーに変更
)
func main() {
logger.Info("Starting app")
r := gin.Default()
r.Run()
}
EOF
# ===== go mod tidyで自動整理 =====
$ go mod tidy -v
# 出力:
# unused github.com/old/logger
# unused github.com/deprecated/helper
# ===== 整理後のgo.mod =====
$ cat go.mod
# module example.com/myapp
#
# go 1.21
#
# require (
# github.com/gin-gonic/gin v1.9.1
# github.com/new/logger v2.0.0
# )
#
# require (
# github.com/bytedance/sonic v1.9.1 // indirect
# ... その他の間接的依存関係
# )
# github.com/old/logger と github.com/deprecated/helper は削除された
# ===== 確認 =====
$ go build
# 成功
$ go test ./...
# PASS
go mod tidyの動作詳細
何が起こるか:
ステップ1: ソースコード解析
├─ すべての.goファイルを読み込む
├─ import文を抽出
└─ 実際に使用されているパッケージを特定
ステップ2: 依存関係の比較
├─ go.modに記載されている依存関係
├─ 実際に使用されている依存関係
└─ 差分を計算
ステップ3: 削除と追加
├─ 未使用の依存関係を削除
├─ 不足している依存関係を追加
└─ go.modとgo.sumを更新
ステップ4: 間接的依存関係の整理
├─ 孤立した間接的依存関係を削除
├─ 必要な間接的依存関係を保持
└─ // indirectマーカーを適切に設定
@noneを使った明示的な削除
基本的な使い方
# 特定のモジュールを削除
go get example.com/theirmodule@none
# 複数のモジュールを一度に削除
go get \
example.com/module1@none \
example.com/module2@none \
example.com/module3@none
実践例: 特定パッケージの削除
シナリオ: 特定のパッケージだけを削除したい
# ===== 削除前の状態 =====
$ go list -m all
# example.com/myapp
# github.com/gin-gonic/gin v1.9.1
# github.com/lib/pq v1.10.9 # PostgreSQLドライバ
# github.com/go-sql-driver/mysql v1.7.1 # MySQLドライバ
# github.com/mattn/go-sqlite3 v1.14.18 # SQLiteドライバ
# 状況: PostgreSQLに統一したので、MySQLとSQLiteは不要
# ===== 特定のパッケージを削除 =====
# MySQLドライバを削除
$ go get github.com/go-sql-driver/mysql@none
# go: removed github.com/go-sql-driver/mysql v1.7.1
# SQLiteドライバを削除
$ go get github.com/mattn/go-sqlite3@none
# go: removed github.com/mattn/go-sqlite3 v1.14.18
# ===== 削除後の状態 =====
$ go list -m all
# example.com/myapp
# github.com/gin-gonic/gin v1.9.1
# github.com/lib/pq v1.10.9
# ===== go.modの確認 =====
$ cat go.mod
# module example.com/myapp
#
# go 1.21
#
# require (
# github.com/gin-gonic/gin v1.9.1
# github.com/lib/pq v1.10.9
# )
# ===== 整理 =====
$ go mod tidy
# ===== 確認 =====
$ go build
# 成功
$ go test ./...
# PASS
@noneの使用ケース
ケース1: 意図的な削除
# パッケージAからパッケージBに移行
# 古いパッケージを明示的に削除
go get github.com/old/package@none
# 新しいパッケージを追加
go get github.com/new/package@latest
# 整理
go mod tidy
ケース2: 開発用パッケージの削除
# 開発中だけ使用していたパッケージ
go get github.com/debug/tool@none
go get github.com/test/helper@none
# 整理
go mod tidy
ケース3: セキュリティ上の理由
# 脆弱性のあるパッケージを削除
go get github.com/vulnerable/package@none
# 代替パッケージを追加
go get github.com/secure/alternative@latest
依存関係削除の完全ワークフロー
ワークフロー1: 計画的なパッケージ削除
# ===== フェーズ1: 現状確認 =====
# 現在の依存関係を記録
$ go list -m all > dependencies-before.txt
# 依存関係の使用状況を確認
$ go mod why github.com/package/to-remove
# 出力:
# # github.com/package/to-remove
# example.com/myapp/handlers
# github.com/package/to-remove
# どこで使われているか特定
$ grep -r "package/to-remove" --include="*.go"
# handlers/api.go: "github.com/package/to-remove"
# ===== フェーズ2: コード修正 =====
# import文を削除
$ nano handlers/api.go
# import "github.com/package/to-remove" を削除
# 関連するコードも修正
# ===== フェーズ3: テスト =====
# ビルド確認
$ go build
# 成功
# テスト実行
$ go test ./...
# PASS
# ===== フェーズ4: 依存関係削除 =====
# go mod tidyで自動削除
$ go mod tidy -v
# unused github.com/package/to-remove
# または明示的に削除
$ go get github.com/package/to-remove@none
# ===== フェーズ5: 確認 =====
# 削除されたことを確認
$ go list -m all > dependencies-after.txt
$ diff dependencies-before.txt dependencies-after.txt
# 出力:
# < github.com/package/to-remove v1.0.0
# ←削除された
# ===== フェーズ6: コミット =====
$ git add go.mod go.sum handlers/api.go
$ git commit -m "Remove unused package/to-remove dependency
- Removed import from handlers/api.go
- Replaced functionality with native implementation
- All tests passing
Removed dependencies:
- github.com/package/to-remove v1.0.0"
ワークフロー2: 大規模リファクタリング
# ===== シナリオ =====
# 古いフレームワークから新しいフレームワークへの移行
# ===== フェーズ1: 移行計画 =====
cat > MIGRATION.md << 'EOF'
# Framework Migration Plan
## Old Framework
- github.com/old/framework v1.5.0
- github.com/old/middleware v1.2.0
- github.com/old/router v2.0.0
## New Framework
- github.com/new/framework v2.0.0
- github.com/new/middleware v1.0.0
- github.com/new/router v1.5.0
## Migration Steps
1. Create new handlers with new framework
2. Run both frameworks in parallel
3. Migrate routes one by one
4. Test each migration
5. Remove old framework
## Rollback Plan
Keep old framework code in separate branch
EOF
# ===== フェーズ2: 段階的移行 =====
# 週1: 新フレームワークを追加
go get github.com/new/framework@latest
go get github.com/new/middleware@latest
go get github.com/new/router@latest
# 週2-4: ルートを順次移行
# 新旧両方のフレームワークが共存
# 週5: 最後のルートを移行
# 古いフレームワークのコードを削除
# ===== フェーズ3: 古いフレームワーク削除 =====
# 古いimportをすべて削除
find . -name "*.go" -exec sed -i '/old\/framework/d' {} \;
find . -name "*.go" -exec sed -i '/old\/middleware/d' {} \;
find . -name "*.go" -exec sed -i '/old\/router/d' {} \;
# テスト
go test ./...
# 依存関係削除
go get github.com/old/framework@none
go get github.com/old/middleware@none
go get github.com/old/router@none
# 整理
go mod tidy
# ===== フェーズ4: 確認とコミット =====
# ビルド確認
go build
# すべてのテスト
go test ./...
# ベンチマーク比較
go test -bench=. ./... > new-benchmark.txt
# 旧フレームワークと比較
# コミット
git add .
git commit -m "Complete migration from old framework to new framework
Migration summary:
- Migrated 50 routes
- Removed old framework dependencies
- All tests passing
- Performance improved by 30%
Removed dependencies:
- github.com/old/framework v1.5.0
- github.com/old/middleware v1.2.0
- github.com/old/router v2.0.0
Added dependencies:
- github.com/new/framework v2.0.0
- github.com/new/middleware v1.0.0
- github.com/new/router v1.5.0"
間接的依存関係の削除
孤立した依存関係の削除
シナリオ: 直接的依存関係を削除したが、間接的依存関係が残っている
# ===== 削除前 =====
$ cat go.mod
# module example.com/myapp
#
# go 1.21
#
# require github.com/package/parent v1.0.0
#
# require (
# github.com/package/child1 v1.0.0 // indirect
# github.com/package/child2 v1.0.0 // indirect
# github.com/package/child3 v1.0.0 // indirect
# )
# ===== parentを削除 =====
$ go get github.com/package/parent@none
# ===== go mod tidyで孤立依存関係を削除 =====
$ go mod tidy -v
# unused github.com/package/child1
# unused github.com/package/child2
# unused github.com/package/child3
# ===== 削除後 =====
$ cat go.mod
# module example.com/myapp
#
# go 1.21
#
# # すべての関連依存関係が削除された
依存関係ツリーの確認
# 依存関係ツリーを確認
$ go mod graph | grep github.com/package/parent
# 出力:
# example.com/myapp github.com/package/parent@v1.0.0
# github.com/package/parent@v1.0.0 github.com/package/child1@v1.0.0
# github.com/package/parent@v1.0.0 github.com/package/child2@v1.0.0
# github.com/package/parent@v1.0.0 github.com/package/child3@v1.0.0
# parentを削除すると、child1, child2, child3も不要になる
トラブルシューティング
問題1: go mod tidyで削除されない
# 症状: パッケージを使っていないのに削除されない
# 原因1: ビルドタグで使用されている
# ファイル例: database_postgres.go
// +build postgres
import _ "github.com/lib/pq"
# 対処: ビルドタグを指定してtidy
go mod tidy -tags=postgres
# 原因2: テストコードで使用
# ファイル例: test_helper_test.go
import "github.com/test/helper"
# 対処: 本当に不要なら削除
rm test_helper_test.go
go mod tidy
# 原因3: コメントアウトされているだけ
# // import "github.com/old/package" ← コメント
# 対処: 完全に削除
# コメントも含めて削除
# 確認
grep -r "old/package" --include="*.go"
問題2: @noneで削除できない
# エラー
$ go get github.com/package/name@none
go: github.com/package/name@none: invalid version: unknown revision none
# 原因: typo
# 正しい構文:
$ go get github.com/package/name@none
# ↑
# 小文字のnone
問題3: 他のパッケージが依存している
# エラー
$ go get github.com/package/a@none
go: github.com/package/b requires github.com/package/a
# 原因: パッケージbがパッケージaに依存
# 確認
$ go mod why github.com/package/a
# # github.com/package/a
# example.com/myapp
# github.com/package/b
# github.com/package/a
# 対処方法1: パッケージbも削除
$ go get github.com/package/b@none
$ go get github.com/package/a@none
# 対処方法2: パッケージbを更新
# (パッケージaへの依存がない新バージョン)
$ go get github.com/package/b@latest
$ go get github.com/package/a@none
ベストプラクティス
定期的な依存関係の整理
月次メンテナンスの例:
#!/bin/bash
# monthly-cleanup.sh
echo "=== Monthly Dependency Cleanup ==="
echo ""
# 1. 現状を記録
echo "1. Recording current state..."
go list -m all > deps-before-cleanup.txt
# 2. 未使用依存関係を削除
echo "2. Cleaning up unused dependencies..."
go mod tidy -v > cleanup-report.txt
# 3. 削除された依存関係を表示
echo "3. Removed dependencies:"
cat cleanup-report.txt
# 4. 新しい状態を記録
go list -m all > deps-after-cleanup.txt
# 5. 差分を表示
echo "4. Changes:"
diff deps-before-cleanup.txt deps-after-cleanup.txt
# 6. テスト
echo "5. Running tests..."
go test ./...
# 7. ビルド
echo "6. Building..."
go build
echo ""
echo "=== Cleanup Complete ==="
チーム開発でのルール
# Dependency Management Guidelines
## Regular Cleanup
- **Weekly**: Review new dependencies
- **Monthly**: Run `go mod tidy`
- **Quarterly**: Audit all dependencies
## Removal Process
1. Identify unused package
2. Remove import statements
3. Run tests
4. Execute `go mod tidy`
5. Commit with explanation
## Commit Message Format
deps: Remove unused package XYZ
- Removed import from module A
- Replaced with native implementation
- All tests passing
Removed:
- github.com/package/xyz v1.0.0
Reason: No longer needed after refactoring
## Review Checklist
- [ ] All imports removed
- [ ] Tests passing
- [ ] Build successful
- [ ] go.mod cleaned
- [ ] Commit message clear
ドキュメント化
DEPENDENCIES.mdの更新:
# Dependencies Log
## 2023-12-15: Cleanup
### Removed
- github.com/old/logger v1.0.0
- Reason: Migrated to new logger
- Replaced by: github.com/new/logger v2.0.0
- Removed by: @developer1
- github.com/deprecated/helper v2.1.0
- Reason: Functionality moved to native implementation
- No replacement needed
- Removed by: @developer2
### Impact
- Build size reduced by 15MB
- Dependency count reduced from 45 to 38
- No breaking changes
### Testing
- All unit tests passing
- Integration tests passing
- Manual testing completed
まとめ
依存関係削除の2つの方法
方法1: go mod tidy(推奨)
go mod tidy
- ✅ すべての未使用依存関係を自動削除
- ✅ 安全で確実
- ✅ 最も一般的
方法2: @noneで明示的削除
go get package@none
- ✅ 特定のパッケージのみ削除
- ✅ 意図が明確
- ✅ 間接的依存関係も削除
削除の流れ
1. import文を削除
↓
2. テスト実行
↓
3. go mod tidy 実行
↓
4. ビルド確認
↓
5. コミット
チェックリスト
削除前の確認:
□ パッケージが本当に不要か確認
□ 代替実装の準備完了
□ テストの更新完了
削除後の確認:
□ go build が成功
□ go test ./... がパス
□ go.modから削除されている
□ go.sumから削除されている
重要なポイント
- ✅ 定期的に go mod tidy を実行
- ✅ 削除理由をドキュメント化
- ✅ テストを必ず実行
- ✅ チームで情報共有
- ✅ コミットメッセージに詳細記載
これで、依存関係を適切に削除・管理できます!
おわりに
本日は、Go言語の依存関係の管理について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント