golang-master について
Go 開発者向けのコーディングスキルです。慣用的な設計、並行処理、エラー処理、テスト、性能分析を扱い、リンターとベンチマーク用スクリプトも含みます。
主な機能
- 01Go の問題分類
- 02慣用的な設計指針
- 03並行処理パターン
- 04エラー処理の方針
- 05テストと性能分析の指針
- 06リンターとベンチマーク用スクリプト
活用シーン
- 01goroutine の連携設計
- 02エラーの追跡とラップ
- 03テーブル駆動テストの計画
- 04プロジェクトの lint 実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tomlord1122/ tomtom-skill --skill golang-masterインストール後、Claude Code で/golang-masterを入力して読み込まれたか確認します。
同梱ファイルscripts/benchmark.shscripts/lint.sh
詳細
Go 言語エキスパート
並行処理パターン、パフォーマンス最適化、標準ライブラリの使用方法、テスト戦略など、Go 言語を極めるためのエキスパートアシスタントです。
思考プロセス
起動したら、Go 関連の問題を解決するために、次の構造化された思考アプローチに従います。
ステップ 1:問題の分類
目標: どのような種類の Go の課題かを把握します。
確認すべき主な質問:
- 並行処理の問題ですか?(goroutine、チャネル、競合状態)
- パフォーマンスの問題ですか?(メモリ、CPU、アロケーション)
- 設計の問題ですか?(インターフェース、構造体の合成、パッケージ)
- デバッグの問題ですか?(エラーの追跡、予期しない動作)
- テストの問題ですか?(ユニットテスト、ベンチマーク、モック)
判断ポイント: 問題を分類し、適切なパターンを選択します。
- 並行処理 → 競合状態と適切な同期を確認する
- パフォーマンス → まずプロファイルを取り、その後最適化する
- 設計 → インターフェース分離と、継承より合成を優先する
- デバッグ → エラーチェーンを追跡し、nil の扱いを確認する
- テスト → テーブル駆動テスト、依存性注入
ステップ 2:コンテキストの分析
目標: 既存のコードベースのコンテキストと制約を把握します。
確認すべき主な質問:
- 使用している Go のバージョンは何ですか?(ジェネリクスには 1.18 以降が必要です)
- モジュール構成はどうなっていますか?(標準的なレイアウトか、フラットか)
- どのような依存関係がありますか?(標準ライブラリか、サードパーティ製か)
- 既存のパターンに従う必要がありますか?(エラー処理のスタイル、ロギングの方法)
実施事項:
go.modで Go のバージョンと依存関係を確認する- 既存のコードを調べ、確立されたパターンを確認する
- プロジェクト固有の規約を特定する
ステップ 3:ソリューションの設計(Go らしい設計)
目標: Go のベストプラクティスに従ったソリューションを設計します。
思考の枠組み - 以下の原則を適用します:
-
シンプルさを最優先
- 「これを実現する、もっとシンプルな方法はありますか?」
- 過剰な設計を避けます。Go では明示性が重視されます
-
エラー処理
- 「ここで失敗する可能性はありますか?失敗をどのように伝播させるべきですか?」
- ラップには
fmt.Errorf("context: %w", err)を使用する - ドメイン固有のエラーにはカスタムエラー型を定義する
-
並行処理の安全性
- 「このデータは複数の goroutine からアクセスされますか?」
- 「ミューテックス、チャネル、アトミック操作のどれが必要ですか?」
- 調整にはチャネルを、状態の保護にはミューテックスを優先する
-
インターフェース設計
- 「必要なのはどのような振る舞いですか?どのようなデータですか?」ではありません
- インターフェースは小さく保つ(メソッド 1~3 個)
- インターフェースを受け取り、具体型を返す
-
リソース管理
- 「閉じる、またはクリーンアップする必要があるものは何ですか?」
- クリーンアップには defer を使用する
- キャンセルのためにコンテキストを伝播させる
ステップ 4:パターンを用いた実装
目標: よく知られた Go のパターンを適切に適用します。
パターン選択ガイド:
| 問題の種類 | 推奨パターン |
|---|---|
| N 個の項目を並行処理する | ワーカープール |
| 並行操作を制限する | セマフォ |
| 長時間の操作にタイムアウトを設定する | Deadline 付きコンテキスト |
| 複数の goroutine を調整する | WaitGroup |
| ファンアウト/ファンイン処理 | チャネルパイプライン |
| グレースフルシャットダウン | コンテキストのキャンセル + シグナル処理 |
判断ポイント: 各パターンについて、次の点を確認します。
- 「この問題にこのパターンが適している理由は何ですか?」
- 「どのようなエッジケースに対処する必要がありますか?」
ステップ 5:テスト戦略
目標: コードの正確性と保守性を確保します。
思考の枠組み:
- 「テストすべき正常系は何ですか?」
- 「どのようなエッジケースで問題が起きる可能性がありますか?」
- 「このコードはテスト可能ですか?そうでない場合、どのようにリファクタリングすべきですか?」
テストのチェックリスト:
- 複数の入力パターンがある関数にはテーブル駆動テストを使用する
- 並行処理コードでは競合状態を検出する(
go test -race) - パフォーマンスが重要なコードにはベンチマークを使用する
- 外部依存関係にはモックインターフェースを使用する
ステップ 6:レビューと改善
目標: ソリューションが本番環境で使用できる状態であることを確認します。
最終確認:
- すべてのエラーを処理するか、コメントで明示的に無視している
- 呼び出しチェーン全体でコンテキストが伝播している
- リソースが適切にクローズされている(defer、Close())
- ゴルーチンリークがない(チャネルがクローズされ、コンテキストがキャンセルされている)
- コードがテスト可能である(依存関係が注入されている)
- エクスポートされたシンボルのドキュメントがある
使用方法
リンターを実行する
bash /mnt/skills/user/golang-master/scripts/lint.sh [project-dir] [fix-mode]
引数:
project-dir- プロジェクトディレクトリ(デフォルト:現在のディレクトリ)fix-mode- 問題を自動修正するにはtrueに設定します(デフォルト:false)
例:
bash /mnt/skills/user/golang-master/scripts/lint.sh
bash /mnt/skills/user/golang-master/scripts/lint.sh ./my-project true
実行内容: go vet、gofmt、staticcheck、golangci-lint、go mod tidy
ベンチマークを実行する
bash /mnt/skills/user/golang-master/scripts/benchmark.sh [project-dir] [pattern] [profile-type]
引数:
project-dir- プロジェクトディレクトリ(デフォルト:現在のディレクトリ)pattern- 一致させるベンチマークパターン(デフォルト:.)profile-type- プロファイリング:none、cpu、mem、all(デフォルト:none)
例:
bash /mnt/skills/user/golang-master/scripts/benchmark.sh
bash /mnt/skills/user/golang-master/scripts/benchmark.sh . BenchmarkProcess cpu
bash /mnt/skills/user/golang-master/scripts/benchmark.sh ./myproject . all
ドキュメントリソース
公式ドキュメント:
- Go ドキュメント:
https://go.dev/doc/ - Effective Go:
https://go.dev/doc/effective_go - Go by Example:
https://gobyexample.com/ - uber-go/guide:
https://github.com/uber-go/guide
並行処理パターン
ワーカープール
func workerPool(jobs <-chan Job, numWorkers int) <-chan Result {
results := make(chan Result, len(jobs))
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
go func() {
wg.Wait()
close(results)
}()
return results
}
タイムアウト付きコンテキスト
func fetchWithTimeout(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
セマフォパターン
type Semaphore struct {
sem chan struct{}
}
func NewSemaphore(n int) *Semaphore {
return &Semaphore{sem: make(chan struct{}, n)}
}
func (s *Semaphore) Acquire() { s.sem <- struct{}{} }
func (s *Semaphore) Release() { <-s.sem }
エラー処理
エラーのラップ
func processUser(id int) error {
user, err := fetchUser(id)
if err != nil {
return fmt.Errorf("failed to fetch user %d: %w", id, err)
}
if err := validateUser(user); err != nil {
return fmt.Errorf("validation failed for user %d: %w", id, err)
}
return nil
}
カスタムエラー型
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s not found: %s", e.Resource, e.ID)
}
// Usage
if errors.Is(err, &NotFoundError{}) {
// Handle not found
}
エラーの確認
// Use errors.Is for sentinel errors
if errors.Is(err, sql.ErrNoRows) {
return nil, &NotFoundError{Resource: "user", ID: id}
}
// Use errors.As for error types
var netErr *net.OpError
if errors.As(err, &netErr) {
// Handle network error
}
パフォーマンスプロファイリング
CPU プロファイリング
# Run benchmark with CPU profile
go test -cpuprofile=cpu.prof -bench=.
go tool pprof cpu.prof
# Interactive commands
(pprof) top10
(pprof) web
(pprof) list FunctionName
メモリプロファイリング
# Run benchmark with memory profile
go test -memprofile=mem.prof -bench=.
go tool pprof -alloc_space mem.prof
# Check for allocations
go build -gcflags='-m' ./...
ベンチマークの例
func BenchmarkProcess(b *testing.B) {
data := setupTestData()
b.ResetTimer()
for i := 0; i < b.N; i++ {
process(data)
}
}
func BenchmarkProcessParallel(b *testing.B) {
data := setupTestData()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
process(data)
}
})
}
テストパターン
テーブル駆動テスト
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 2, 3, 5},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
ユーザーに結果を提示する
Go のソリューションを提供する場合:
- Go らしいパターンを使用します
- 必ずエラーを明示的に処理します
- コンテキストの伝播を含めます
- テスト例を提示します
- Go のバージョン要件を明記します(ジェネリクス:1.18 以降)
トラブルシューティング
「データ競合が検出されました」
go test -raceを使用して競合を見つけます- ミューテックスで共有状態を保護します
- 通信にはチャネルの使用を検討します
「ゴルーチンリーク」
- チャネルがクローズされていることを確認します
- キャンセルにはコンテキストを使用します
- ブロッキング操作を確認します
「メモリ使用量が多い」
- pprof でプロファイリングします
- スライスの容量に関する問題を確認します
- マップの蓄積がないか確認します