Go 常见陷阱汇总
本文档跨主题汇总了 Go 标准库使用中最容易犯的错误,作为速查清单。
并发与同步
| 陷阱 | 后果 | 检测 |
|---|---|---|
复制 sync.Mutex(值传递) |
锁失效 | go vet |
WaitGroup.Add 放在 goroutine 内部 |
panic: negative counter | -race |
RWMutex 读锁内获取写锁 |
死锁 | 运行时阻塞 |
sync.Once.Do 内递归调用同一个 Once |
死锁 | 运行时阻塞 |
time.After 在循环中频繁创建 |
内存泄漏(channel 不回收) | pprof heap |
| 向已关闭 channel 发送 | panic | 代码审查 |
| 读取 channel 时未区分关闭 vs 有值 | 逻辑错误 | v, ok := <-ch |
| Context 的 cancel 从不调用 | goroutine 泄漏 | pprof goroutine |
sync.Pool 假设 Put 的对象一直存在 |
对象被 GC 清除 | 逻辑错误 |
时间处理
| 陷阱 | 正确做法 |
|---|---|
time.Parse 默认 UTC |
用 ParseInLocation |
AddDate(0,1,0) 月末溢出 |
检查结果日期 |
time.AfterFunc 忘记 Stop |
计时器泄漏 |
defer cancel() 漏写 |
Context 泄漏 |
用 == 比较两个 time.Time |
用 Equal() |
JSON 编解码
| 陷阱 | 后果 |
|---|---|
JSON 数字反序列化到 interface{} 时精度丢失 |
大整数变 float64 |
omitempty 对 struct 不生效 |
空结构体仍被序列化 |
| 默认 HTML 转义 | &、<、> 被转义 |
nil slice → null,empty slice → [] |
API 行为不一致 |
json.Unmarshal 和 Decoder.UseNumber() 不同 |
数字类型不同 |
MarshalJSON 返回值没有引号 |
非法 JSON |
I/O 操作
| 陷阱 | 正确做法 |
|---|---|
假设 Read 一次返回所有数据 |
循环读取或用 io.ReadFull |
忽略 Read 在 n>0 时返回的 err |
先处理 buf[:n],再检查 err |
bufio.Writer 忘记 Flush() |
数据丢失 |
bufio.Scanner 不检查 Err() |
截断数据不报错 |
忘记 resp.Body.Close() |
HTTP 连接泄漏 |
os.Exit 跳过 defer |
手动清理 |
路径与文件系统
| 陷阱 | 正确做法 |
|---|---|
字符串拼接路径 dir + "/" + file |
filepath.Join |
filepath.Clean 后不验证路径 |
检查路径仍在基础目录内 |
os.Create 截断已有文件 |
追加用 O_APPEND |
defer file.Close() 在任何需要写后检查 |
用闭包处理 Close 错误 |
反射
| 陷阱 | 后果 |
|---|---|
对不可寻址 Value 调 SetXxx |
panic |
Int() 调在非整数类型 |
panic |
FieldByName 找不到字段 |
返回零值(不 panic) |
reflect.TypeOf(nil) 返回 nil |
检查 nil |
reflect.ValueOf(nil) 返回零值 Value |
行为与 TypeOf 不同 |
其他常见错误
| 陷阱 | 正确做法 |
|---|---|
fmt.Sprintf 在热路径中 |
strconv + strings.Builder |
string([]byte) 频繁转换 |
统一使用一种类型 |
regexp.Compile 在循环中 |
全局预编译 |
| nil map 赋值 | make(map[...]...) |
| 忽略 error 返回值 | 始终检查 |
:= 在错误的 scope 创建新变量 |
小心 if/for 中的 := |