agentleFS
brpazbrpaz/agent-skills/skills/golang-development/SKILL.md

golang-development

Skill1 starsChanged 6 months agoMITView on GitHub

Write and review Go code with idiomatic project structure, errors, concurrency, testing, and performance guidance.

  • Deletes or force-pushes
  • Installs packages

We split this skill into the rules it gives an agent

10 rules. 0 are common, 5 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when writing, reviewing, or debugging Go code.

    line 10
  2. Very specific:

    Keep pkg/ minimal. Place application code in internal/ unless other modules must import it.

    line 82
  3. Distinctive:

    No underscores or mixedCaps

    line 116
  4. Very specific:

    Avoid generic names (util, common, base, helper)

    line 117
  5. Very specific:

    Rule: Use sync.RWMutex when reads vastly outnumber writes.

    line 504
  6. Distinctive:

    Note: Prefer dependency injection over singletons.

    line 849
  7. Very specific:

    ALWAYS use go fmt: before committing code - no exceptions.

    line 1396
  8. Distinctive:

    Accept interfaces, return structs: - keep APIs flexible by accepting interfaces but returning concrete types.

    line 1402
  9. Very specific:

    Preallocate slices when size is known: - use make([]T, 0, capacity).

    line 1406
  10. Distinctive:

    Pass context to downstream functions: - never create new context in the middle of a call chain.

    line 1408

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Go (Golang) - Patterns and Guidance

Use this skill when writing, reviewing, or debugging Go code. Covers idiomatic Go patterns, project structure, concurrency, testing, and production-ready practices.

When to Use

  • Writing new Go packages, services, or CLI tools
  • Reviewing or refactoring existing Go code for idiomatic style
  • Debugging Go concurrency, error handling, or performance issues
  • Setting up Go project structure, module layout, or test harness

Go Philosophy

Core principles:

  • Simplicity over cleverness
  • Explicit over implicit
  • Composition over inheritance
  • Clear is better than clever
  • Errors are values
  • Concurrency is not parallelism

Key idioms:

  • "Don't communicate by sharing memory; share memory by communicating"
  • "A little copying is better than a little dependency"
  • "The bigger the interface, the weaker the abstraction"
  • "Make the zero value useful"
  • "Accept interfaces, return structs"

Project Structure

Standard Layout

myproject/
├── cmd/                    # Main applications
│   ├── api/
│   │   └── main.go        # API server entry point
│   └── worker/
│       └── main.go        # Background worker entry point
├── internal/              # Private application code
│   ├── app/              # Application logic
│   ├── domain/           # Domain models
│   └── platform/         # Platform-specific code
├── pkg/                   # Public libraries (reusable)
│   ├── auth/
│   └── logger/
├── api/                   # API definitions (OpenAPI, protobuf)
├── web/                   # Web assets (templates, static files)
├── scripts/               # Build, install, analysis scripts
├── configs/               # Configuration files
├── deployments/           # Deployment configs (docker, k8s)
├── test/                  # Additional test data
├── docs/                  # Documentation
├── examples/              # Example code
├── tools/                 # Supporting tools
├── vendor/                # Vendored dependencies (optional)
├── go.mod                 # Module definition
├── go.sum                 # Dependency checksums
├── Makefile              # Build automation
└── README.md

Key directories:

DirectoryPurposePublic/Private
cmd/Main applications (one per binary)Private
internal/Private application code (cannot be imported)Private
pkg/Public libraries (can be imported by others)Public
api/API definitions, schemasPublic

Rules:

  • internal/ cannot be imported by external packages (enforced by Go compiler)
  • One main.go per cmd/<app>/ subdirectory
  • Keep pkg/ minimal. Place application code in internal/ unless other modules must import it.
  • Flat is better than nested - avoid deep hierarchies

Minimal Project

For small projects:

myproject/
├── main.go
├── handler.go
├── service.go
├── repository.go
├── go.mod
├── go.sum
└── README.md

Package Naming

// ❌ BAD: Stuttering
auth.AuthService
user.UserRepository
http.HTTPClient

// ✅ GOOD: Concise
auth.Service
user.Repository
http.Client

Rules:

  • Lowercase, single word
  • No underscores or mixedCaps
  • Avoid generic names (util, common, base, helper)
  • Name by purpose, not type (auth, logger, not structs, interfaces)

Error Handling

Basic Pattern

// ✅ GOOD: Immediate error check
result, err := doSomething()
if err != nil {
    return nil, err
}

// ❌ BAD: Deferred error check
result, err := doSomething()
// ... many lines ...
if err != nil {
    return nil, err
}

Error Wrapping

// ✅ GOOD: Wrap errors with context
func processUser(id int) error {
    user, err := fetchUser(id)
    if err != nil {
        return fmt.Errorf("failed to fetch user %d: %w", id, err)
    }
    
    if err := saveUser(user); err != nil {
        return fmt.Errorf("failed to save user %d: %w", id, err)
    }
    
    return nil
}

// Check wrapped errors
err := processUser(123)
if errors.Is(err, sql.ErrNoRows) {
    // Handle specific error
}

Use %w for wrapping, %v for opaque errors.

Custom Errors

// Define sentinel errors for expected conditions
var (
    ErrUserNotFound = errors.New("user not found")
    ErrInvalidInput = errors.New("invalid input")
    ErrUnauthorized = errors.New("unauthorized")
)

// Use in code
func getUser(id int) (*User, error) {
    if id <= 0 {
        return nil, ErrInvalidInput
    }
    
    user := db.FindUser(id)
    if user == nil {
        return nil, ErrUserNotFound
    }
    
    return user, nil
}

// Check in caller
user, err := getUser(id)
if errors.Is(err, ErrUserNotFound) {
    return http.StatusNotFound
}

Error Types

// For errors with additional context
type ValidationError struct {
    Field string
    Value interface{}
    Err   error
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for field %s: %v", e.Field, e.Err)
}

func (e *ValidationError) Unwrap() error {
    return e.Err
}

// Usage
func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field: "age",
            Value: age,
            Err:   errors.New("must be non-negative"),
        }
    }
    return nil
}

Error Handling Anti-Patterns

// ❌ BAD: Ignoring errors
result, _ := doSomething()

// ❌ BAD: Generic error messages
if err != nil {
    return errors.New("error")
}

// ❌ BAD: Panic for expected errors
if err != nil {
    panic(err)  // Only use panic for truly unrecoverable situations
}

// ✅ GOOD: Explicit handling
result, err := doSomething()
if err != nil {
    return fmt.Errorf("failed to do something: %w", err)
}

Interfaces

Accept Interfaces, Return Structs

// ❌ BAD: Returning interface
func NewService() ServiceInterface {
    return &service{}
}

// ✅ GOOD: Returning struct
func NewService() *Service {
    return &Service{}
}

// ✅ GOOD: Accepting interface
func ProcessData(r io.Reader) error {
    // Can accept *os.File, bytes.Buffer, etc.
}

Small Interfaces

// ✅ GOOD: Single-method interfaces
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ✅ GOOD: Compose when needed
type ReadWriter interface {
    Reader
    Writer
}

// ❌ BAD: Large interfaces
type UserService interface {
    CreateUser(ctx context.Context, user *User) error
    GetUser(ctx context.Context, id int) (*User, error)
    UpdateUser(ctx context.Context, user *User) error
    DeleteUser(ctx context.Context, id int) error
    ListUsers(ctx context.Context, filter Filter) ([]*User, error)
    SearchUsers(ctx context.Context, query string) ([]*User, error)
    // ... 10 more methods
}

Rule: "The bigger the interface, the weaker the abstraction."

Interface Definition Location

// ✅ GOOD: Define interfaces where they're used (consumer-side)
package handler

type UserRepository interface {
    GetUser(id int) (*User, error)
}

type Handler struct {
    repo UserRepository
}

// Separate implementation
package postgres

type UserRepo struct {
    db *sql.DB
}

func (r *UserRepo) GetUser(id int) (*User, error) {
    // Implementation
}

Rule: Let consumers define interfaces, not providers.

Concurrency

Goroutines

// ✅ GOOD: Launch goroutine
go func() {
    result := compute()
    fmt.Println(result)
}()

// ✅ GOOD: Launch with closure
for _, item := range items {
    item := item  // Capture loop variable
    go func() {
        process(item)
    }()
}

// ❌ BAD: Loop variable capture
for _, item := range items {
    go func() {
        process(item)  // All goroutines will see the same 'item'
    }()
}

Channels

// Unbuffered channel (synchronous)
ch := make(chan int)

// Buffered channel (asynchronous)
ch := make(chan int, 10)

// Send and receive
ch <- 42      // Send
value := <-ch // Receive

// Close channel (sender's responsibility)
close(ch)

// Check if closed
value, ok := <-ch
if !ok {
    // Channel closed
}

// Range over channel (stops when closed)
for value := range ch {
    process(value)
}

Select Statement

select {
case msg := <-ch1:
    fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
    fmt.Println("Received from ch2:", msg)
case ch3 <- msg:
    fmt.Println("Sent to ch3")
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
default:
    fmt.Println("No channel ready")
}

Worker Pool Pattern

func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup
    
    // Start workers
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                result := processJob(job)
                results <- result
            }
        }()
    }
    
    // Wait for all workers to finish
    wg.Wait()
    close(results)
}

// Usage
jobs := make(chan Job, 100)
results := make(chan Result, 100)

go workerPool(jobs, results, 5)

// Send jobs
for _, job := range allJobs {
    jobs <- job
}
close(jobs)

// Collect results
for result := range results {
    fmt.Println(result)
}

Context for Cancellation

func doWork(ctx context.Context) error {
    // Create channel for result
    resultCh := make(chan Result)
    errCh := make(chan error)
    
    // Start work in goroutine
    go func() {
        result, err := expensiveOperation()
        if err != nil {
            errCh <- err
            return
        }
        resultCh <- result
    }()
    
    // Wait for result or cancellation
    select {
    case result := <-resultCh:
        return processResult(result)
    case err := <-errCh:
        return fmt.Errorf("operation failed: %w", err)
    case <-ctx.Done():
        return ctx.Err()
    }
}

// Usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err := doWork(ctx)
if errors.Is(err, context.DeadlineExceeded) {
    // Handle timeout
}

Mutex for Shared State

type SafeCounter struct {
    mu    sync.RWMutex
    count map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.count[key]
}

Rule: Use sync.RWMutex when reads vastly outnumber writes.

Common Concurrency Patterns

Fan-out, fan-in:

func fanOut(input <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        ch := make(chan int)
        channels[i] = ch
        go func() {
            defer close(ch)
            for v := range input {
                ch <- process(v)
            }
        }()
    }
    return channels
}

func fanIn(channels []<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    
    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                out <- v
            }
        }(ch)
    }
    
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

Pipeline:

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

// Usage
nums := gen(2, 3, 4)
squares := sq(nums)
for s := range squares {
    fmt.Println(s)
}

Testing

Table-Driven Tests

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 1, 2, 3},
        {"negative", -1, -2, -3},
        {"mixed", 1, -1, 0},
        {"zero", 0, 0, 0},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Subtests

func TestUser(t *testing.T) {
    t.Run("Create", func(t *testing.T) {
        user := NewUser("alice")
        if user.Name != "alice" {
            t.Errorf("expected name alice, got %s", user.Name)
        }
    })
    
    t.Run("Update", func(t *testing.T) {
        user := NewUser("alice")
        user.SetName("bob")
        if user.Name != "bob" {
            t.Errorf("expected name bob, got %s", user.Name)
        }
    })
}

Test Helpers

func TestMain(m *testing.M) {
    // Setup
    setup()
    
    // Run tests
    code := m.Run()
    
    // Teardown
    teardown()
    
    os.Exit(code)
}

func setup() {
    // Initialize test database, etc.
}

func teardown() {
    // Cleanup
}

Mocking with Interfaces

// Define interface
type UserRepository interface {
    GetUser(id int) (*User, error)
}

// Real implementation
type PostgresRepo struct {
    db *sql.DB
}

func (r *PostgresRepo) GetUser(id int) (*User, error) {
    // Real database query
}

// Mock for testing
type MockRepo struct {
    GetUserFunc func(id int) (*User, error)
}

func (m *MockRepo) GetUser(id int) (*User, error) {
    return m.GetUserFunc(id)
}

// Test
func TestService(t *testing.T) {
    mockRepo := &MockRepo{
        GetUserFunc: func(id int) (*User, error) {
            return &User{ID: id, Name: "test"}, nil
        },
    }
    
    service := NewService(mockRepo)
    user, err := service.GetUser(1)
    
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "test" {
        t.Errorf("expected name test, got %s", user.Name)
    }
}

Testing HTTP Handlers

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/users/1", nil)
    w := httptest.NewRecorder()
    
    handler := NewHandler()
    handler.ServeHTTP(w, req)
    
    if w.Code != http.StatusOK {
        t.Errorf("expected status 200, got %d", w.Code)
    }
    
    var user User
    if err := json.NewDecoder(w.Body).Decode(&user); err != nil {
        t.Fatalf("failed to decode response: %v", err)
    }
    
    if user.ID != 1 {
        t.Errorf("expected user ID 1, got %d", user.ID)
    }
}

Benchmarks

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

func BenchmarkAddParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(1, 2)
        }
    })
}

Run: go test -bench=.

Coverage

go test -cover
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

Common Patterns

Constructor Pattern

// ✅ GOOD: New function returns pointer
func NewService(repo Repository) *Service {
    return &Service{
        repo: repo,
    }
}

// ✅ GOOD: Functional options
type ServiceOption func(*Service)

func WithTimeout(d time.Duration) ServiceOption {
    return func(s *Service) {
        s.timeout = d
    }
}

func NewService(opts ...ServiceOption) *Service {
    s := &Service{
        timeout: 30 * time.Second, // default
    }
    
    for _, opt := range opts {
        opt(s)
    }
    
    return s
}

// Usage
svc := NewService(
    WithTimeout(1 * time.Minute),
    WithRetries(3),
)

Builder Pattern (Rare in Go)

// Only use when construction is complex
type RequestBuilder struct {
    method  string
    url     string
    headers map[string]string
    body    io.Reader
}

func NewRequestBuilder() *RequestBuilder {
    return &RequestBuilder{
        headers: make(map[string]string),
    }
}

func (b *RequestBuilder) Method(m string) *RequestBuilder {
    b.method = m
    return b
}

func (b *RequestBuilder) URL(u string) *RequestBuilder {
    b.url = u
    return b
}

func (b *RequestBuilder) Build() (*http.Request, error) {
    return http.NewRequest(b.method, b.url, b.body)
}

// Usage
req, err := NewRequestBuilder().
    Method("POST").
    URL("https://api.example.com").
    Build()

Singleton (Use Sparingly)

var (
    instance *Service
    once     sync.Once
)

func GetInstance() *Service {
    once.Do(func() {
        instance = &Service{}
    })
    return instance
}

Note: Prefer dependency injection over singletons.

Adapter Pattern

// Adapt third-party library to your interface
type Logger interface {
    Info(msg string)
    Error(msg string)
}

type ZapAdapter struct {
    logger *zap.Logger
}

func (z *ZapAdapter) Info(msg string) {
    z.logger.Info(msg)
}

func (z *ZapAdapter) Error(msg string) {
    z.logger.Error(msg)
}

Struct Patterns

Composition Over Inheritance

// ✅ GOOD: Embedding for composition
type User struct {
    ID   int
    Name string
}

type Admin struct {
    User        // Embedded
    Permissions []string
}

// Admin has all User fields
admin := Admin{
    User: User{ID: 1, Name: "alice"},
    Permissions: []string{"read", "write"},
}
fmt.Println(admin.Name) // Directly accessible

Zero Value Usability

// ✅ GOOD: Usable with zero value
type Buffer struct {
    data []byte
}

func (b *Buffer) Write(p []byte) (int, error) {
    b.data = append(b.data, p...)
    return len(p), nil
}

// Usage without initialization
var buf Buffer
buf.Write([]byte("hello")) // Works!

Private Fields, Public Methods

type Account struct {
    balance int // private
}

func (a *Account) Balance() int {
    return a.balance
}

func (a *Account) Deposit(amount int) error {
    if amount <= 0 {
        return errors.New("amount must be positive")
    }
    a.balance += amount
    return nil
}

Performance Optimization

Preallocate Slices

// ❌ BAD: Append without capacity
var items []Item
for i := 0; i < 1000; i++ {
    items = append(items, Item{})
}

// ✅ GOOD: Preallocate capacity
items := make([]Item, 0, 1000)
for i := 0; i < 1000; i++ {
    items = append(items, Item{})
}

String Builder

// ❌ BAD: String concatenation
var s string
for i := 0; i < 1000; i++ {
    s += "x"
}

// ✅ GOOD: strings.Builder
var sb strings.Builder
sb.Grow(1000) // Preallocate
for i := 0; i < 1000; i++ {
    sb.WriteString("x")
}
s := sb.String()

Avoid Allocations

// ❌ BAD: Allocates on every call
func process(items []int) []int {
    result := make([]int, 0)
    for _, item := range items {
        if item > 0 {
            result = append(result, item)
        }
    }
    return result
}

// ✅ GOOD: Reuse slice
func process(items []int, result []int) []int {
    result = result[:0] // Reset length
    for _, item := range items {
        if item > 0 {
            result = append(result, item)
        }
    }
    return result
}

Sync.Pool for Temporary Objects

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func processData(data []byte) {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufferPool.Put(buf)
    }()
    
    buf.Write(data)
    // Use buf
}

Profiling

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    
    // Your application code
}

Access profiles:

  • CPU: http://localhost:6060/debug/pprof/profile
  • Heap: http://localhost:6060/debug/pprof/heap
  • Goroutines: http://localhost:6060/debug/pprof/goroutine

Analyze:

go tool pprof http://localhost:6060/debug/pprof/profile

Common Pitfalls

Range Loop Variable Capture

// ❌ BAD: Captures loop variable
for _, item := range items {
    go func() {
        fmt.Println(item) // All goroutines print last item
    }()
}

// ✅ GOOD: Capture explicitly
for _, item := range items {
    item := item // Reassign
    go func() {
        fmt.Println(item)
    }()
}

// ✅ GOOD: Pass as parameter
for _, item := range items {
    go func(i Item) {
        fmt.Println(i)
    }(item)
}

Nil Interface Gotcha

// ❌ BAD: Interface is not nil
func returnsError() error {
    var p *MyError = nil
    return p // error interface is not nil!
}

err := returnsError()
if err != nil {
    // This block executes!
}

// ✅ GOOD: Return explicit nil
func returnsError() error {
    var p *MyError = nil
    if p == nil {
        return nil
    }
    return p
}

Defer in Loops

// ❌ BAD: Defer accumulates
for _, file := range files {
    f, err := os.Open(file)
    if err != nil {
        return err
    }
    defer f.Close() // Won't run until function returns
}

// ✅ GOOD: Close immediately or use function
for _, file := range files {
    if err := processFile(file); err != nil {
        return err
    }
}

func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    
    // Process file
    return nil
}

Slice Append Gotcha

// ❌ BAD: Original slice affected
a := []int{1, 2, 3}
b := a[:2]
b = append(b, 4)
// a is now [1, 2, 4]

// ✅ GOOD: Copy slice
a := []int{1, 2, 3}
b := make([]int, len(a[:2]))
copy(b, a[:2])
b = append(b, 4)
// a is still [1, 2, 3]

Map Concurrent Access

// ❌ BAD: Concurrent map writes cause panic
m := make(map[string]int)

go func() {
    m["key"] = 1
}()

go func() {
    m["key"] = 2
}()

// ✅ GOOD: Use sync.Map or mutex
var mu sync.Mutex
m := make(map[string]int)

go func() {
    mu.Lock()
    m["key"] = 1
    mu.Unlock()
}()

go func() {
    mu.Lock()
    m["key"] = 2
    mu.Unlock()
}()

Production Guidance

Logging

// Use structured logging
import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("user logged in",
    zap.Int("user_id", 123),
    zap.String("ip", "192.168.1.1"),
)

logger.Error("failed to process request",
    zap.Error(err),
    zap.String("request_id", reqID),
)

Configuration

// Use environment variables or config files
import "github.com/spf13/viper"

func loadConfig() (*Config, error) {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    viper.AddConfigPath("/etc/myapp/")
    
    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()
    
    if err := viper.ReadInConfig(); err != nil {
        return nil, err
    }
    
    var cfg Config
    if err := viper.Unmarshal(&cfg); err != nil {
        return nil, err
    }
    
    return &cfg, nil
}

Graceful Shutdown

func main() {
    srv := &http.Server{
        Addr:    ":8080",
        Handler: handler,
    }
    
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()
    
    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    
    log.Println("Shutting down server...")
    
    // Graceful shutdown with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }
    
    log.Println("Server exiting")
}

Database Connections

import "database/sql"

func initDB() (*sql.DB, error) {
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return nil, err
    }
    
    // Connection pool settings
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    // Verify connection
    if err := db.Ping(); err != nil {
        return nil, err
    }
    
    return db, nil
}

Context Propagation

// Always pass context as first parameter
func fetchUser(ctx context.Context, userID int) (*User, error) {
    // Use context for cancellation, timeouts, and values
    if err := ctx.Err(); err != nil {
        return nil, err
    }
    
    // Pass context to downstream calls
    return db.GetUser(ctx, userID)
}

// HTTP handler
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    
    user, err := fetchUser(ctx, 123)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

Tools and Commands

Essential Commands

# Format code
go fmt ./...

# Lint code (requires golangci-lint)
golangci-lint run

# Run tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run tests with race detector
go test -race ./...

# Run benchmarks
go test -bench=. ./...

# Build binary
go build -o myapp ./cmd/myapp

# Install dependencies
go mod download

# Tidy dependencies
go mod tidy

# Vendor dependencies
go mod vendor

# Update dependencies
go get -u ./...

# View documentation
go doc fmt.Println

# Run code
go run main.go

Useful Tools

ToolPurposeInstall
golangci-lintComprehensive lintergo install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
gofmtCode formatterBuilt-in
goimportsImport formattergo install golang.org/x/tools/cmd/goimports@latest
staticcheckStatic analysisgo install honnef.co/go/tools/cmd/staticcheck@latest
govulncheckVulnerability scannergo install golang.org/x/vuln/cmd/govulncheck@latest
dlvDebuggergo install github.com/go-delve/delve/cmd/dlv@latest

Makefile Example

.PHONY: build test lint clean

build:
	go build -o bin/myapp ./cmd/myapp

test:
	go test -v -race -cover ./...

lint:
	golangci-lint run

clean:
	rm -rf bin/

install-tools:
	go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
	go install golang.org/x/tools/cmd/goimports@latest

run:
	go run ./cmd/myapp

Rules

  • ALWAYS check errors immediately - defer error checks only when a later check is intentional and documented.
  • ALWAYS use context.Context as the first parameter of functions that do I/O or long-running work.
  • ALWAYS close resources with defer immediately after opening them (files, connections, etc.).
  • ALWAYS use go fmt before committing code - no exceptions.
  • ALWAYS run tests with -race flag to catch race conditions.
  • NEVER ignore errors - at minimum, log them; never use _ for error returns.
  • NEVER use panic for expected errors - panic only for truly unrecoverable situations (programmer errors).
  • NEVER share memory by communicating - communicate by sharing memory (use channels over mutexes when possible).
  • NEVER mutate slices/maps without understanding capacity - be aware of shared underlying arrays.
  • Accept interfaces, return structs - keep APIs flexible by accepting interfaces but returning concrete types.
  • Define interfaces where they're used, not where they're implemented (consumer-side).
  • Keep interfaces small - single-method interfaces are ideal.
  • Use table-driven tests for comprehensive test coverage with minimal code.
  • Preallocate slices when size is known - use make([]T, 0, capacity).
  • Use strings.Builder for string concatenation in loops - never concatenate strings with +.
  • Pass context to downstream functions - never create new context in the middle of a call chain.
  • Name packages by purpose, not type - auth, not structs; logger, not interfaces.
  • Avoid stuttering in names - user.Repository, not user.UserRepository.

Quick Reference

Minimal HTTP Server

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        users := []User{
            {ID: 1, Name: "Alice"},
            {ID: 2, Name: "Bob"},
        }
        
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(users)
    })
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Minimal CLI App

package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    name := flag.String("name", "World", "name to greet")
    flag.Parse()
    
    fmt.Printf("Hello, %s!\n", *name)
}

Module Initialization

# Initialize module
go mod init github.com/myorg/myapp

# Add dependency
go get github.com/pkg/errors

# Remove unused dependencies
go mod tidy

Resources

Inputs

  • Go source files, module (go.mod), and description of the feature, bug, or refactoring goal
  • Target Go version and any relevant package constraints

Outputs

  • Idiomatic Go code following standard project layout, explicit error handling, and table-driven tests

Examples

// Idiomatic error wrapping
func getUser(id int) (*User, error) {
    u, err := db.QueryUser(id)
    if err != nil {
        return nil, fmt.Errorf("getUser %d: %w", id, err)
    }
    return u, nil
}

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.