Claude Media
Claude CodeでGoアプリを開発する手順 — CLAUDE.mdとhooksの設定例

Claude CodeでGoアプリを開発する手順 — CLAUDE.mdとhooksの設定例

Claude CodeにGoアプリを作らせるときの、go mod initからgo test・-race・クロスコンパイルまでの検証ループと、CLAUDE.md・hooksの設定例をまとめます。

Goの開発をClaude Codeに任せるときの要点

Goは検証コマンドが標準ツールに揃っている言語です。go build、go test、go vet、gofmt を回せば、Claude Codeが書いたコードの良し悪しをその場で判定できます。任せ方の要は、この検証コマンドを CLAUDE.md に書いておき、変更のたびに実行させて出力を読ませることです。

手順

Goアプリを任せる流れ

  1. 1

    モジュールを作る

    go mod init でモジュールを作り、cmd/ と internal/ に分けます。

  2. 2

    規約を書く

    CLAUDE.md に規約と検証コマンドを置きます。

  3. 3

    整形をhookに任せる

    gofmt をhookで自動実行します。

  4. 4

    テストの結果を読ませる

    go test と -race の出力を読ませて直させます。

  5. 5

    配布物を確かめる

    GOOS と GOARCH でクロスコンパイルが通るか見ます。

Claude Codeのインストールや基本操作は済んでいる前提です。同じ型でWebフレームワークを扱う例はClaude CodeでDjangoアプリをMVT構成で開発する手順にあります。

モジュールを作りディレクトリを分ける

前提として、Goのチュートリアルは、ある程度のプログラミング経験、コードを編集するテキストエディター、コマンドの端末があれば始められるとしています。エディターはVSCode(無料)、GoLand(有料)、Vim(無料)が代表例です。Goのインストールは公式サイトのダウンロード手順に従います。Claude Codeを使う場合も、go version が通る状態までは自分で整えておきます。

Goの公式チュートリアルは、空のディレクトリで go mod init を実行するところから始まります。go.mod は依存関係を追跡するファイルで、コードと一緒にリポジトリへ置きます。モジュールパスは、公開するならGoツールがダウンロードできる場所(リポジトリのURL)にします。

mkdir myapp && cd myapp
go mod init github.com/example/myapp

最初の動作確認は go run . です。チュートリアルでは main パッケージに fmt.Println("Hello, World!") を書いた hello.go をこのコマンドで実行します。go run は数あるgoコマンドの1つで、go help で一覧を確認できます。Claude Codeに「動く最小構成を作って go run . で確かめて」と頼めば、実行結果まで読んで報告してくれるので、環境の不備も早い段階で見つかります。

モジュールレイアウトの解説では、コマンドが補助パッケージを持つ場合は internal/ にまとめるのが推奨です。internal/ 配下のパッケージは他のモジュールからimportできないため、APIを公開せずに自由にリファクタリングできます。コマンドが複数あるリポジトリでは cmd/ の下に置くのが一般的な慣習です。

myapp/
  go.mod
  cmd/
    myapp/
      main.go
  internal/
    store/
      store.go
      store_test.go

Claude Codeに「新しいパッケージを作って」と頼むと、置き場所をその場で決めます。最初にこの構成を明示しておくと、公開したくないコードが根に散らばるのを防げます。

CLAUDE.mdに書く規約と検証コマンド

CLAUDE.md はセッション開始時に毎回読み込まれる指示です。メモリのドキュメントでは、具体的で簡潔な指示ほど守られやすく、1ファイル200行以内が目安とされています。ただしClaudeはこれを文脈として読むだけで、強制設定ではありません。必ず守らせたい動作は、後述のhookで担保します。

Go向けの例を示します。内容はEffective Goの記述に沿っています。

# CLAUDE.md
## 構成
- 実行ファイルは cmd/<name>/main.go、共有ロジックは internal/ に置く
## コーディング規約
- コードは gofmt の出力を正とする(手で整形しない)
- エラーの戻り値は必ず確認する。`_` で捨てない
- goroutine を起動する関数は、終了と失敗を呼び出し側に返す
## 検証(変更のたびに実行)
- go build ./...
- go vet ./...
- go test ./...
- 並行処理に触れたら go test -race ./...

規約の根拠はEffective Goにあります。Effective Goは、エラーを捨てるために戻り値を無視するのを「terrible practice(ひどい慣行)」と呼び、エラーの戻り値は必ず確認するよう述べています。整形については、書き方に迷ったら gofmt を実行し、出力が期待と違うならプログラムの側を組み替えるよう説明しています。

CLAUDE.md の置き場所は用途で分かれます。チームで共有する規約はプロジェクト直下、自分だけの環境設定は CLAUDE.local.md に書きます。後者は .gitignore に入れる想定です。役割分担の考え方はCLAUDE.mdとSkillsの役割分担で詳しく扱っています。

hookでgofmtを必ず走らせる

整形のように毎回機械的に済ませたい処理は、CLAUDE.md よりhookに向いています。

くらべる

同じ整形ルールの置き場所による違い

文脈

CLAUDE.mdに書く

Claudeが読んで従う指示です。「gofmtを正とする」と書いても、実行するかどうかはClaudeの判断に委ねられます。

コマンド

hookに書く

編集イベントのたびに、設定したコマンドが実行されます。Claudeの判断を経由しません。

hooksガイドには、ファイル編集後にPrettierを実行する例があります。PostToolUse イベントに Edit|Write のmatcherを指定し、編集されたファイルのパスをコマンドへ渡す構成です。この型をGo向けに組み替えると、次のようになります(Go向けの書き換えは本記事の例示です)。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path | select(endswith(\".go\"))' | xargs -r -I{} gofmt -w {}"
          }
        ]
      }
    ]
  }
}

これを .claude/settings.json に置きます。jq が必要です。select(endswith(".go")) で .go ファイルだけに絞るので、Markdownなど他の編集では何も実行されません。

ガイドのPrettier例をそのまま写すと壊れる場面

ガイドの例に grep '\.go$' を足しただけの形は、パスにスペースがあると崩れます。次の出力は、gofmt の代わりに受け取った引数を表示する printf を使い、macOSの端末で2つの形を比べたものです。スペースを含むパス /tmp/my dir/a b.go を渡した結果です。

echo '{"tool_input":{"file_path":"/tmp/my dir/a b.go"}}' \
  | jq -r '.tool_input.file_path' | grep '\.go$' \
  | xargs -r -n1 printf '[%s]\n'
[/tmp/my]
[dir/a]
[b.go]
 
echo '{"tool_input":{"file_path":"/tmp/my dir/a b.go"}}' \
  | jq -r '.tool_input.file_path | select(endswith(".go"))' \
  | xargs -r -I{} printf '[%s]\n' {}
[/tmp/my dir/a b.go]

xargs は既定で空白区切りなので、1つのパスが3つの引数に割れました。-I{} を付けると1行を1引数として渡せます。ディレクトリ名にスペースがあるリポジトリでは、前者の形だと gofmt が存在しないパスを3つ受け取って失敗します。

もう1点、grep は一致が無いと終了コード1を返します。ただしパイプの終了コードは最後のコマンドのものなので、.md を編集したときもフック全体は終了コード0で終わりました。gofmt が走らなかったことは、画面には出ません。整形が効いたかは、編集後のファイルで確かめます。

整形の失敗をClaudeに返す

もう一段強く縛りたいときは、終了コード2を使います。hooksのリファレンスでは、PreToolUse でのexit 2は呼び出しのブロックで、stderrに書いた理由がClaudeへのフィードバックになります。go.sum の手編集を止めるような用途に向きます。

PostToolUse ではツールがすでに実行済みなので、exit 2でも編集は取り消されず、stderrがClaudeに見えるだけです。この性質は、go vet の指摘を編集の直後に返す用途に合います。同じmatcherの hooks 配列に次の1件を足します。リンタを束ねて回す構成はClaude Codeでgolangci-lintを回すにあります。

{
  "type": "command",
  "command": "go vet ./... || exit 2"
}

同じmatcherに並べたhookは並列に動きます。gofmt との実行順は決まらないので、互いの出力に依存させないでください。go vet は編集のたびに走るため、複数ファイルにまたがる変更の途中でも失敗を返しうる点は、運用しながら調整する部分です。

命名とリソース解放の規約も書いておく

Claude Codeは他言語の癖でGoを書くことがあります。Effective Goが挙げる慣習のうち、レビューで指摘されやすいものを CLAUDE.md に足しておくと、手戻りが減ります。

  • パッケージ名は小文字の1語にし、アンダースコアや mixedCaps を使わない
  • フィールド owner のgetterは GetOwner でなく Owner とし、setterは SetOwner とする
  • 複数語の名前は MixedCaps か mixedCaps で書き、アンダースコアで区切らない
  • ファイルなどを開いたら、直後に defer f.Close() と書く

Effective Goによると、Close のような呼び出しを defer で予約すると利点が2つあります。1つは閉じ忘れを防げること、もう1つは関数のどの出口でも確実に実行されることです。

回復できないエラーには組み込み関数 panic があります。ただし通常のエラー報告は、戻り値に error を足して返す方法が基本です。Claudeが安易に panic を使っていたら、戻り値に直させます。

テストを書かせて結果を読ませる

Goでは _test.go で終わるファイル名にテスト関数を書きます。関数名は Test で始め、引数に *testing.T を取ります。公式チュートリアルは t.Errorf で失敗を報告する形です。

package store
 
import "testing"
 
func TestGetMissing(t *testing.T) {
    s := New()
    if _, err := s.Get("nope"); err == nil {
        t.Errorf("Get(%q) = nil error, want error", "nope")
    }
}

実行は go test です。-v を付けると、テストの一覧と結果が出ます。

go test ./...
go test -v ./internal/store

Claude Codeへの頼み方はこうです。

internal/store に Get を実装して。先にテストを書き、go test ./... を実行して失敗を確認してから実装し、最後に全部通ることを確認して。

テストが通るまでの反復は、Claude Codeが得意とする作業です。出力に失敗メッセージが出れば、それを読んで直します。人間の役目は、テストが仕様を表しているかの確認です。テストが緩ければ、通っても意味がありません。

goroutineとエラー処理を任せるときの落とし穴

Effective Goは並行処理について、共有した値をチャネルで受け渡すことで、値に触れるgoroutineを同時に1つに保つ考え方を紹介しています。標語は「メモリを共有して通信するな。通信してメモリを共有せよ」です。この方式ならデータ競合は設計上起きないとされています。

ただし同じ箇所で、この考え方は行き過ぎることもあると断っています。例えば参照カウントは、整数変数をmutexで囲むほうが向く場合があるとしています。

一方、goroutineの中で起きたエラーは、呼び出し元に自然には届きません。Claude Codeに並行処理を書かせるときは、CLAUDE.md に書いた「終了と失敗を呼び出し側に返す」を具体的に指示すると安定します。例えば、次のような形です。

func fetchAll(urls []string) error {
    errCh := make(chan error, len(urls))
    for _, u := range urls {
        go func(u string) {
            errCh <- fetch(u)
        }(u)
    }
    var firstErr error
    for range urls {
        if err := <-errCh; err != nil && firstErr == nil {
            firstErr = err
        }
    }
    return firstErr
}

バッファ付きチャネルに全件のエラーを流し、呼び出し側で回収する最小の形です。fetch は例示用の関数です。

並行処理を含む変更では、検証にレース検出器を加えます。レース検出器の解説では、go test・go run・go build・go install のいずれにも -race を付けられます。

go test -race ./...

レース検出器の出力を読ませ、競合の箇所を直させる流れを反復すると、レビューの負担が減ります。CLAUDE.md の検証欄に「並行処理に触れたら -race」と書いたのはこのためです。レポートの読み方と修正の選び方はgo test -raceのレポートを読む手順で扱っています。

GOOSとGOARCHでクロスコンパイルを確認する

Goは環境変数 GOOS(対象OS)と GOARCH(対象アーキテクチャ)を指定するだけで、別環境向けの実行ファイルを作れます。GOOS には linux、darwin、windows などが、GOARCH には amd64 や arm64 などが選べます。

GOOS=linux GOARCH=arm64 go build -o dist/myapp-linux-arm64 ./cmd/myapp
GOOS=windows GOARCH=amd64 go build -o dist/myapp.exe ./cmd/myapp

有効な組み合わせは限られています。Claude Codeに配布物の作成を任せるときは、組み合わせを自分で決めて指示してください。ビルドが通ることまでは自動で確認できますが、対象環境での動作確認は別に必要です。

任せる範囲の目安

作業Claude Codeに任せる理由
関数・パッケージの実装Claude Codeに任せる◎理由go build と go vet で即座に検証できる
テストの追加と修正の反復Claude Codeに任せる◎理由go test の出力が判断材料になる
goroutineを含む変更Claude Codeに任せる○理由-race を通しても、設計の妥当性は自分で確認する
モジュールパスの決定Claude Codeに任せる△理由公開URLと一致させる必要があり、自分で決める
配布用バイナリの動作保証Claude Codeに任せる△理由ビルドは通っても、対象環境での実機確認が必要

よくあるつまずき

依存を足したのにgo buildが通らない

外部パッケージをimportしたのに解決されないときは、go mod tidy で go.mod を更新させます。Goのはじめてのチュートリアルでも、外部パッケージ rsc.io/quote をimportしたあとにこのコマンドを実行しています。

Claudeが手で整形して差分が増える

CLAUDE.md に「gofmtを正とする」と書き、hookで gofmt -w を実行しておけば、整形差分でレビューが荒れる場面は減ります。

エラーを握りつぶすコードが混じる

CLAUDE.md の規約に加え、レビューの依頼文に「エラーの戻り値が _ で捨てられていないか確認して」と書き添えます。Effective Goが「必ず確認する」と述べている点なので、根拠を示して指示できます。

まとめ

Goの開発は、検証コマンドの出力をClaude Codeに読ませる反復とよく噛み合います。CLAUDE.md に構成と検証コマンドを書き、gofmt はhookで自動化し、並行処理には -race を加える。この3点を最初に整えておくと、任せられる範囲が広がります。

GoでMCPサーバーを作る場合はGoでMCPサーバーを書く公式SDKの使い方、Claude APIをGoから呼ぶ場合はClaude Go SDKの実装ガイドが続きの手順です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. go.dev/doc/tutorial/getting-started
  2. go.dev/doc/effective_go
  3. go.dev/doc/tutorial/add-a-test
  4. go.dev/doc/articles/race_detector
  5. go.dev/doc/modules/layout
  6. go.dev/doc/install/source
  7. code.claude.com/docs/en/memory
  8. code.claude.com/docs/en/hooks-guide
  9. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn