Claude Agent Skill · by Affaan M

Golang Patterns

A comprehensive reference for idiomatic Go development that covers the language's core philosophy and practical patterns. It emphasizes Go's fundamental princip

Install
Terminal · npx
$npx skills add https://github.com/affaan-m/everything-claude-code --skill golang-patterns
Works with Paperclip

How Golang Patterns fits into a Paperclip company.

Golang Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md674 lines
Expand
---name: golang-patternsdescription: Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.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. ```go// Good: Clear and directfunc 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 cleverfunc 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. ```go// Good: Zero value is usefultype 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 valuevar buf bytes.Bufferbuf.WriteString("hello") // Bad: Requires initializationtype BadCounter struct {    counts map[string]int // nil map will panic}``` ### 3. Accept Interfaces, Return Structs Functions should accept interface parameters and return concrete types. ```go// Good: Accepts interface, returns concrete typefunc 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 ```go// Good: Wrap errors with contextfunc 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 ```go// Define domain-specific errorstype 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 casesvar (    ErrNotFound     = errors.New("resource not found")    ErrUnauthorized = errors.New("unauthorized")    ErrInvalidInput = errors.New("invalid input"))``` ### Error Checking with errors.Is and errors.As ```gofunc 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 ```go// Bad: Ignoring error with blank identifierresult, _ := doSomething() // Good: Handle or explicitly document why it's safe to ignoreresult, 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 ```gofunc 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 ```gofunc 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 ```gofunc 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 ```goimport "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 ```go// Bad: Goroutine leak if context is cancelledfunc 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 cancellationfunc 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 ```go// Good: Single-method interfacestype 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 neededtype ReadWriteCloser interface {    Reader    Writer    Closer}``` ### Define Interfaces Where They're Used ```go// In the consumer package, not the providerpackage service // UserStore defines what this service needstype 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 ```gotype 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 ```textmyproject/├── 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 ```go// Good: Short, lowercase, no underscorespackage httppackage jsonpackage user // Bad: Verbose, mixed case, or redundantpackage httpHandlerpackage json_parserpackage userService // Redundant 'Service' suffix``` ### Avoid Package-Level State ```go// Bad: Global mutable statevar db *sql.DB func init() {    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))} // Good: Dependency injectiontype Server struct {    db *sql.DB} func NewServer(db *sql.DB) *Server {    return &Server{db: db}}``` ## Struct Design ### Functional Options Pattern ```gotype 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} // Usageserver := NewServer(":8080",    WithTimeout(60*time.Second),    WithLogger(customLogger),)``` ### Embedding for Composition ```gotype 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,    }} // Usages := NewServer(":8080")s.Log("Starting...") // Calls embedded Logger.Log``` ## Memory and Performance ### Preallocate Slices When Size is Known ```go// Bad: Grows slice multiple timesfunc processItems(items []Item) []Result {    var results []Result    for _, item := range items {        results = append(results, process(item))    }    return results} // Good: Single allocationfunc 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 ```govar 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 ```go// Bad: Creates many string allocationsfunc join(parts []string) string {    var result string    for _, p := range parts {        result += p + ","    }    return result} // Good: Single allocation with strings.Builderfunc 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 libraryfunc join(parts []string) string {    return strings.Join(parts, ",")}``` ## Go Tooling Integration ### Essential Commands ```bash# Build and rungo build ./...go run ./cmd/myapp # Testinggo test ./...go test -race ./...go test -cover ./... # Static analysisgo vet ./...staticcheck ./...golangci-lint run # Module managementgo mod tidygo mod verify # Formattinggofmt -w .goimports -w .``` ### Recommended Linter Configuration (.golangci.yml) ```yamllinters:  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 | Idiom | Description ||-------|-------------|| Accept interfaces, return structs | Functions accept interface params, return concrete types || Errors are values | Treat errors as first-class values, not exceptions || Don't communicate by sharing memory | Use channels for coordination between goroutines || Make the zero value useful | Types should work without explicit initialization || A little copying is better than a little dependency | Avoid unnecessary external dependencies || Clear is better than clever | Prioritize readability over cleverness || gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports || Return early | Handle errors first, keep happy path unindented | ## Anti-Patterns to Avoid ```go// Bad: Naked returns in long functionsfunc process() (result int, err error) {    // ... 50 lines ...    return // What is being returned?} // Bad: Using panic for control flowfunc GetUser(id string) *User {    user, err := db.Find(id)    if err != nil {        panic(err) // Don't do this    }    return user} // Bad: Passing context in structtype Request struct {    ctx context.Context // Context should be first param    ID  string} // Good: Context as first parameterfunc ProcessRequest(ctx context.Context, id string) error {    // ...} // Bad: Mixing value and pointer receiverstype Counter struct{ n int }func (c Counter) Value() int { return c.n }    // Value receiverfunc (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.