BruceChen7 / gitblog

My blog
6 stars 1 forks source link

practical golang #6

Open BruceChen7 opened 4 years ago

BruceChen7 commented 4 years ago

practical-go

命名风格

包名 当命名包时,请按下面规则选择一个名称:

局部变量的声明

如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。

// bad
var s = "foo"
// god
s := "foo"

nil是一个有效的slice

nil 是一个有效的长度为 0 的 slice,这意味着,

在边界处拷贝Slices 和 Maps

slices和maps包含了指向底层数据的指针,因此在需要复制它们时要特别注意。请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改

// 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] = ...

使用defer来释放资源

使用 defer 释放资源,诸如文件和锁。

// bad
p.Lock()
if p.count < 10 {
  p.Unlock()
  return p.count
}

p.count++
newCount := p.count
p.Unlock()

return newCount
// 当有多个 return 分支时,很容易遗忘 unlock

// good
p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}

p.count++
return p.count

避免字符串到字节的转换

不要反复从固定字符串创建字节slice。相反,请执行一次转换并捕获结果。

// bad
for i := 0; i < b.N; i++ {
  w.Write([]byte("Hello world"))
}
// good
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
  w.Write(data)
}

尽量初始化时指定Map容量

make(map[T1]T2, hint)
// bad
m := make(map[string]os.FileInfo)

files, _ := ioutil.ReadDir("./files")
for _, f := range files {
    m[f.Name()] = f
}

// good
files, _ := ioutil.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
    m[f.Name()] = f
}

避免使用全局变量

使用选择依赖注入方式避免改变全局变量。 既适用于函数指针又适用于其他值类型

// bad
// sign.go
var _timeNow = time.Now
func sign(msg string) string {
  now := _timeNow()
  return signWithTime(msg, now)
}
// sign_test.go
func TestSign(t *testing.T) {
  oldTimeNow := _timeNow
  _timeNow = func() time.Time {
    return someFixedTime
  }
  defer func() { _timeNow = oldTimeNow }()
  assert.Equal(t, want, sign(give))
}

// good
// sign.go
type signer struct {
  now func() time.Time
}
func newSigner() *signer {
  return &signer{
    now: time.Now,
  }
}
func (s *signer) Sign(msg string) string {
  now := s.now()
  return signWithTime(msg, now)

}
// sign_test.go
func TestSigner(t *testing.T) {
  s := newSigner()
  s.now = func() time.Time {
    return someFixedTime
  }
  assert.Equal(t, want, s.Sign(give))
}

不要 panic

在生产环境中运行的代码必须避免出现 panic。panic是cascading failures级联失败的主要根源。如果发生错误,该函数必须返回错误,并允许调用方决定如何处理它

// bad
func foo(bar string) {
  if len(bar) == 0 {
    panic("bar must not be empty")
  }
  // ...
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  foo(os.Args[1])
}

// good
func foo(bar string) error {
  if len(bar) == 0 {
    return errors.New("bar must not be empty")
  }
  // ...
  return nil
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  if err := foo(os.Args[1]); err != nil {
    panic(err)
  }
}

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

这些嵌入的类型泄漏实现细节、禁止类型演化和模糊的文档。假设共享的AbstractList实现了多种列表类型,请避免在具体的列表实现中嵌入 AbstractList。 相反,只需手动将方法写入具体的列表,该列表将委托给抽象列表。

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

// ConcreteList 是一个实体列表。
type ConcreteList struct {
  *AbstractList
}

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

Go 允许类型嵌入作为继承和组合之间的折衷。外部类型获取嵌入类型的方法的隐式副本。 默认情况下,这些方法委托给嵌入实例的同一方法

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

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

即使嵌入兼容的抽象列表 interface,而不是结构体,这将为开发人员提供更大的灵活性来改变未来,但仍然泄露了具体列表使用抽象实现的细节

// bad
// 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) {
  return l.list.Add(e)
}
// 移除从列表中移除实体。
func (l *ConcreteList) Remove(e Entity) {
  return l.list.Remove(e)
}

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

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