Communitygithub.com

golang-patterns

慣用的 Go 模式、最佳實踐與慣例,用於構建穩健、高效且易於維護的 Go 應用程式。在編寫或審查 Go 程式碼,且對其慣用結構或慣例有疑問時使用。

golang-patterns 是什麼?

這項技能提供了一份全面的指南,涵蓋慣用的 Go 開發模式、最佳實踐和約定。它支援使用 Go 編寫、審查或重構程式碼的開發者,並專注於 Go 的核心原則:簡潔、清晰、零值有用以及基於介面的設計。該技能涵蓋了關鍵的錯誤處理模式(如錯誤包裝、自訂錯誤型別)、並發模式(工作者池、情境取消、優雅關閉和 errgroup 用法)、介面設計(小型聚焦介面、按需定義)以及套件組織。它還涵蓋了結構設計模式(功能選項、嵌入)、記憶體和性能最佳化技術(預分配、sync.Pool、strings.Builder),以及 Go 工具鏈集成,包括 go vet 和 golangci-lint 等工具的配置。文件強調避免反模式,例如在長函式中使用裸返回、用 panic 進行流程控制,以及將 context.Context 儲存在結構體中。其目標是幫助開發者編寫可預測、一致且易於理解的程式碼,遵循「清晰勝於巧妙」的格言。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/golang-patterns

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Go 開發模式

針對構建穩健、高效且易於維護的應用程式的慣用 Go 模式與最佳實踐。

使用時機

  • 編寫新的 Go 程式碼時
  • 審查 Go 程式碼時
  • 重構現有的 Go 程式碼時
  • 設計 Go 套件/模組時

核心原則

1. 簡潔與清晰

Go 偏好簡潔而非巧妙。程式碼應清晰易懂。

// 好:清晰直接
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
}

// 壞:過於巧妙
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. 讓零值變得有用

設計型別,使其零值無需初始化即可立即使用。

// 好:零值有用
type Counter struct {
    mu    sync.Mutex
    count int // 零值為 0,可直接使用
}

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

// 好:bytes.Buffer 可直接使用零值
var buf bytes.Buffer
buf.WriteString("hello")

// 壞:需要初始化
type BadCounter struct {
    counts map[string]int // nil map 會導致 panic
}

3. 接受介面,返回結構

函式應接受介面參數並返回具體型別。

// 好:接受介面,返回具體型別
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

// 壞:返回介面(不必要地隱藏實作細節)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}

錯誤處理模式

帶上下文的錯誤包裝

// 好:用上下文包裝錯誤
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
}

自訂錯誤型別

// 定義領域特定的錯誤
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")
    ErrInvalidInput = errors.New("invalid input")
)

使用 errors.Is 和 errors.As 檢查錯誤

func HandleError(err error) {
    // 檢查特定錯誤
    if errors.Is(err, sql.ErrNoRows) {
        log.Println("未找到記錄")
        return
    }

    // 檢查錯誤型別
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("驗證錯誤,欄位 %s:%s",
            validationErr.Field, validationErr.Message)
        return
    }

    // 未知錯誤
    log.Printf("意外的錯誤:%v", err)
}

絕不忽略錯誤

// 壞:使用空白識別符號忽略錯誤
result, _ := doSomething()

// 好:處理或明確記錄為何可以安全地忽略
result, err := doSomething()
if err != nil {
    return err
}

// 可接受:當錯誤確實不重要時(罕見)
_ = writer.Close() // 盡最大努力清理,錯誤在其他地方記錄

並發模式

工作者池

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

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("建立請求:%w", err)
    }

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

    return io.ReadAll(resp.Body)
}

優雅關閉

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

    <-quit
    log.Println("正在關閉伺服器...")

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

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("伺服器被迫關閉:%v", err)
    }

    log.Println("伺服器已退出")
}

errgroup 用於協調的 Goroutine

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 // 捕獲迴圈變數
        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
}

避免 Goroutine 洩漏

// 壞:如果 context 被取消,會發生 Goroutine 洩漏
func leakyFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte)
    go func() {
        data, _ := fetch(url)
        ch <- data // 如果沒有接收者,會永久阻塞
    }()
    return ch
}

// 好:妥善處理取消
func safeFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte, 1) // 緩衝通道
    go func() {
        data, err := fetch(url)
        if err != nil {
            return
        }
        select {
        case ch <- data:
        case <-ctx.Done():
        }
    }()
    return ch
}

介面設計

小型、聚焦的介面

// 好:單方法介面
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
}

// 根據需要組合介面
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

在使用處定義介面

// 在消費者套件中定義,而非提供者套件
package service

// UserStore 定義此服務需要的內容
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// 具體實作可以在其他套件中
// 它不需要知道這個介面

使用型別斷言的選擇性行為

type Flusher interface {
    Flush() error
}

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

    // 如果支援,則進行 Flush
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

套件組織

標準專案佈局

myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # 進入點
├── internal/
│   ├── handler/              # HTTP 處理器
│   ├── service/              # 業務邏輯
│   ├── repository/           # 資料存取
│   └── config/               # 配置
├── pkg/
│   └── client/               # 公開 API 用戶端
├── api/
│   └── v1/                   # API 定義 (proto, OpenAPI)
├── testdata/                 # 測試用數據
├── go.mod
├── go.sum
└── Makefile

套件命名

// 好:簡短、小寫、無底線
package http
package json
package user

// 壞:冗長、混合大小寫或冗餘
package httpHandler
package json_parser
package userService // 冗餘的 'Service' 後綴

避免套件級別狀態

// 壞:全域可變狀態
var db *sql.DB

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

// 好:依賴注入
type Server struct {
    db *sql.DB
}

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

結構設計

功能選項模式

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, // 預設值
        logger:  log.Default(),    // 預設值
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

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

用於組合的嵌入

type Logger struct {
    prefix string
}

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

type Server struct {
    *Logger // 嵌入 - Server 獲得 Log 方法
    addr    string
}

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

// 使用
s := NewServer(":8080")
s.Log("正在啟動...") // 呼叫嵌入的 Logger.Log

記憶體與效能

已知大小時預先分配 Slice

// 壞:多次擴容 slice
func processItems(items []Item) []Result {
    var results []Result
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

// 好:單次分配
func processItems(items []Item) []Result {
    results := make([]Result, 0, len(items))
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

使用 sync.Pool 處理頻繁分配

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)
    // 處理...
    return buf.Bytes()
}

避免在迴圈中進行字串拼接

// 壞:產生大量字串分配
func join(parts []string) string {
    var result string
    for _, p := range parts {
        result += p + ","
    }
    return result
}

// 好:使用 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()
}

// 最佳:使用標準函式庫
func join(parts []string) string {
    return strings.Join(parts, ",")
}

Go 工具鏈整合

基本指令

# 建置與執行
go build ./...
go run ./cmd/myapp

# 測試
go test ./...
go test -race ./...
go test -cover ./...

# 靜態分析
go vet ./...
staticcheck ./...
golangci-lint run

# 模組管理
go mod tidy
go mod verify

# 格式化
gofmt -w .
goimports -w .

推薦的 Linter 配置 (.golangci.yml)

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

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    enable:
      - shadow

issues:
  exclude-use-default: false

快速參考:Go 慣用語

慣用語說明
接受介面,返回結構函式接受介面參數,返回具體型別
錯誤是值將錯誤視為一等公民,而非例外
不要透過共享記憶體來通訊使用通道協調 goroutine
讓零值變得有用型別應無需顯式初始化即可工作
少量複製勝過少量依賴避免不必要的外部依賴
清晰勝於巧妙優先考慮可讀性而非巧妙性
gofmt 不是每個人的最愛,但卻是每個人的朋友始終使用 gofmt/goimports 格式化
提前返回先處理錯誤,保持快樂路徑不縮排

應避免的反模式

// 壞:長函數中的裸返回
func process() (result int, err error) {
    // ... 50 行 ...
    return // 返回了什麼?
}

// 壞:使用 panic 進行流程控制
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err) // 不要這樣做
    }
    return user
}

// 壞:在結構體中傳遞 context
type Request struct {
    ctx context.Context // Context 應作為第一個參數
    ID  string
}

// 好:Context 作為第一個參數
func ProcessRequest(ctx context.Context, id string) error {
    // ...
}

// 壞:混合使用值接收者和指針接收者
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }    // 值接收者
func (c *Counter) Increment() { c.n++ }        // 指針接收者
// 選擇一種風格並保持一致

記住:Go 程式碼應該以最好的方式顯得無聊——可預測、一致且易於理解。如有疑問,保持簡單。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相關技能