Go Development (1.25+)
Critical Output Rules
- State stdlib-first choices explicitly: use
net/http,encoding/json,context, andtestingwhere practical before adding dependencies. - Avoid unnecessary dependencies. Add a library only when concrete requirements beat stdlib simplicity.
- Prefer concrete types and small consumer-side interfaces only at real seams; do not abstract everything by default.
- Error handling guidance must say normal failures return
error, notpanic; wrap with context using%w; avoid custom error hierarchies unless callers need distinct behavior. - Always mention behavior tests for success and error paths, even when only giving design or error-handling advice. Prefer table-driven tests with
t.Run; stdlibtestingis enough unless the project already uses another test library. - For handlers/services, pass
context.Contextthrough API boundaries and map service errors to HTTP status at the edge. - Do not run destructive shell commands. For broad or risky changes, state the risk and ask before acting.
Core Philosophy
Stdlib-first stance, concrete-types-over-any, consumer-side interfaces, flat control flow, explicit error handling, the no-destructive-commands safety rule, and the post-generation verification loop are in references/principles.md — read it before generating code.
Quick Patterns
Private Interface at Consumer
// service/user.go - private interface where it's USED
type userStore interface {
Get(ctx context.Context, id string) (*User, error)
}
type Service struct {
store userStore // accepts interface
}
// repo/postgres.go - returns concrete type
func NewPostgresStore(db *sql.DB) *PostgresStore {
return &PostgresStore{db: db}
}
Flat Control Flow (No Nesting)
// GOOD: guard clauses, early returns
func process(user *User) error {
if user == nil {
return ErrNilUser
}
if user.Email == "" {
return ErrMissingEmail
}
if !isValidEmail(user.Email) {
return ErrInvalidEmail
}
return doWork(user)
}
// BAD: nested conditions
func process(user *User) error {
if user != nil {
if user.Email != "" {
if isValidEmail(user.Email) {
return doWork(user)
}
}
}
return nil
}
Error Handling
if err := doThing(); err != nil {
return fmt.Errorf("do thing: %w", err) // always wrap
}
// Sentinel errors
if errors.Is(err, ErrNotFound) {
return http.StatusNotFound
}
Concrete Types (Avoid any)
// GOOD: concrete types
func ProcessUsers(users []User) error { ... }
func GetUserByID(id string) (*User, error) { ... }
// BAD: unnecessary any
func ProcessItems(items []any) error { ... }
func GetByID(id any) (any, error) { ... }
Table-Driven Tests
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"valid", "hello", "HELLO", false},
{"empty", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Process(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
Go 1.25 Features
- testing/synctest: Deterministic concurrent testing
- encoding/json/v2: 3-10x faster (GOEXPERIMENT=jsonv2)
- runtime/trace.FlightRecorder: Production trace capture
- Container-aware GOMAXPROCS: Auto-detects cgroup limits
References
- principles.md - Core philosophy, safety rule, and verification loop (read before generating code)
- PATTERNS.md - Detailed code patterns
- TESTING.md - Testing with testify/mockery
- CLI.md - CLI application patterns
Tooling
go build ./... # Build
go test -race ./... # Test with race detector
golangci-lint run # Lint
mockery --all # Generate mocks
Failure Cases
- No Go files in repo / ambiguous project root: run
find . -name 'go.mod'to locate modules before generating code; do not assume a single root. - Compilation or test failure after generation: quote the failing line, state the cause, show the exact fix. Do not retry blindly—diagnose first.