4/01/2026

Go 工程实践:常见坑与设计注意事项


一、指针 vs 值传递(核心原则)

✅ 判断规则

1. 需要修改数据 → 用指针
2. 对象很大 → 用指针
3. 小对象 + 只读 → 用值(推荐)
4. 方法接收者一致 → 保持一致

❌ 常见误区

func distance(p *Point) {} // ❌ 不必要的指针

问题:

  • 增加 nil 风险

  • 语义误导(看起来会修改)

  • 可能增加 GC 压力


✅ 推荐写法

func distance(p Point) {} // ✅

二、interface 使用原则

1️⃣ 不要使用 *interface(反模式)

func f(w *io.Writer) {} // ❌

原因:

  • interface 本身已是引用语义

  • 增加一层间接访问,没有收益

  • 破坏抽象


2️⃣ interface 的本质

interface = (type, data pointer)

👉 已经包含指针语义


三、nil 设计与风险控制

核心原则

不要让 nil 在系统中“流动”

✅ 正确做法

1️⃣ 构造函数保证非 nil

func NewUser() *User {
    return &User{}
}

2️⃣ 边界检查(而不是内部检查)

func Handle(u *User) error {
    if u == nil {
        return errors.New("nil user")
    }
    return u.do()
}

3️⃣ 小对象优先值传递

func Process(p Point) {} // 永远不会 nil

❌ 错误做法

func (u *User) Save() {
    if u == nil { // ❌ 到处防御
        return
    }
}

四、interface “假 nil”陷阱(高危)

现象

var e *MyError = nil
var err error = e

fmt.Println(err == nil) // false ❗

原因

err = (type: *MyError, data: nil)

👉 interface 判断 nil 要求:

type == nil && data == nil

✅ 规避方法

if e == nil {
    return nil // ✅
}
return e

五、error wrapping(错误链)

❌ 错误写法

return fmt.Errorf("failed: %v", err) // ❌

👉 丢失错误链


✅ 正确写法

return fmt.Errorf("failed: %w", err) // ✅

使用方式

if errors.Is(err, target) { }

自定义 error 必须实现

func (e *MyError) Unwrap() error {
    return e.Err
}

六、error 判断原则

场景推荐
判断具体错误errors.Is
类型断言errors.As
直接比较❌ 避免

七、context 使用原则

1️⃣ context 是“控制器”,不是“数据容器”

用于:
- 超时控制
- 取消传播

2️⃣ 必须向下传递

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    do(ctx)
}

3️⃣ goroutine 必须绑定 ctx

go func(ctx context.Context) {
    select {
    case <-ctx.Done():
        return
    }
}(ctx)

八、HTTP / Shutdown 关键点

核心理解

signal → ctx → Shutdown

Shutdown 行为

  • 停止接收新请求

  • 等待已有请求完成

  • 超时后强制关闭


❌ 常见误解

Shutdown 会监听系统信号 ❌

实际:

  • 只监听 ctx


九、接口 vs 结构体设计哲学

类型作用
struct表达数据
interface表达行为

net/http 示例

func handler(w http.ResponseWriter, r *http.Request)
  • r:数据(大对象 → 指针)

  • w:行为(接口)


十、工程实践总结(最重要)

一句话原则

1. 用语义决定指针还是值,不要机械统一
2. interface 不要加指针
3. nil 不要在系统中传播
4. error 必须支持 unwrap
5. ctx 必须贯穿调用链

最容易踩的三个坑

1. interface != nil 但内部是 nil
2. error wrapping 用了 %v 而不是 %w
3. 所有 struct 都用指针(过度设计)

结尾

这套规则本质上围绕三件事:

- 可读性(语义清晰)
- 可控性(避免隐式行为)
- 可维护性(错误可追踪)


Go 工程实践:常见坑与设计注意事项

一、指针 vs 值传递(核心原则) ✅ 判断规则 1. 需要修改数据 → 用指针 2. 对象很大 → 用指针 3. 小对象 + 只读 → 用值(推荐) 4. 方法接收者一致 → 保持一致 ❌ 常见误区 func distance(p *Point) {} // ❌ 不必要的指针 ...