AgentSkillsCN

golang-patterns

构建稳健、高效且易于维护的 Go 应用程序所需的一系列惯用 Go 模式、最佳实践与规范

SKILL.md
--- frontmatter
name: golang-patterns
description: 견고하고 효율적이며 유지보수 가능한 Go 애플리케이션을 구축하기 위한 관용적 Go 패턴, 모범 사례 및 규칙

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
// 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
}

2. Make the Zero Value Useful

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

3. Accept Interfaces, Return Structs

go
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

Error Handling Patterns

Error Wrapping with Context

go
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

go
type ValidationError struct {
    Field   string
    Message string
}

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

var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
)

Error Checking with errors.Is and errors.As

go
if errors.Is(err, sql.ErrNoRows) {
    log.Println("No records found")
    return
}

var validationErr *ValidationError
if errors.As(err, &validationErr) {
    log.Printf("Validation error: %s", validationErr.Field)
}

Concurrency Patterns

Worker Pool

go
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

go
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)
}

Avoiding Goroutine Leaks

go
// 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

go
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

Define Interfaces Where They're Used

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

type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

Struct Design: Functional Options

go
type Option func(*Server)

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

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

Memory and Performance

go
// Preallocate slices
results := make([]Result, 0, len(items))

// Use strings.Builder for concatenation
var sb strings.Builder
sb.WriteString("hello")

// Use sync.Pool for frequent allocations
var bufferPool = sync.Pool{
    New: func() interface{} { return new(bytes.Buffer) },
}

Go Idioms Quick Reference

IdiomDescription
Accept interfaces, return structsFlexible input, concrete output
Errors are valuesFirst-class, not exceptions
Make the zero value usefulNo required init
Return earlyHandle errors first
A little copying > a little dependencyAvoid unnecessary deps
Clear is better than cleverReadability first