PluginBench
Skill
Pass
Audit score 90

golang-patterns

affaan-m/everything-claude-code

Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable applications.

What is golang-patterns?

A reference guide for writing idiomatic Go code that emphasizes simplicity, clarity, and correctness. Use this when writing new Go code, reviewing implementations, or refactoring existing packages to align with Go conventions and proven patterns.

  • Apply core Go principles: simplicity, useful zero values, and interface design
  • Handle errors idiomatically with wrapping, custom types, and proper checking
  • Implement concurrency patterns including worker pools, context-based cancellation, and graceful shutdown
  • Design small, focused interfaces that compose well and are defined where used
  • Organize packages following standard Go project layout conventions
  • Avoid common pitfalls like goroutine leaks, ignored errors, and overly clever code

How to install golang-patterns

npx skills add https://github.com/affaan-m/everything-claude-code --skill golang-patterns
Claude Code
Cursor
Windsurf
Cline

How to use golang-patterns

  1. 1.Review the core principles section when starting new code or refactoring
  2. 2.Reference error handling patterns when designing error types and checking logic
  3. 3.Apply concurrency patterns when implementing goroutines, channels, or timeouts
  4. 4.Use interface design guidelines when defining contracts between packages
  5. 5.Follow package organization structure for new projects or modules

Use cases

Good for
  • Writing new Go services or applications from scratch
  • Reviewing Go code to ensure it follows idiomatic patterns
  • Refactoring existing Go code to improve clarity and maintainability
  • Designing package structures and interfaces for new modules
  • Implementing concurrent operations safely with context and goroutine management
Who it's for
  • Go developers building new applications
  • Teams establishing Go coding standards
  • Developers refactoring or reviewing Go codebases
  • Architects designing Go package and module structures

golang-patterns FAQ

When should I use custom error types vs. sentinel errors?

Use sentinel errors (errors.New) for common, reusable error conditions. Use custom error types when you need to attach additional context or fields that callers might need to inspect.

Why does Go prefer accepting interfaces and returning structs?

Interfaces define minimal contracts and are flexible for testing. Returning concrete types gives callers clear, predictable behavior and avoids hiding implementation details unnecessarily.

How do I prevent goroutine leaks?

Always respect context cancellation, use buffered channels when returning from goroutines, and ensure goroutines can exit when their work is done or context is cancelled.

What is the zero value and why does it matter?

The zero value is what a variable holds when declared but not initialized. Good Go design makes zero values immediately usable (e.g., sync.Mutex, bytes.Buffer) to reduce boilerplate and initialization errors.

Should I define interfaces in the provider package or consumer package?

Define interfaces in the consumer package where they're needed. This keeps dependencies clear and allows concrete implementations to exist independently without knowing about the interface.

Full instructions (SKILL.md)

Source of truth, from affaan-m/everything-claude-code.


name: golang-patterns description: Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications. metadata: origin: ECC

Go Development Patterns

Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.

When to Activate

  • Writing new Go code
  • Reviewing Go code
  • Refactoring existing Go code
  • Designing Go packages/modules

Core Principles

1. Simplicity and Clarity

Go favors simplicity over cleverness. Code should be obvious and easy to read.

// Good: Clear and direct
func GetUser(id string) (*User, error) {
    user, err := db.FindUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return user, nil
}

// Bad: Overly clever
func GetUser(id string) (*User, error) {
    return func() (*User, error) {
        if u, e := db.FindUser(id); e == nil {
            return u, nil
        } else {
            return nil, e
        }
    }()
}

2. Make the Zero Value Useful

Design types so their zero value is immediately usable without initialization.

// Good: Zero value is useful
type Counter struct {
    mu    sync.Mutex
    count int // zero value is 0, ready to use
}

func (c *Counter) Inc() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// Good: bytes.Buffer works with zero value
var buf bytes.Buffer
buf.WriteString("hello")

// Bad: Requires initialization
type BadCounter struct {
    counts map[string]int // nil map will panic
}

3. Accept Interfaces, Return Structs

Functions should accept interface parameters and return concrete types.

// Good: Accepts interface, returns concrete type
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

// Bad: Returns interface (hides implementation details unnecessarily)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}

Error Handling Patterns

Error Wrapping with Context

// Good: Wrap errors with context
func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }

    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("parse config %s: %w", path, err)
    }

    return &cfg, nil
}

Custom Error Types

// Define domain-specific errors
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Sentinel errors for common cases
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

Error Checking with errors.Is and errors.As

func HandleError(err error) {
    // Check for specific error
    if errors.Is(err, sql.ErrNoRows) {
        log.Println("No records found")
        return
    }

    // Check for error type
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation error on field %s: %s",
            validationErr.Field, validationErr.Message)
        return
    }

    // Unknown error
    log.Printf("Unexpected error: %v", err)
}

Never Ignore Errors

// Bad: Ignoring error with blank identifier
result, _ := doSomething()

// Good: Handle or explicitly document why it's safe to ignore
result, err := doSomething()
if err != nil {
    return err
}

// Acceptable: When error truly doesn't matter (rare)
_ = writer.Close() // Best-effort cleanup, error logged elsewhere

Concurrency Patterns

Worker Pool

func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- process(job)
            }
        }()
    }

    wg.Wait()
    close(results)
}

Context for Cancellation and Timeouts

func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

Graceful Shutdown

func GracefulShutdown(server *http.Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    <-quit
    log.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}

errgroup for Coordinated Goroutines

import "golang.org/x/sync/errgroup"

func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([][]byte, len(urls))

    for i, url := range urls {
        i, url := i, url // Capture loop variables
        g.Go(func() error {
            data, err := FetchWithTimeout(ctx, url)
            if err != nil {
                return err
            }
            results[i] = data
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

Avoiding Goroutine Leaks

// Bad: Goroutine leak if context is cancelled
func leakyFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte)
    go func() {
        data, _ := fetch(url)
        ch <- data // Blocks forever if no receiver
    }()
    return ch
}

// Good: Properly handles cancellation
func safeFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte, 1) // Buffered channel
    go func() {
        data, err := fetch(url)
        if err != nil {
            return
        }
        select {
        case ch <- data:
        case <-ctx.Done():
        }
    }()
    return ch
}

Interface Design

Small, Focused 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)
}

type Closer interface {
    Close() error
}

// Compose interfaces as needed
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Define Interfaces Where They're Used

// In the consumer package, not the provider
package service

// UserStore defines what this service needs
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// Concrete implementation can be in another package
// It doesn't need to know about this interface

Optional Behavior with Type Assertions

type Flusher interface {
    Flush() error
}

func WriteAndFlush(w io.Writer, data []byte) error {
    if _, err := w.Write(data); err != nil {
        return err
    }

    // Flush if supported
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

Package Organization

Standard Project Layout

myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # Entry point
├── internal/
│   ├── handler/              # HTTP handlers
│   ├── service/              # Business logic
│   ├── repository/           # Data access
│   └── config/               # Configuration
├── pkg/
│   └── client/               # Public API client
├── api/
│   └── v1/                   # API definitions (proto, OpenAPI)
├── testdata/                 # Test fixtures
├── go.mod
├── go.sum
└── Makefile

Package Naming

// Good: Short, lowercase, no underscores
package http
package json
package user

// Bad: Verbose, mixed case, or redundant
package httpHandler
package json_parser
package userService // Redundant 'Service' suffix

Avoid Package-Level State

// Bad: Global mutable state
var db *sql.DB

func init() {
    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}

// Good: Dependency injection
type Server struct {
    db *sql.DB
}

func NewServer(db *sql.DB) *Server {
    return &Server{db: db}
}

Struct Design

Functional Options Pattern

type Server struct {
    addr    string
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func WithLogger(l *log.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(addr string, opts ...Option) *Server {
    s := &Server{
        addr:    addr,
        timeout: 30 * time.Second, // default
        logger:  log.Default(),    // default
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer(":8080",
    WithTimeout(60*time.Second),
    WithLogger(customLogger),
)

Embedding for Composition

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

type Server struct {
    *Logger // Embedding - Server gets Log method
    addr    string
}

func NewServer(addr string) *Server {
    return &Server{
        Logger: &Logger{prefix: "SERVER"},
        addr:   addr,
    }
}

// Usage
s := NewServer(":8080")
s.Log("Starting...") // Calls embedded Logger.Log

Memory and Performance

Preallocate Slices When Size is Known

// Bad: Grows slice multiple times
func processItems(items []Item) []Result {
    var results []Result
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

// Good: Single allocation
func processItems(items []Item) []Result {
    results := make([]Result, 0, len(items))
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

Use sync.Pool for Frequent Allocations

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

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

    buf.Write(data)
    // Process...
    return buf.Bytes()
}

Avoid String Concatenation in Loops

// Bad: Creates many string allocations
func join(parts []string) string {
    var result string
    for _, p := range parts {
        result += p + ","
    }
    return result
}

// Good: Single allocation with strings.Builder
func join(parts []string) string {
    var sb strings.Builder
    for i, p := range parts {
        if i > 0 {
            sb.WriteString(",")
        }
        sb.WriteString(p)
    }
    return sb.String()
}

// Best: Use standard library
func join(parts []string) string {
    return strings.Join(parts, ",")
}

Go Tooling Integration

Essential Commands

# Build and run
go build ./...
go run ./cmd/myapp

# Testing
go test ./...
go test -race ./...
go test -cover ./...

# Static analysis
go vet ./...
staticcheck ./...
golangci-lint run

# Module management
go mod tidy
go mod verify

# Formatting
gofmt -w .
goimports -w .

Recommended Linter Configuration (.golangci.yml)

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - unused
    - gofmt
    - goimports
    - misspell
    - unconvert
    - unparam

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    check-shadowing: true

issues:
  exclude-use-default: false

Quick Reference: Go Idioms

IdiomDescription
Accept interfaces, return structsFunctions accept interface params, return concrete types
Errors are valuesTreat errors as first-class values, not exceptions
Don't communicate by sharing memoryUse channels for coordination between goroutines
Make the zero value usefulTypes should work without explicit initialization
A little copying is better than a little dependencyAvoid unnecessary external dependencies
Clear is better than cleverPrioritize readability over cleverness
gofmt is no one's favorite but everyone's friendAlways format with gofmt/goimports
Return earlyHandle errors first, keep happy path unindented

Anti-Patterns to Avoid

// Bad: Naked returns in long functions
func process() (result int, err error) {
    // ... 50 lines ...
    return // What is being returned?
}

// Bad: Using panic for control flow
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err) // Don't do this
    }
    return user
}

// Bad: Passing context in struct
type Request struct {
    ctx context.Context // Context should be first param
    ID  string
}

// Good: Context as first parameter
func ProcessRequest(ctx context.Context, id string) error {
    // ...
}

// Bad: Mixing value and pointer receivers
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }    // Value receiver
func (c *Counter) Increment() { c.n++ }        // Pointer receiver
// Pick one style and be consistent

Remember: Go code should be boring in the best way - predictable, consistent, and easy to understand. When in doubt, keep it simple.