はじめに
Claude CodeにGoのコードを生成させると、エラーハンドリングの「形」は出てくる。if err != nilで分岐し、return nil, errを返す。ただし、それだけだ。どのファイルを読もうとして失敗したのか、どの操作で詰まったのかというコンテキストが消える。goroutineは起動するが終了パスがない。インターフェースは過剰に定義される。
Claude CodeがGoのコードで詰まる3つのパターン
エラーコンテキストのない return err
// Claude Codeが出しやすい(コンテキストなし)
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err // どのファイルを読もうとして失敗したか分からない
}
}// 正しいパターン(%wでwrap)
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config from %s: %w", path, err)
}
}fmt.Errorf("操作内容: %w", err)でwrapするのがGoの標準的なアプローチだ(Go公式ドキュメントで確認済み)。CLAUDE.mdに「裸のreturn errは禁止。全エラーをwrapする」と書くだけでこのパターンが消える。
goroutineリーク(終了パスの欠落)
コンテキストによるキャンセルとerrgroupの2点が重要だ。
// 正しいパターン(errgroupで制御)
func StartWorker(ctx context.Context, jobs <-chan Job) error {
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case job, ok := <-jobs:
if !ok { return nil }
if err := process(ctx, job); err != nil {
return fmt.Errorf("process job: %w", err)
}
}
}
})
return g.Wait()
}過剰なインターフェース設計
Goのイディオムは「消費側でインターフェースを定義する」だが、Claude Codeは実装側に大きなインターフェースを定義しやすい。
// 消費側で必要なメソッドだけ定義する(Goのイディオム)
type UserFinder interface {
FindByID(ctx context.Context, id string) (*User, error)
}
func NewUserHandler(finder UserFinder) *UserHandler { ... }「Accept interfaces, return structsの原則を守る」「1インターフェースは1〜3メソッドを目安」とCLAUDE.mdに書く。
GoプロジェクトのCLAUDE.md完全テンプレート
# Go プロジェクト
技術スタック
Go 1.22+ / gin(またはecho/net/http) / sqlx(またはGORM/pgx) / testing + testify
エラーハンドリング
全エラーをfmt.Errorf("操作内容: %w", err)でwrap
裸の return err は禁止 / panicは予期しないバグのみ
並行処理
I/O関数の第一引数: context.Context
goroutineには必ず終了パスを定義(select + ctx.Done())
lifecycle管理: errgroup / channelはsender側だけがclose
インターフェース設計
消費側で定義 / 1〜3メソッドを目安 / "Accept interfaces, return structs"
テスト
table-driven testsパターン必須(t.Runでサブテスト)
ゼロ値・境界値・エラーケースを必ず含める
コード品質
gofmt / goimports: 全ファイルに適用
golangci-lint: 警告ゼロ / go test -race: 並行処理変更時に必須
パッケージ設計
ディレクトリ: cmd/ internal/ pkg/
util/ helpers/ common/ などのキャッチオールパッケージは作らない table-driven tests + golangci-lintを自動化する
CLAUDE.mdに「table-driven testsパターン必須」と指定すると、Claude Codeがこの形式で出力する。
func TestLoadConfig(t *testing.T) {
tests := []struct {
name string
path string
wantErr bool
}{
{"valid config", "testdata/valid.yaml", false},
{"missing file", "testdata/notexist.yaml", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := LoadConfig(tt.path)
if (err != nil) != tt.wantErr {
t.Fatalf("wantErr %v, got err: %v", tt.wantErr, err)
}
})
}
}PostToolUseフックでgofmtを自動実行する設定はシンプルだ。.claude/settings.jsonでWrite|Editにフックを設定し、.claude/hooks/go_format.shを呼び出す。
#!/usr/bin/env bash
FILE_PATH=$(python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input', {}).get('file_path', ''))")
if [[ "$FILE_PATH" == *.go ]]; then
gofmt -w "$FILE_PATH"
goimports -w "$FILE_PATH" 2>/dev/null || true
figo test -race ./...は並行処理を含むコードの変更時に必ず実行する習慣をCLAUDE.mdで明記しておくと、生成されたgoroutineのコードを毎回手動で確認する手間が減る。
EM視点──GoチームにClaude Codeを根付かせる
CLAUDE.mdをリポジトリにコミットすれば、チーム全員が同じルールでClaude Codeを使える。定着のカギは段階を踏むことだ。
return err禁止、goroutineの裸起動禁止fmt.Errorf・errgroup等をCLAUDE.mdに直接書くまとめ
GoのコードをClaude Codeに書かせるとき、エラーwrap・goroutineのライフサイクル管理・インターフェースの設計方針を何も伝えなければ、Goらしくないコードが出続ける。CLAUDE.mdにGoの哲学を書いておくことで、生成されるコードの質が変わる。
まずfmt.Errorf("操作内容: %w", err)の1行をCLAUDE.mdに追記してほしい。

コメント