Uber 《Go语言编程规范》学习笔记(一)

Interface 合理性验证

在编译时验证接口的符合性。这包括:

  • 将实现特定接口的导出类型作为接口API 的一部分进行检查
  • 实现同一接口的(导出和非导出)类型属于实现类型的集合
  • 任何违反接口合理性检查的场景,都会终止编译,并通知给用户

补充:上面3条是编译器对接口的检查机制, 大体意思是错误使用接口会在编译期报错. 所以可以利用这个机制让部分问题在编译期暴露.

如果 *Handlerhttp.Handler 的接口不匹配, 那么语句 var _ http.Handler = (*Handler)(nil) 将无法编译通过.

Bad

// 如果Handler没有实现http.Handler,会在运行时报错
type Handler struct {
  // ...
}
func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  ...
}

Good

type Handler struct {
  // ...
}
// 用于触发编译期的接口的合理性检查机制
// 如果Handler没有实现http.Handler,会在编译期报错
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}

赋值的右边应该是断言类型的零值。 对于指针类型(如 *Handler)、切片和映射,这是 nil; 对于结构类型,这是空结构。

type LogHandler struct {
  h   http.Handler
  log *zap.Logger
}
var _ http.Handler = LogHandler{}
func (h LogHandler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}

Mutex 的使用方法

非导出,嵌入 mutex

type smap struct {
  sync.Mutex // only for unexported types(仅适用于非导出类型)

  data map[string]string
}

func newSMap() *smap {
  return &smap{
    data: make(map[string]string),
  }
}

func (m *smap) Get(k string) string {
  m.Lock()
  defer m.Unlock()

  return m.data[k]
}

对于导出的类型,使用专用字段

type SMap struct {
  mu sync.Mutex // 对于导出类型,请使用私有锁

  data map[string]string
}

func NewSMap() *SMap {
  return &SMap{
    data: make(map[string]string),
  }
}

func (m *SMap) Get(k string) string {
  m.mu.Lock()
  defer m.mu.Unlock()

  return m.data[k]
}

在危险的边缘拷贝在 Slices 和 Maps

slices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。

接收 Slices 和 Maps

Bad

func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// 你是要修改 d1.trips 吗?
trips[0] = ...

Good

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

trips := ...
d1.SetTrips(trips)

// 这里我们修改 trips[0],但不会影响到 d1.trips
trips[0] = ...

返回 slices 或 maps

Bad

type Stats struct {
  mu sync.Mutex

  counters map[string]int
}

// Snapshot 返回当前状态。
func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  return s.counters
}

// snapshot 不再受互斥锁保护
// 因此对 snapshot 的任何访问都将受到数据竞争的影响
// 影响 stats.counters
snapshot := stats.Snapshot()

Good

type Stats struct {
  mu sync.Mutex

  counters map[string]int
}

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
}

// snapshot 现在是一个拷贝
snapshot := stats.Snapshot()

慎重创建 size > 1 的 Channel

默认情况下,channel 是无缓冲的,其 size 为 0。

channel 通常 size 应为 1 或是 0。

Bad

// 不推荐
c := make(chan int, 64)

一般来说,不推荐创建 size > 1 的 Channel。

当需要创建 size > 1 的 Channel ****时,需要考虑:

是什么阻止了 channel 在高负载下和阻塞写时的写入,以及当这种情况发生时系统逻辑有哪些变化

Good

// 大小:1
c := make(chan int, 1) // 或者
// 无缓冲 channel,大小为 0
c := make(chan int)

避免在公共结构中使用类型嵌入

Go 允许 类型嵌入 作为继承和组合之间的折衷。

外部类型获取嵌入类型的方法的隐式副本、获得与类型同名的字段。

如果嵌入的类型是 public,那么外部类型获得的字段也是 public。为了保持向后兼容性,外部类型的每个未来版本都必须保留嵌入类型。

无论是使用嵌入式结构还是使用嵌入式接口,嵌入式类型都会限制类型的演化:

  • 向嵌入式接口添加方法是一个破坏性的改变。
  • 删除嵌入类型是一个破坏性的改变。
  • 即使使用满足相同接口的替代方法替换嵌入类型,也是一个破坏性的改变。

Bad

// ConcreteList 是一个实体列表。
type ConcreteList struct {
  *AbstractList
}
// AbstractList 是各种实体列表的通用实现。
type AbstractList interface {
  Add(Entity)
  Remove(Entity)
}
// ConcreteList 是一个实体列表。
type ConcreteList struct {
  AbstractList
}

Good

// ConcreteList 是一个实体列表。
type ConcreteList struct {
  list AbstractList
}
// 添加将实体添加到列表中。
func (l *ConcreteList) Add(e Entity) {
  l.list.Add(e)
}
// 移除从列表中移除实体。
func (l *ConcreteList) Remove(e Entity) {
  l.list.Remove(e)
}

业务中很少需要嵌入类型。 这是一种方便,可以避免编写冗长的委托方法。

尽管编写这些委托方法是乏味的,但是额外的工作隐藏了实现细节,留下了更多的更改机会,还消除了在文档中发现完整列表接口的间接性操作。

使用 init() 时的注意事项

虽然init()顺序是明确的,但代码可以更改, 因此init()函数之间的关系可能会使代码变得脆弱和容易出错。

应避免因依赖于其他init()函数顺序而造成的问题。

同时应该避免一些动态操作,以免在如go test 之类的环境造成不必要的动态操作:

  • 避免访问或操作全局或环境状态,如机器信息、环境变量、工作目录等
  • 避免I/O,包括文件系统、数据库、网络和系统调用等

但在某些情况下,init()可能更可取或是必要的,可能包括:

  • 不能表示为单个赋值的复杂表达式。
  • 可插入的钩子,如database/sql、编码类型注册表等。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值