`if err != nil`を量産させないために──Claude Code × Go開発のCLAUDE.md設計と典型的アンチパターン対策

はじめに

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.jsonWrite|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
    fi

    go test -race ./...は並行処理を含むコードの変更時に必ず実行する習慣をCLAUDE.mdで明記しておくと、生成されたgoroutineのコードを毎回手動で確認する手間が減る。


    EM視点──GoチームにClaude Codeを根付かせる

    CLAUDE.mdをリポジトリにコミットすれば、チーム全員が同じルールでClaude Codeを使える。定着のカギは段階を踏むことだ。

  • 禁止パターンの明記: return err禁止、goroutineの裸起動禁止
  • 推奨パターンのコード例を追加: fmt.Errorferrgroup等をCLAUDE.mdに直接書く
  • golangci-lintをフックで自動実行: コードから強制する

  • まとめ

    GoのコードをClaude Codeに書かせるとき、エラーwrap・goroutineのライフサイクル管理・インターフェースの設計方針を何も伝えなければ、Goらしくないコードが出続ける。CLAUDE.mdにGoの哲学を書いておくことで、生成されるコードの質が変わる。

    まずfmt.Errorf("操作内容: %w", err)の1行をCLAUDE.mdに追記してほしい。

    コメント

    タイトルとURLをコピーしました