Claude Code से Go विकास: go.mod, टेस्ट, concurrency और race detector
Claude Code के साथ Go विकास का व्यावहारिक workflow: repo map, go.mod, go work, API/CLI, tests, context और concurrency.
Claude Code से Go development कराते समय सबसे जोखिम भरा prompt होता है: “API बना दो” या “tests जोड़ दो”, वह भी repository context के बिना। Code चल सकता है, लेकिन वह मौजूदा go.mod, package boundaries, error policy, context propagation और concurrency safety से अलग हो सकता है। Go की syntax सरल है, इसलिए ऐसी गड़बड़ियां छोटे diff में छिप जाती हैं।
यह guide Claude Code को सिर्फ code generator नहीं, बल्कि Go development agent की तरह इस्तेमाल करती है। Flow में repository mapping, module और workspace decisions, API/CLI changes, table-driven tests, error wrapping, context cancellation, concurrency pitfalls, race detector, benchmarks और safe prompts शामिल हैं। Module dependency manage करने की इकाई है, workspace कई local modules पर साथ काम करने का आधार है, और context cancellation तथा deadline को नीचे वाली functions तक पहुंचाने का तरीका है।
Masa ने यह तरीका एक छोटी Go task API पर आजमाया। पहला prompt सिर्फ “handler जोड़ो” था। Result local पर चला, लेकिन tests नहीं थे, cancellation अस्पष्ट था और goroutine से shared map लिखने का risk था। Prompt को “पहले repo पढ़ो और go test -race से verify करो” में बदलने पर review points साफ हो गए।
पहले repository map बनाएं
Go में पहली task code generation नहीं, map बनाना है: कौन से directories commands हैं, कौन से internal packages हैं, कौन सा go.mod dependencies संभालता है, और CI क्या check करता है। Claude Code को first pass read-only दें।
pwd
find . -name go.mod -o -name go.work -o -name "*.go" | sort | sed -n '1,120p'
go env GOMOD GOWORK
go list -m
go list ./...
go test ./...
पहला prompt इस तरह रखें:
इस Go repository को inspect करें। अभी files edit न करें।
Report करें:
- go.mod और go.work मौजूद हैं या नहीं
- cmd, internal, pkg, api, migrations, testdata की भूमिका
- public types/functions जिन्हें बदलने से compatibility टूट सकती है
- मौजूदा error handling style
- कौन से boundaries context.Context लेते हैं
- go test ./... का result
- अगली task के लिए safe minimum files
Official references के लिए layout पर Organizing a Go module और modules पर Go Modules Reference देखें। Claude Code के लिए Claude Code overview उपयोगी है।
flowchart LR
A["repo map"] --> B["go.mod / go.work"]
B --> C["API or CLI change"]
C --> D["table-driven tests"]
D --> E["go test -race"]
E --> F["benchmark and review"]
इसी तरह के workflow के लिए existing codebase repo map और CLAUDE.md best practices देखें। Go में भी पहले working agreement तय करना जरूरी है, ताकि Claude Code unrelated files न छुए।
go.mod और go work को drift न होने दें
go.mod module path, Go version और dependencies लिखता है। छोटी app में अक्सर एक ही go.mod काफी होता है। Multi-module repo में go.work से go command कई local modules को साथ देख सकता है। Official tutorial है Getting started with multi-module workspaces।
Claude Code को dependency जोड़ने देने से पहले current state देखें:
go env GOMOD GOWORK
go list -m all
go mod tidy
go test ./...
Workspace तभी बनाएं जब सच में कई modules साथ edit करने हों:
mkdir -p services/taskapi tools/taskctl
cd services/taskapi
go mod init example.com/acme/taskapi
cd ../..
cd tools/taskctl
go mod init example.com/acme/taskctl
cd ../..
go work init ./services/taskapi ./tools/taskctl
go work use ./services/taskapi ./tools/taskctl
go work sync
Pitfall यह है कि go.work hidden dependency trick बन जाए। Local पर code इसलिए pass हो सकता है क्योंकि workspace local modules की ओर point कर रहा है, लेकिन CI या teammate की machine पर fail हो सकता है। तय करें कि go.work team infrastructure है या personal local state। Claude Code से कहें कि नया module या dependency जोड़ने से पहले reason बताए।
API और CLI एक ही service layer इस्तेमाल करें
Go में executable entrypoints अक्सर cmd/ में और reusable business logic internal/ में रखे जाते हैं। नीचे example में core Store HTTP से अलग है, ताकि बाद में CLI भी वही behavior use करे। यह सिर्फ standard library इस्तेमाल करता है। इसे cmd/taskapi/main.go में paste कर सकते हैं।
// cmd/taskapi/main.go
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"time"
)
var ErrValidation = errors.New("validation failed")
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
}
type Store struct {
mu sync.Mutex
next int
tasks map[string]Task
}
func NewStore() *Store {
return &Store{next: 1, tasks: make(map[string]Task)}
}
func (s *Store) Create(ctx context.Context, title string) (Task, error) {
select {
case <-ctx.Done():
return Task{}, fmt.Errorf("create task canceled: %w", ctx.Err())
default:
}
title = strings.TrimSpace(title)
if title == "" {
return Task{}, fmt.Errorf("%w: title is required", ErrValidation)
}
s.mu.Lock()
defer s.mu.Unlock()
task := Task{
ID: fmt.Sprintf("task-%06d", s.next),
Title: title,
Status: "open",
CreatedAt: time.Now().UTC(),
}
s.next++
s.tasks[task.ID] = task
return task, nil
}
func (s *Store) List(ctx context.Context) ([]Task, error) {
select {
case <-ctx.Done():
return nil, fmt.Errorf("list tasks canceled: %w", ctx.Err())
default:
}
s.mu.Lock()
defer s.mu.Unlock()
tasks := make([]Task, 0, len(s.tasks))
for _, task := range s.tasks {
tasks = append(tasks, task)
}
return tasks, nil
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
store := NewStore()
mux := http.NewServeMux()
mux.HandleFunc("GET /tasks", func(w http.ResponseWriter, r *http.Request) {
tasks, err := store.List(r.Context())
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, tasks)
})
mux.HandleFunc("POST /tasks", func(w http.ResponseWriter, r *http.Request) {
var body struct {
Title string `json:"title"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid json"})
return
}
task, err := store.Create(r.Context(), body.Title)
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusCreated, task)
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
logger.Info("taskapi listening", "addr", server.Addr)
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("server failed", "error", err)
os.Exit(1)
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
logger.Error("graceful shutdown failed", "error", err)
}
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func writeError(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, ErrValidation):
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
writeJSON(w, http.StatusRequestTimeout, map[string]string{"error": err.Error()})
default:
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
}
}
इसे चलाने के commands:
go mod init example.com/taskapi
mkdir -p cmd/taskapi
go run ./cmd/taskapi
curl -s http://localhost:8080/tasks
curl -s -X POST http://localhost:8080/tasks \
-H 'content-type: application/json' \
-d '{"title":"write table-driven tests"}'
बाद में CLI जोड़ें तो handler logic copy न करें। उसी service layer को call करें। इसके लिए Claude Code से CLI tool development देखें।
table-driven tests को Done का हिस्सा बनाएं
Table-driven test में inputs और expected results को slice में table की तरह लिखा जाता है और हर row पर वही test logic चलती है। यह validation, errors और edge cases के लिए Go में बहुत उपयोगी pattern है। Claude Code से exact cases मांगें।
// cmd/taskapi/store_test.go
package main
import (
"context"
"errors"
"fmt"
"testing"
)
func TestStoreCreate(t *testing.T) {
tests := []struct {
name string
title string
wantErr error
}{
{name: "valid title", title: "ship release notes"},
{name: "blank title", title: " ", wantErr: ErrValidation},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store := NewStore()
task, err := store.Create(context.Background(), tt.title)
if tt.wantErr != nil {
if !errors.Is(err, tt.wantErr) {
t.Fatalf("error = %v, want %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if task.ID == "" || task.Status != "open" {
t.Fatalf("unexpected task: %+v", task)
}
})
}
}
func TestStoreCreateHonorsCanceledContext(t *testing.T) {
store := NewStore()
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := store.Create(ctx, "will not be created")
if !errors.Is(err, context.Canceled) {
t.Fatalf("error = %v, want context.Canceled", err)
}
}
func BenchmarkStoreCreate(b *testing.B) {
store := NewStore()
ctx := context.Background()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := store.Create(ctx, fmt.Sprintf("task-%d", i)); err != nil {
b.Fatal(err)
}
}
}
Acceptance criteria में ये commands रखें:
gofmt -w cmd/taskapi
go test ./...
go test -race ./...
go test -run TestStoreCreate -count=1 ./...
go test -bench=. -benchmem ./...
Official testing package tests और benchmarks explain करता है। Benchmark में Go b.N से loop count control करता है, इसलिए “तेज लग रहा है” की जगह measurement मिलता है।
context cancellation को न तोड़ें
context.Context API boundaries के बीच cancellation, deadlines और request values ले जाता है। Official context package बताता है कि server request से Context बनता है और outgoing calls को Context accept करना चाहिए।
Claude Code की common mistake है कि handler में r.Context() मिलता है, लेकिन service या repository में फिर context.Background() बना दिया जाता है। इससे cancellation chain टूटती है। Client disconnect हो जाए तो database query या external API call फिर भी चल सकती है। Prompt में लिखें: lower layers में नया background context न बनाएं, caller का ctx pass करें।
दूसरी mistake है context.WithTimeout से मिला cancel call न करना। Claude Code से कहें कि derived context बनाते समय defer cancel() लगाए और ऐसे changes के बाद go vet चलाए।
race detector से concurrency verify करें
Goroutines हल्की होती हैं, पर shared data को synchronization चाहिए। Data race तब होता है जब goroutines एक ही variable को साथ access करें, कम से कम एक write हो और synchronization न हो। Official Data Race Detector go test -race से चलता है। यह सिर्फ executed code paths में race पकड़ता है।
Shared map में concurrent writes से बचें:
func CountByStatusBad(tasks []Task) map[string]int {
counts := make(map[string]int)
var wg sync.WaitGroup
for _, task := range tasks {
task := task
wg.Add(1)
go func() {
defer wg.Done()
counts[task.Status]++
}()
}
wg.Wait()
return counts
}
Shared state को mutex से protect करें:
func CountByStatus(tasks []Task) map[string]int {
counts := make(map[string]int)
var (
mu sync.Mutex
wg sync.WaitGroup
)
for _, task := range tasks {
task := task
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counts[task.Status]++
mu.Unlock()
}()
}
wg.Wait()
return counts
}
Prompt में सिर्फ “इसे concurrent बनाओ” न लिखें। Claude Code से shared variables, channel close ownership, cancellation behavior, WaitGroup correctness और go test -race result check कराएं। Review angle के लिए Claude Code code review देखें।
Go के लिए safe Claude Code prompts
अच्छे Go prompts edit scope, contracts, verification commands और forbidden changes साफ बताते हैं।
Goal: taskapi में POST /tasks के लिए validation और tests जोड़ना।
Allowed files:
- cmd/taskapi/main.go
- cmd/taskapi/store_test.go
Rules:
- go.mod का module path न बदलें
- नई external dependency न जोड़ें
- public types या JSON fields rename न करें
- context.Context handler से service तक propagate करें
- errors को fmt.Errorf("%w") से wrap करें और caller errors.Is से check करे
Done means:
- gofmt -w cmd/taskapi
- go test ./...
- go test -race ./...
- change और remaining risk छोटा report करें
Dependency चाहिए लगे तो पहले रुकवाएं:
अगर external library जरूरी लगती है, तो implement करने से पहले candidate, कारण, standard library क्यों काफी नहीं है, और go.mod impact समझाएं। Approval के बिना go get न चलाएं।
इससे अचानक CLI framework, router, ORM या mock library जोड़ने का risk घटता है। Go में standard library से काफी काम हो जाता है, इसलिए dependency changes हमेशा review में आने चाहिए।
Use cases और pitfalls
पहला use case existing API में छोटा endpoint जोड़ना है। Handler, service method, table-driven tests और go test -race को एक ही task में रखें। Pitfall है सिर्फ handler जोड़ना और service boundary या error format तोड़ देना।
दूसरा use case CLI और API के लिए common logic बनाना है। Operations CLI और admin API अगर वही business rule चलाते हैं, तो rule को internal/service में रखें। Pitfall है CLI के लिए logic copy करना और बाद में behavior drift होना।
तीसरा use case slow aggregation parallelize करना है। Dashboard अगर तीन external APIs बुलाता है, तो goroutines मदद कर सकती हैं। Pitfalls हैं shared slice/map writes, कई जगह से same channel close करना, और cancellation के बाद goroutine leak।
चौथा use case performance work है। Claude Code से “तेज करो” कहने से पहले go test -bench=. -benchmem से baseline लें। Pitfall है बिना measurement cache या goroutines जोड़ना और memory/complexity बढ़ाना।
पांचवां use case multi-module repo है। go.work local edits आसान करता है, लेकिन go env GOWORK और CI जैसी test location confirm करें। Pitfall है local workspace pass को production evidence मान लेना।
CTA और अगला कदम
Go के लिए Claude Code इस्तेमाल करने वाले readers अक्सर real team repository में safe adoption चाहते हैं। Personal workflow के लिए free cheatsheet से शुरू करें। CLAUDE.md, permissions, review prompts और Go checklists को साथ में व्यवस्थित करना हो तो products and templates देखें। Team adoption, Go APIs, CLIs, CI और review rules के लिए Claude Code training and consultation उपयोगी है।
सारांश
Stable Go workflow है: पहले repository map बनाएं, go.mod और go.work inspect करें, API/CLI changes को service layer के पीछे रखें, table-driven tests को Done का हिस्सा बनाएं, context cancellation बचाएं, race detector चलाएं और performance claim से पहले benchmark लें।
मैंने यह workflow एक छोटी task API पर test किया। सबसे असरदार बदलाव था verification commands को prompt में सीधे लिखना। जब gofmt, go test ./..., go test -race ./... और go test -bench=. -benchmem completion criteria बने, Claude Code की final report “implemented” से बदलकर “verified, remaining risks ये हैं” हो गई। Publish करने से पहले official links, internal links, body depth, code fences, updatedDate और heroImage भी check करें।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Obsidian to CLAUDE.md workflow: context बार-बार न समझाएं
Obsidian notes को CLAUDE.md operating notes में बदलकर Claude Code sessions को resume करना आसान बनाएं.
Claude Code Revenue CTA Routing: article से PDF, Gumroad और consultation तक
Reader intent के आधार पर free PDF, Gumroad products और consultation तक CTA route करने वाला workflow.
Claude Code टीम हैंडऑफ नियम: review proof, permissions, rollback और revenue path
Claude Code टीम काम के लिए evidence, permission rules, rollback, free PDF, Gumroad और consultation path वाला handoff.