Files
OpenFlare/.agents/skills/go-defensive/references/BOUNDARY-COPYING.md
T
2026-08-04 11:40:32 +08:00

2.1 KiB
Raw Blame History

在 API 边界处复制切片和 Map

来源:Uber 风格指南

切片和 map 包含对其底层数据的引用。在 API 边界处复制它们,以防止调用者修改内部状态(反之亦然)。

接收切片和 Map

当函数存储调用者传入的切片或 map 时,始终进行防御性复制。调用者保留原始引用,可以在函数返回后修改它。

切片

不好

func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips  // 调用者仍然可以修改 d.trips
}

好

func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}

Map

不好

func (s *Server) SetConfig(cfg map[string]string) {
  s.config = cfg  // 调用者仍然可以修改 s.config
}

好

func (s *Server) SetConfig(cfg map[string]string) {
  s.config = make(map[string]string, len(cfg))
  for k, v := range cfg {
    s.config[k] = v
  }
}

返回切片和 Map

返回内部切片或 map 时,返回副本以防止调用者修改你的内部状态。

返回 Map

不好

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  return s.counters  // 暴露了内部状态!
}

好

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  result := make(map[string]int, len(s.counters))
  for k, v := range s.counters {
    result[k] = v
  }
  return result
}

返回切片

不好

func (q *Queue) Items() []Item {
  return q.items  // 调用者可以追加、修改或重新切片
}

好

func (q *Queue) Items() []Item {
  result := make([]Item, len(q.items))
  copy(result, q.items)
  return result
}

何时不需要复制

防御性复制有开销。在以下情况下可以跳过:

  • 数据按约定是不可变的,并且有清晰的文档说明
  • 切片/map 是为调用者新创建的(不在内部存储)
  • 性能分析表明复制在热路径中是瓶颈

如有疑问,就复制。与共享引用导致的 bug 相比,开销通常可以忽略不计。