gopilot

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Engineering

Go 工程实践

Design Guidelines

设计准则

  • Keep things simple.
    • Prefer stateless, pure functions over stateful structs with methods if no state is needed to solve the problem.
    • Prefer synchronous code to concurrent code, an obvious concurrency pattern applies.
    • Prefer simple APIs and small interfaces.
    • Make the zero value useful:
      bytes.Buffer
      and
      sync.Mutex
      work without init. When zero values compose, there's less API.
    • Avoid external dependencies. A little copying is better than a little dependency.
  • Clear is better than clever. Maintainability and readibility are important.
  • Don't just check errors, handle them gracefully.
  • Design the architecture, name the components, document the details: Good names carry the weight of expressing your design. If names are good, the design is clear on the page.
  • Reduce nesting by using guard clauses.
    • Invert conditions for early return
    • In loops, use early
      continue
      for invalid items instead of nesting
    • Max 2-3 nesting levels
    • Extract helpers for long methods
  • 保持简洁。
    • 若无需状态即可解决问题,优先使用无状态纯函数,而非带方法的有状态结构体。
    • 优先使用同步代码,仅在明确需要时使用并发模式。
    • 优先设计简洁的API和小型接口。
    • 让零值具备可用性:
      bytes.Buffer
      sync.Mutex
      无需初始化即可工作。当零值可组合时,API会更简洁。
    • 避免外部依赖。少量的复制操作比引入依赖更优。
  • 清晰胜于巧妙。可维护性和可读性至关重要。
  • 不要仅检查错误,还要优雅地处理它们。
  • 先设计架构,再命名组件,最后记录细节:好的命名能清晰传达设计意图。如果命名得当,设计在代码中自然一目了然。
  • 使用卫语句减少嵌套层级。
    • 反转条件以提前返回
    • 在循环中,对无效项使用提前
      continue
      而非嵌套
    • 最多保留2-3层嵌套
    • 为长方法提取辅助函数

Code Style

代码风格

  • Avoid stuttering:
    http.Client
    not
    http.HTTPClient
  • Getters:
    Foo()
    not
    GetFoo()
  • Receiver: short (1-2 chars), consistent across type methods
  • Must
    prefix for panicking functions
  • Enums: use
    iota + 1
    to start at one, distinguishing intentional values from zero default
  • 避免重复命名:使用
    http.Client
    而非
    http.HTTPClient
  • Getter方法:使用
    Foo()
    而非
    GetFoo()
  • 接收器名称:简短(1-2个字符),且在类型的所有方法中保持一致
  • 会触发panic的函数使用
    Must
    前缀
  • 枚举类型:使用
    iota + 1
    从1开始,区分有意设置的值与零默认值

Error Handling

错误处理

  • Errors are values. Design APIs around that.
  • Wrap with context:
    fmt.Errorf("get config %s: %w", name, err)
  • Sentinel errors:
    var ErrNotFound = errors.New("not found")
  • Check with
    errors.Is(err, ErrNotFound)
    or
    errors.As(err, &target)
    , or use generic
    errors.AsType[T]
    (Go 1.26+)
  • Static errors: prefer
    errors.New
    over
    fmt.Errorf
    without formatting
  • Join multiple errors:
    err := errors.Join(err1, err2, err3)
    (Go 1.20+)
  • Error strings: lowercase, no punctuation
  • Avoid "failed to" prefixes - they accumulate through the stack (
    "connect: %w"
    not
    "failed to connect: %w"
    )
  • CRITICAL: Always check errors immediately before using returned values (Go 1.25 fixed compiler bug that could delay nil checks)
  • 错误是值。围绕这一点设计API。
  • 携带上下文包装错误:
    fmt.Errorf("get config %s: %w", name, err)
  • 哨兵错误:
    var ErrNotFound = errors.New("not found")
  • 使用
    errors.Is(err, ErrNotFound)
    errors.As(err, &target)
    检查错误,或使用泛型
    errors.AsType[T]
    (Go 1.26+)
  • 静态错误:优先使用
    errors.New
    而非无格式化的
    fmt.Errorf
  • 合并多个错误:
    err := errors.Join(err1, err2, err3)
    (Go 1.20+)
  • 错误字符串:小写,无标点符号
  • 避免使用"failed to"前缀——该前缀会在调用栈中重复出现(使用
    "connect: %w"
    而非
    "failed to connect: %w"
  • 关键注意事项:在使用返回值前务必立即检查错误(Go 1.25修复了可能延迟nil检查的编译器bug)

Error Strategy (Opaque Errors First)

错误策略(优先使用不透明错误)

Prefer opaque error handling: treat errors as opaque values, don't inspect internals. This minimizes coupling.
Three strategies in order of preference:
  1. Opaque errors (preferred): return and wrap errors without exposing type or value. Callers only check
    err != nil
    .
  2. Sentinel errors (
    var ErrNotFound = errors.New(...)
    ): use sparingly for expected conditions callers must distinguish. They become public API.
  3. Error types (
    type NotFoundError struct{...}
    ): use when callers need structured context. Also public API — avoid when opaque or sentinel suffices.
优先采用不透明错误处理:将错误视为不透明值,不检查其内部细节。这能最小化耦合。
按优先级排序的三种策略:
  1. 不透明错误(首选):返回并包装错误,不暴露其类型或值。调用者仅需检查
    err != nil
  2. 哨兵错误
    var ErrNotFound = errors.New(...)
    ):仅在调用者必须区分预期条件时谨慎使用。它们属于公共API的一部分。
  3. 错误类型
    type NotFoundError struct{...}
    ):仅在调用者需要结构化上下文时使用。同样属于公共API——当不透明错误或哨兵错误足够时应避免使用。

Assert Behavior, Not Type

断言行为而非类型

When you must inspect errors beyond
errors.Is
/
errors.As
, assert on behavior interfaces instead of concrete types:
go
type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    te, ok := err.(temporary)
    return ok && te.Temporary()
}
当必须通过
errors.Is
/
errors.As
之外的方式检查错误时,应断言行为接口而非具体类型:
go
type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    te, ok := err.(temporary)
    return ok && te.Temporary()
}

Handle Errors Once

错误仅处理一次

An error should be handled exactly once. Handling = logging, returning, or degrading gracefully. Never log and return — duplicates without useful context.
go
// Bad: logs AND returns
if err != nil {
    log.Printf("connect failed: %v", err)
    return fmt.Errorf("connect: %w", err)
}

// Good: wrap and return; let the top-level caller log
if err != nil {
    return fmt.Errorf("connect to %s: %w", addr, err)
}
Wrap with context at each layer; log/handle only at the application boundary.
一个错误应仅被处理一次。处理包括日志记录、返回或优雅降级。绝不要既记录又返回——这会产生无有用上下文的重复信息。
go
// 错误示例:既记录又返回
if err != nil {
    log.Printf("connect failed: %v", err)
    return fmt.Errorf("connect: %w", err)
}

// 正确示例:包装后返回;由顶层调用者负责日志记录
if err != nil {
    return fmt.Errorf("connect to %s: %w", addr, err)
}
在每个层级都携带上下文包装错误;仅在应用边界处记录/处理错误。

Context with Cause (Go 1.21+)

带原因的上下文(Go 1.21+)

Propagate cancellation reasons through context:
go
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))

// Later retrieve the cause
if cause := context.Cause(ctx); cause != nil {
    log.Printf("context cancelled: %v", cause)
}
通过上下文传播取消原因:
go
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))

// 后续获取原因
if cause := context.Cause(ctx); cause != nil {
    log.Printf("context cancelled: %v", cause)
}

Generics

泛型

  • Type parameters:
    func Min[T cmp.Ordered] (a, b T) T
  • Use
    comparable
    for map keys,
    cmp.Ordered
    for sortable types
  • Custom constraints:
    type Number interface { ~int | ~int64 | ~float64 }
  • Generic type alias (Go 1.24+):
    type Set[T comparable] = map[T]struct{}
  • Self-referential constraints (Go 1.26+):
    type Adder[A Adder[A]] interface { Add(A) A }
  • Prefer concrete types when generics add no value
  • Use
    any
    sparingly; prefer specific constraints
  • 类型参数:
    func Min[T cmp.Ordered] (a, b T) T
  • 使用
    comparable
    作为映射键的约束,
    cmp.Ordered
    作为可排序类型的约束
  • 自定义约束:
    type Number interface { ~int | ~int64 | ~float64 }
  • 泛型类型别名(Go 1.24+):
    type Set[T comparable] = map[T]struct{}
  • 自引用约束(Go 1.26+):
    type Adder[A Adder[A]] interface { Add(A) A }
  • 当泛型无法带来价值时,优先使用具体类型
  • 谨慎使用
    any
    ;优先使用特定约束

Built-in Functions

内置函数

  • min(a, b, c)
    and
    max(a, b, c)
    - compute smallest/largest values (Go 1.21+)
  • clear(m)
    - delete all map entries;
    clear(s)
    - zero all slice elements (Go 1.21+)
  • new(expr)
    - allocate and initialize with value (Go 1.26+):
    ptr := new(computeValue())
  • min(a, b, c)
    max(a, b, c)
    - 计算最小值/最大值(Go 1.21+)
  • clear(m)
    - 删除映射的所有条目;
    clear(s)
    - 将切片的所有元素置零(Go 1.21+)
  • new(expr)
    - 分配内存并使用指定值初始化(Go 1.26+):
    ptr := new(computeValue())

Testing

测试

Table-Driven Tests

表驱动测试

go
func TestFoo(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    string
        wantErr error
    }{
        {"EmptyInput", "", "", ErrEmpty},
        {"ValidInput", "hello", "HELLO", nil},
    }
    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()
            got, err := Foo(tc.input)
            if tc.wantErr != nil {
                require.ErrorIs(t, err, tc.wantErr)
                return
            }
            require.NoError(t, err)
            require.Equal(t, tc.want, got)
        })
    }
}
go
func TestFoo(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    string
        wantErr error
    }{
        {"EmptyInput", "", "", ErrEmpty},
        {"ValidInput", "hello", "HELLO", nil},
    }
    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()
            got, err := Foo(tc.input)
            if tc.wantErr != nil {
                require.ErrorIs(t, err, tc.wantErr)
                return
            }
            require.NoError(t, err)
            require.Equal(t, tc.want, got)
        })
    }
}

Benchmarks (Go 1.24+)

基准测试(Go 1.24+)

go
func BenchmarkFoo(b *testing.B) {
    for b.Loop() {  // Cleaner than for i := 0; i < b.N; i++
        Foo()
    }
}
Benefits: single execution per
-count
, prevents compiler optimizations away.
go
func BenchmarkFoo(b *testing.B) {
    for b.Loop() {  // 比for i := 0; i < b.N; i++更简洁
        Foo()
    }
}
优势:每次
-count
执行一次;避免被编译器优化掉。

Assertions

断言

  • Use the testify library for conciseness. Use
    require
    for fatal assertions,
    assert
    for non-fatal
  • require.ErrorIs
    for sentinel errors (not string matching)
  • require.JSONEq
    /
    require.YAMLEq
    for semantic comparison
  • Use
    testdata/
    folders for expected values
  • Use
    embed.FS
    for test data files
  • 使用testify库以简化代码。使用
    require
    进行致命断言,
    assert
    进行非致命断言
  • 对哨兵错误使用
    require.ErrorIs
    (而非字符串匹配)
  • 使用
    require.JSONEq
    /
    require.YAMLEq
    进行语义比较
  • 使用
    testdata/
    文件夹存储预期值
  • 使用
    embed.FS
    加载测试数据文件

Practices

实践

  • t.Helper()
    in helper functions
  • t.Cleanup()
    for resource cleanup
  • t.Context()
    for test-scoped context (Go 1.24+)
  • t.Chdir()
    for temp directory changes (Go 1.24+)
  • t.ArtifactDir()
    for test output files (Go 1.26+)
  • t.Parallel()
    for independent tests
  • -race
    flag always
  • Don't test stdlib; test YOUR code
  • Bug fix → add regression test first
  • Concurrent code needs concurrent tests
  • 在辅助函数中使用
    t.Helper()
  • 使用
    t.Cleanup()
    进行资源清理
  • 使用
    t.Context()
    获取测试作用域的上下文(Go 1.24+)
  • 使用
    t.Chdir()
    切换到临时目录(Go 1.24+)
  • 使用
    t.ArtifactDir()
    存储测试输出文件(Go 1.26+)
  • 对独立测试使用
    t.Parallel()
  • 始终启用
    -race
    标志
  • 不要测试标准库;只测试你自己的代码
  • 修复bug前先添加回归测试
  • 并发代码需要并发测试

Testing Concurrent Code with synctest (Go 1.25+)

使用synctest测试并发代码(Go 1.25+)

testing/synctest
creates an isolated "bubble" with virtualized time. The fake clock advances automatically when all goroutines in the bubble are blocked.
go
import "testing/synctest"

func TestPeriodicWorker(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        var count atomic.Int32
        go func() {
            for {
                time.Sleep(time.Second)
                count.Add(1)
            }
        }()

        // Fake clock advances 5s instantly (no real waiting)
        time.Sleep(5 * time.Second)
        synctest.Wait() // wait for all goroutines to settle
        require.Equal(t, int32(5), count.Load())
    })
}
Key rules:
  • synctest.Wait()
    blocks until all bubble goroutines are idle
  • time.Sleep
    ,
    time.After
    ,
    time.NewTimer
    ,
    time.NewTicker
    all use the fake clock inside the bubble
  • Goroutines spawned inside the bubble belong to it; goroutines outside are unaffected
  • Blocking on I/O or syscalls does NOT advance the clock — only channel ops, sleeps, and sync primitives do
  • Prefer
    synctest.Test
    over manual
    timeNow
    mocking for new code
testing/synctest
创建一个隔离的"沙箱",使用虚拟化时间。当沙箱中的所有goroutine都阻塞时,虚拟时钟会自动推进。
go
import "testing/synctest"

func TestPeriodicWorker(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        var count atomic.Int32
        go func() {
            for {
                time.Sleep(time.Second)
                count.Add(1)
            }
        }()

        // 虚拟时钟立即推进5秒(无需实际等待)
        time.Sleep(5 * time.Second)
        synctest.Wait() // 等待所有goroutines进入空闲状态
        require.Equal(t, int32(5), count.Load())
    })
}
核心规则:
  • synctest.Wait()
    会阻塞,直到沙箱中的所有goroutine都处于空闲状态
  • 在沙箱内,
    time.Sleep
    time.After
    time.NewTimer
    time.NewTicker
    均使用虚拟时钟
  • 在沙箱内启动的goroutine属于该沙箱;沙箱外的goroutine不受影响
  • 阻塞在I/O或系统调用不会推进时钟——只有通道操作、睡眠和同步原语会推进时钟
  • 对于新代码,优先使用
    synctest.Test
    而非手动模拟
    timeNow

Concurrency

并发

Design

设计

  • Don't communicate by sharing memory, share memory by communicating
  • Channels orchestrate; mutexes serialize
  • Use
    errgroup.WithContext
    to launch goroutines that return errors;
    g.Wait()
    returns first error
  • sync.WaitGroup.Go()
    (Go 1.25+): cleaner goroutine launching
    go
    var wg sync.WaitGroup
    wg.Go(func() { work() })  // Combines Add(1) + go
    wg.Wait()
  • Make goroutine lifetime explicit; document when/why they exit
  • Avoid goroutine leaks (blocked on unreachable channels); use goroutine leak profile to detect (
    GOEXPERIMENT=goroutineleakprofile
    , Go 1.26+)
  • Use
    context.Context
    for cancellation
  • Subscribe to
    context.Done()
    for graceful shutdown
  • Prefer synchronous functions; let callers add concurrency if needed
  • sync.Mutex
    /
    RWMutex
    for protection; zero value is ready to use
  • RWMutex
    when reads >> writes
  • Pointer receivers with mutexes (prevent struct copy)
  • Keep critical sections small; avoid locks across I/O
  • sync.Once
    for one-time initialization; helpers:
    sync.OnceFunc()
    ,
    sync.OnceValue()
    ,
    sync.OnceValues()
    (Go 1.21+)
  • atomic
    for primitive counters
  • Don't embed mutex (exposes Lock/Unlock); use named field
  • Channels:
    • Sender closes, receiver checks
    • Don't close from receiver side
    • Never close with multiple concurrent senders
    • Document buffering rationale
    • Prefer
      select
      with
      context.Done()
      for cancellation
  • 不要通过共享内存来通信,而要通过通信来共享内存
  • 通道用于协调;互斥锁用于序列化
  • 使用
    errgroup.WithContext
    启动返回错误的goroutine;
    g.Wait()
    返回第一个错误
  • sync.WaitGroup.Go()
    (Go 1.25+):更简洁的goroutine启动方式
    go
    var wg sync.WaitGroup
    wg.Go(func() { work() })  // 合并了Add(1) + go
    wg.Wait()
  • 明确goroutine的生命周期;注明其退出的时间和原因
  • 避免goroutine泄漏(阻塞在不可达的通道上);使用goroutine泄漏分析工具检测(
    GOEXPERIMENT=goroutineleakprofile
    ,Go 1.26+)
  • 使用
    context.Context
    进行取消操作
  • 订阅
    context.Done()
    以实现优雅关闭
  • 优先使用同步函数;让调用者根据需要添加并发
  • 使用
    sync.Mutex
    /
    RWMutex
    进行保护;零值即可直接使用
  • 当读取操作远多于写入操作时使用
    RWMutex
  • 互斥锁使用指针接收器(防止结构体复制)
  • 保持临界区尽可能小;避免跨I/O操作持有锁
  • 使用
    sync.Once
    进行一次性初始化;辅助函数:
    sync.OnceFunc()
    sync.OnceValue()
    sync.OnceValues()
    (Go 1.21+)
  • 使用
    atomic
    操作处理原始计数器
  • 不要嵌入互斥锁(会暴露Lock/Unlock方法);使用命名字段
  • 通道:
    • 发送者负责关闭通道,接收者检查通道状态
    • 不要从接收端关闭通道
    • 永远不要在多个并发发送者的情况下关闭通道
    • 注明缓冲的理由
    • 优先使用带
      context.Done()
      select
      进行取消

Axioms

公理

Operationnil channelclosed channel
Sendblocks foreverpanics
Receiveblocks foreverreturns zero value
Closepanicspanics
  • Nil channels are useful in
    select
    to disable a case
  • Use
    for range ch
    to receive until closed
  • Check closure with
    v, ok := <-ch
操作nil通道已关闭通道
发送永久阻塞触发panic
接收永久阻塞返回零值
关闭触发panic触发panic
  • Nil通道在
    select
    中可用于禁用某个分支
  • 使用
    for range ch
    接收数据直到通道关闭
  • 使用
    v, ok := <-ch
    检查通道是否关闭

Iterators (Go 1.22+)

迭代器(Go 1.22+)

Range Over Integers (Go 1.22+)

遍历整数范围(Go 1.22+)

go
for i := range 10 {
    fmt.Println(i)  // 0..9
}
go
for i := range 10 {
    fmt.Println(i)  // 0..9
}

Range Over Functions (Go 1.23+)

遍历函数返回值(Go 1.23+)

go
// String iterators
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }

// Slice iterators
for i, v := range slices.All(items) { }
for v := range slices.Values(items) { }
for v := range slices.Backward(items) { }
for chunk := range slices.Chunk(items, 3) { }

// Map iterators
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }

// Collect iterator to slice
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))

// Custom iterator
func (s *Set[T]) All() iter.Seq[T] {
    return func(yield func(T) bool) {
        for v := range s.items {
            if !yield(v) {
                return
            }
        }
    }
}
go
// 字符串迭代器
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }

// 切片迭代器
for i, v := range slices.All(items) { }
for v := range slices.Values(items) { }
for v := range slices.Backward(items) { }
for chunk := range slices.Chunk(items, 3) { }

// 映射迭代器
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }

// 将迭代器结果收集到切片
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))

// 自定义迭代器
func (s *Set[T]) All() iter.Seq[T] {
    return func(yield func(T) bool) {
        for v := range s.items {
            if !yield(v) {
                return
            }
        }
    }
}

Interface Design

接口设计

  • Accept interfaces, return concrete types
  • Define interfaces at the consumer, not the provider; keep them small (1-2 methods)
  • Compile-time interface check:
    var _ http.Handler = (*MyHandler)(nil)
  • For single-method dependencies, use function types instead of interfaces
  • Don't embed types in exported structs—exposes methods and breaks API compatibility
  • 接受接口类型,返回具体类型
  • 在消费者端定义接口,而非提供者端;保持接口小型化(1-2个方法)
  • 编译时接口检查:
    var _ http.Handler = (*MyHandler)(nil)
  • 对于单方法依赖,优先使用函数类型而非接口
  • 不要在导出的结构体中嵌入类型——这会暴露方法并破坏API兼容性

Slice & Map Patterns

切片与映射模式

  • Pre-allocate when size known:
    make([]User, 0, len(ids))
  • Nil vs empty:
    var t []string
    (nil, JSON null) vs
    t := []string{}
    (non-nil, JSON
    []
    )
  • Copy at boundaries with
    slices.Clone(items)
    to prevent external mutations
  • Prefer
    strings.Cut(s, "/")
    over
    strings.Split
    for prefix/suffix extraction
  • Append handles nil:
    var items []Item; items = append(items, newItem)
  • 已知大小时预先分配:
    make([]User, 0, len(ids))
  • Nil切片与空切片的区别:
    var t []string
    (Nil,JSON为null) vs
    t := []string{}
    (非Nil,JSON为
    []
  • 在边界处使用
    slices.Clone(items)
    复制,防止外部修改
  • 提取前缀/后缀时优先使用
    strings.Cut(s, "/")
    而非
    strings.Split
  • Append操作可处理Nil切片:
    var items []Item; items = append(items, newItem)

Common Patterns

常见模式

Options Pattern

选项模式

Define
type Option func(*Config)
. Create
WithX
functions returning
Option
that set fields. Constructor takes
...Option
, applies each to default config.
定义
type Option func(*Config)
。创建返回
Option
WithX
函数来设置字段。构造函数接受
...Option
参数,将每个选项应用到默认配置。

Default Values (Go 1.22+)

默认值(Go 1.22+)

Use
cmp.Or(a, b, c)
to return first non-zero value—e.g.,
cmp.Or(cfg.Port, envPort, 8080)
.
使用
cmp.Or(a, b, c)
返回第一个非零值——例如:
cmp.Or(cfg.Port, envPort, 8080)

Context Usage

Context使用

  • First parameter:
    func Foo(ctx context.Context, ...)
  • Don't store in structs
  • Use for cancellation, deadlines, request-scoped values only
  • 第一个参数:
    func Foo(ctx context.Context, ...)
  • 不要将Context存储在结构体中
  • 仅用于取消操作、截止时间和请求作用域的值

HTTP Best Practices

HTTP最佳实践

  • Use
    http.Server{}
    with explicit
    ReadTimeout
    /
    WriteTimeout
    ; avoid
    http.ListenAndServe
  • Always
    defer resp.Body.Close()
    after checking error
  • Accept
    *http.Client
    as dependency for testability
  • 使用
    http.Server{}
    并显式设置
    ReadTimeout
    /
    WriteTimeout
    ;避免使用
    http.ListenAndServe
  • 检查错误后务必
    defer resp.Body.Close()
  • 接受
    *http.Client
    作为依赖以提高可测试性

HTTP Routing (Go 1.22+)

HTTP路由(Go 1.22+)

go
// Method-based routing with path patterns
mux.HandleFunc("POST /items/create", createHandler)
mux.HandleFunc("GET /items/{id}", getHandler)
mux.HandleFunc("GET /files/{path...}", serveFiles)  // Greedy wildcard

// Extract path values
func getHandler(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
}
go
// 基于方法的路由和路径模式
mux.HandleFunc("POST /items/create", createHandler)
mux.HandleFunc("GET /items/{id}", getHandler)
mux.HandleFunc("GET /files/{path...}", serveFiles)  // 贪婪通配符

// 提取路径参数
func getHandler(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
}

CSRF Protection (Go 1.25+)

CSRF防护(Go 1.25+)

go
import "net/http"

handler := http.CrossOriginProtection(myHandler)
// Rejects non-safe cross-origin requests using Fetch metadata
go
import "net/http"

handler := http.CrossOriginProtection(myHandler)
// 使用Fetch元数据拒绝非安全的跨域请求

Directory-Scoped File Access (Go 1.24+)

目录范围的文件访问(Go 1.24+)

go
root, err := os.OpenRoot("/var/data")
if err != nil {
    return err
}
f, err := root.Open("file.txt")  // Can't escape /var/data
Prevents path traversal attacks; works like a chroot.
go
root, err := os.OpenRoot("/var/data")
if err != nil {
    return err
}
f, err := root.Open("file.txt")  // 无法突破/var/data目录
防止路径遍历攻击;功能类似chroot。

Cleanup Functions (Go 1.24+)

清理函数(Go 1.24+)

go
runtime.AddCleanup(obj, func() { cleanup() })
Advantages over
SetFinalizer
: multiple cleanups per object, works with interior pointers, no cycle leaks, faster.
go
runtime.AddCleanup(obj, func() { cleanup() })
相较于
SetFinalizer
的优势:每个对象可添加多个清理函数,支持内部指针,无循环泄漏,速度更快。

Structured Logging (log/slog)

结构化日志(log/slog)

  • Use
    slog.Info("msg", "key", value, "key2", value2)
    with key-value pairs
  • Add persistent attributes:
    logger := slog.With("service", "api")
  • JSON output:
    slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
  • 使用
    slog.Info("msg", "key", value, "key2", value2)
    格式的键值对日志
  • 添加持久属性:
    logger := slog.With("service", "api")
  • JSON输出:
    slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))

Common Gotchas

常见陷阱

  • Check errors immediately (Go 1.25 fixed compiler bug): always check
    err != nil
    before using any returned values
    go
    // WRONG: could execute f.Name() before err check in Go 1.21-1.24
    f, err := os.Open("file")
    name := f.Name()
    if err != nil { return }
    
    // CORRECT: check immediately
    f, err := os.Open("file")
    if err != nil { return }
    name := f.Name()
  • time.Ticker
    : always call
    Stop()
    to prevent leaks
  • Slices hold refs to backing arrays (can retain large memory)
  • nil
    interface vs
    nil
    concrete:
    var err error = (*MyError)(nil)
    err != nil
    is true
  • Loop variables: each iteration has own copy (Go 1.22+); older versions share
  • Timer/Ticker channels: capacity 0 (Go 1.23+); previously capacity 1
  • init()
    is an anti-pattern; prefer explicit initialization
  • 立即检查错误(Go 1.25修复了编译器bug):在使用任何返回值前务必检查
    err != nil
    go
    // 错误示例:在Go 1.21-1.24中可能在检查错误前执行f.Name()
    f, err := os.Open("file")
    name := f.Name()
    if err != nil { return }
    
    // 正确示例:立即检查错误
    f, err := os.Open("file")
    if err != nil { return }
    name := f.Name()
  • time.Ticker
    :务必调用
    Stop()
    以防止泄漏
  • 切片持有对底层数组的引用(可能占用大量内存)
  • Nil接口与Nil具体值的区别:
    var err error = (*MyError)(nil)
    err != nil
    为true
  • 循环变量:每个迭代有独立副本(Go 1.22+);旧版本共享同一变量
  • Timer/Ticker通道:容量为0(Go 1.23+);之前版本容量为1
  • init()
    是反模式;优先使用显式初始化

Linting (golangci-lint)

代码检查(golangci-lint)

yaml
undefined
yaml
undefined

.golangci.yml

.golangci.yml

linters: enable: - errcheck # Unchecked errors - govet # Suspicious constructs - staticcheck # Static analysis - unused # Unused code - gosimple # Simplifications - ineffassign # Ineffectual assignments - typecheck # Type checking - gocritic # Opinionated checks - gofumpt # Stricter gofmt - misspell # Spelling - nolintlint # Malformed //nolint directives - wrapcheck # Errors from external packages wrapped - errorlint # errors.Is/As usage
linters-settings: govet: enable-all: true gocritic: enabled-tags: [diagnostic, style, performance]

```bash
golangci-lint run              # Lint current module
golangci-lint run --fix        # Auto-fix where possible
golangci-lint run --timeout 5m # Increase timeout for large codebases
linters: enable: - errcheck # 未检查的错误 - govet # 可疑构造 - staticcheck # 静态分析 - unused # 未使用的代码 - gosimple # 代码简化建议 - ineffassign # 无效赋值 - typecheck # 类型检查 - gocritic # 主观性检查 - gofumpt # 更严格的gofmt - misspell # 拼写检查 - nolintlint # 格式错误的//nolint指令 - wrapcheck # 外部包错误的包装检查 - errorlint # errors.Is/As使用检查
linters-settings: govet: enable-all: true gocritic: enabled-tags: [diagnostic, style, performance]

```bash
golangci-lint run              # 检查当前模块
golangci-lint run --fix        # 自动修复可修复的问题
golangci-lint run --timeout 5m # 为大型代码库增加超时时间

Pre-Commit

提交前检查

Check for Makefile targets first (
make help
, or read Makefile). Common targets:
  • make lint
    or
    make check
  • make test
  • make build
Fallback if no Makefile:
  1. go build ./...
  2. go test -v -race ./...
  3. golangci-lint run
  4. go fix ./...
    (Go 1.26+: modernizes code to latest idioms)
  5. gofmt -w .
    or
    goimports -w .
  6. go mod tidy
先查看Makefile中的目标(
make help
或直接阅读Makefile)。常见目标:
  • make lint
    make check
  • make test
  • make build
若无Makefile,可按以下步骤执行:
  1. go build ./...
  2. go test -v -race ./...
  3. golangci-lint run
  4. go fix ./...
    (Go 1.26+:将代码更新为最新的惯用写法)
  5. gofmt -w .
    goimports -w .
  6. go mod tidy

Performance

性能

Profile-Guided Optimization (PGO)

基于分析的优化(PGO)

bash
undefined
bash
undefined

Collect profile

收集分析数据

go test -cpuprofile=default.pgo
go test -cpuprofile=default.pgo

PGO automatically enabled if default.pgo exists in main package

若主包中存在default.pgo,会自动启用PGO

go build # Uses default.pgo
go build # 使用default.pgo

Typical 2-7% performance improvement

通常可提升2-7%的性能

undefined
undefined

Security

安全

Based on OWASP Go Secure Coding Practices. Read the linked reference for each topic.
基于OWASP Go安全编码实践。每个主题请参考链接的详细指南。

Quick Checklist

快速检查清单

Input/Output:
  • All user input validated server-side
  • SQL queries use prepared statements only
  • XSS protection via
    html/template
  • CSRF tokens on state-changing requests
  • File paths validated against traversal (
    os.OpenRoot
    Go 1.24+)
Auth/Sessions:
  • Passwords hashed with bcrypt/Argon2/PBKDF2
  • crypto/rand
    for all tokens/session IDs (
    crypto/rand.Text()
    Go 1.24+)
  • Secure cookie flags (HttpOnly, Secure, SameSite)
  • Session expiration enforced
Communication:
  • HTTPS/TLS everywhere, TLS 1.2+ only (post-quantum ML-KEM default Go 1.24+)
  • HSTS header set
  • InsecureSkipVerify = false
Data Protection:
  • Secrets in environment variables, never in logs/errors
  • Generic error messages to users
输入/输出:
  • 所有用户输入均在服务端验证
  • SQL查询仅使用预编译语句
  • 通过
    html/template
    防护XSS
  • 状态变更请求使用CSRF令牌
  • 文件路径验证以防止遍历(Go 1.24+使用
    os.OpenRoot
认证/会话:
  • 密码使用bcrypt/Argon2/PBKDF2哈希
  • 所有令牌/会话ID使用
    crypto/rand
    生成(Go 1.24+使用
    crypto/rand.Text()
  • Cookie设置安全标志(HttpOnly、Secure、SameSite)
  • 强制会话过期
通信:
  • 全程使用HTTPS/TLS,仅支持TLS 1.2+(Go 1.24+默认使用后量子ML-KEM)
  • 设置HSTS头
  • InsecureSkipVerify = false
数据保护:
  • 密钥存储在环境变量中,绝不要记录在日志/错误信息中
  • 向用户返回通用错误信息

Detailed Guides

详细指南

  • Input Validation — whitelisting, boundary checks, escaping
  • Database Security — prepared statements, parameterized queries
  • Authentication — bcrypt, password storage
  • Cryptography
    crypto/rand
    , never
    math/rand
    for security
  • Session Management — secure cookies, session lifecycle
  • TLS/HTTPS — TLS config, HSTS, post-quantum key exchanges
  • CSRF Protection — token generation,
    http.CrossOriginProtection
    (Go 1.25+)
  • Secure Error Handling — generic user messages, detailed server logs
  • File Security — path traversal prevention,
    os.OpenRoot
  • Security Logging — what to log, what never to log
  • Access Control
  • XSS Prevention
  • 输入验证 — 白名单、边界检查、转义
  • 数据库安全 — 预编译语句、参数化查询
  • 认证 — bcrypt、密码存储
  • 加密
    crypto/rand
    ,绝不要在安全场景使用
    math/rand
  • 会话管理 — 安全Cookie、会话生命周期
  • TLS/HTTPS — TLS配置、HSTS、后量子密钥交换
  • CSRF防护 — 令牌生成、
    http.CrossOriginProtection
    (Go 1.25+)
  • 安全错误处理 — 通用用户消息、详细服务端日志
  • 文件安全 — 路径遍历防护、
    os.OpenRoot
  • 安全日志 — 日志内容规范、禁止记录的信息
  • 访问控制
  • XSS防护

Security Tools

安全工具

ToolPurposeCommand
gosecSecurity scanner
gosec ./...
govulncheckVulnerability scanner
govulncheck ./...
trivyContainer/dep scanner
trivy fs .
工具用途命令
gosec安全扫描器
gosec ./...
govulncheck漏洞扫描器
govulncheck ./...
trivy容器/依赖扫描器
trivy fs .