Communitygithub.com

golang-patterns

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

golang-patterns 是什麼?

這項技能提供了一套全面的慣用 Go 模式與最佳實踐,旨在協助開發者撰寫更穩健、高效且易於維護的 Go 程式碼。其核心原則著重於簡潔性、實用零值、以及「接受介面,回傳結構體」等設計哲學。技能涵蓋了錯誤處理、並發模式、介面設計、套件組織、結構體設計以及記憶體與效能最佳化等關鍵領域。透過提供豐富的程式碼範例、反模式警示以及整合 Go 工具鏈的指引,它能作為開發者在編寫或審查 Go 程式碼時的可靠參考,支援包括 Claude Code、Cursor 和 Codex 在內的多種 AI 代理平台。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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 // 空的 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("No records found")
        return
    }

    // 檢查錯誤型別
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation error on field %s: %s",
            validationErr.Field, validationErr.Message)
        return
    }

    // 未知錯誤
    log.Printf("Unexpected error: %v", err)
}

絕不忽略錯誤

// 差:使用空白識別字忽略錯誤
result, _ := doSomething()

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

// 可接受:當錯誤真的無關緊要時(很少見)
_ = writer.Close() // 盡力清理,錯誤會在其他地方記錄

並發模式

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

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

優雅關機

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

用於協調 Goroutine 的 errgroup

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("Starting...") // 呼叫嵌入的 Logger.Log

記憶體與效能

在已知大小時預先分配切片

// 差:多次增長切片
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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相關技能