Skip to content

Go 后端面试复盘:语言、并发与服务实践 · 27. 高频代码陷阱 ​

返回分篇目录

前置:Go 系统学习,并完成一个HTTP 工程练习。本页用于面试前查漏补缺,不按章节替代语言课程。

27. 高频代码陷阱 ​

27.1 range 循环变量地址问题 ​

历史上,循环变量可能在每轮复用,取地址或闭包捕获会得到相同变量。现代 Go 对常见循环变量语义已改进,但代码仍应清晰表达意图,并注意:

  • 模块 Go 版本和实际工具链。
  • 外层预声明并被重复赋值的变量。
  • map value 本身为临时副本。
  • 闭包是否共享了其他可变状态。

兼容且清晰的写法:

go
for _, item := range items {
	item := item
	go func() {
		process(item)
	}()
}

如果需要原切片元素地址:

go
for i := range items {
	use(&items[i])
}

27.2 defer 与 nil 方法接收者 ​

defer 注册时函数值和参数立即求值。像 defer resp.Body.Close() 必须在确认 err == nil 且 resp、Body 有效后执行。

27.3 shadowing 变量遮蔽 ​

go
var err error
if cond {
	value, err := load() // 可能创建新的 value,并让 err 在内层被遮蔽
	_ = value
	_ = err
}
return err // 可能仍是外层 nil

短变量声明非常方便,但在错误处理和命名返回值附近要特别留意作用域。

27.4 nil receiver 可以调用方法吗? ​

可以,只要方法内部能安全处理 nil:

go
func (n *Node) Len() int {
	if n == nil {
		return 0
	}
	return 1 + n.Next.Len()
}

调用方法本身不一定 panic,真正解引用 nil 字段才会 panic。

27.5 把 Mutex 复制了 ​

go
type Counter struct {
	mu sync.Mutex
	n  int
}

func (c Counter) Inc() { // 错:复制锁和 n
	c.mu.Lock()
	defer c.mu.Unlock()
	c.n++
}

应使用指针接收者。go vet 的 copylocks 检查可发现部分问题。

27.6 发送到已关闭 channel ​

仅用 recover 掩盖 send on closed channel 不是正确并发设计。应明确 channel 所有权,让关闭和发送流程由同一协调机制控制。

27.7 goroutine 中修改外层 error ​

go
var err error
for _, job := range jobs {
	go func() {
		err = process(job) // 数据竞争
	}()
}

应使用错误 channel、带取消的任务组,或每个 goroutine 返回独立结果。

27.8 time.Ticker 忘记停止 ​

go
ticker := time.NewTicker(time.Second)
defer ticker.Stop()

后台循环还要监听 context,否则函数所有者无法停止它。

27.9 map 返回内部可变对象 ​

即使 map 读操作加了锁,返回内部 slice/map/指针后调用方仍能在锁外修改共享对象。

解决:

  • 返回副本。
  • 返回不可变快照。
  • 提供受控方法而不是暴露内部对象。

27.10 错误使用 time.After 做循环超时 ​

高频循环每次创建新 timer 会增加对象和运行时 timer 压力。复用 time.Timer,或重新设计成统一 ticker/context deadline。

27.11 忽略 HTTP 状态码 ​

go
resp, err := client.Do(req)

err == nil 只表示 HTTP 往返成功,不表示业务成功。404、500 都可能是 nil error,必须判断状态码并限制读取错误响应体。

27.12 JSON 把 int64 交给 JavaScript ​

JavaScript Number 不能精确表达所有 int64。用户 ID、订单 ID、雪花 ID 应与前端约定为字符串,或使用明确支持大整数的方案。

27.13 锁内调用外部函数 ​

回调、RPC、数据库访问可能慢、重入或 panic,持锁执行会扩大临界区甚至死锁。通常先在锁内复制必要状态,解锁后执行外部操作。

27.14 无界 goroutine ​

go
for req := range requests {
	go handle(req)
}

流量突增时可能导致 goroutine、内存和下游请求无限增长。使用 worker pool、semaphore 或有界队列。

27.15 错误的 semaphore 退出 ​

获取 semaphore 后必须确保释放,即使函数返回或 panic:

go
sem <- struct{}{}
go func() {
	defer func() { <-sem }()
	process()
}()

同时监听 context,避免等待令牌时无法取消。


继续阅读