Table of Contents:

构造对象

// $GOROOT/src/time/sleep.go
func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d),
            f:    sendTime,
            arg:  c,
        },
    }
    startTimer(&t.r)
    return t
}

类型嵌入(Type Embedding)

结构体类型

// $GOROOT/src/sync/pool.go
type poolLocal struct {
    private interface{}   
    shared  []interface{}
    Mutex               
    pad     [128]byte  
}

在代码段中,我们在 poolLocal 这个结构体类型中嵌入了类型 Mutex,这就使得 poolLocal 这个类型具有了互斥同步的能力,我们可以通过 poolLocal 类型的变量,直接调用 Mutex 类型的方法 Lock 或 Unlock。

接口类型

// $GOROOT/src/io/io.go
type ReadWriter interface {
    Reader
    Writer
}

这里,标准库通过嵌入接口类型的方式来实现接口行为的聚合,组成大接口,这种方式在标准库中尤为常用。

if语句

if statement; condition {  
    // do something  
}

statement中

comma ok

清晰的map访问

m := make(map[string]int)
v := m["k"]

按照以上写法,当m中不存在k这个key时,v的值是自身类型对应的“零值”。如int类型的零值为0,string类型的零值为"",slice、map、func、channel和指针类型的零值为nil。

// 使用“comma ok”的惯用法来判断key是否在map中
m := make(map[string]int)

if v, ok := m["key1"]; ok  {
    // "key1"在map中
}

安全的类型断言

不使用comma ok的类型断言:

i := 10
var a interface{} = i
s := a.(string)

以上实例中a的实际类型为int,无法通过类型断言转换为string,所以会因断言失败而panic。

comma ok语法的方式:

s, ok := a.(string)

channel receive

Go语言的channel被设计为能够用作“事件通知”,具体实现就是在close之后可以无限receive,不会阻塞并且得到的值都是对应元素类型的“零值”。

首先看一下普通的channel receive语法:

ch := make(chan int)
i := <-ch

如果以上示例中i等于0,我们无法判断是通道ch中确实传递过来一个0;还是因为ch已经被关闭,所以我们得到一个“零值”。
comma ok语法:

// 如果因为ch关闭而得到一个“零值”,ok会是false。
i, ok := <-ch

对于有缓冲channel,如果在其被关闭后缓冲中还有数据,此时comma ok得到的ok会是true,直到缓冲数据读尽变成false:

错误处理

Go 语言惯用法,是使用 error 这个接口类型表示错误,并且按惯例,我们通常将 error 类型返回值放在返回值列表的末尾,这样在调用函数时,我们可以通过简单的判断返回值列表的最后一个值是否为 nil 来判断函数是否执行成功。

接受接口,返回结构体

Go 社区流传一个经验法则:“接受接口,返回结构体(Accept interfaces, return structs)”

type Cond struct {
    ... ...
    L Locker
}
func NewCond(l Locker) *Cond {
    return &Cond{L: l}
}

Option

Option 写法,顾名思义,就是将所有可选的参数作为一个可选方式,一般我们会设计一个“函数类型”来代表这个 Option,然后配套将所有可选字段设计为一个这个函数类型的具体实现。在具体的使用的时候,使用可变字段的方式来控制有多少个函数类型会被执行。

// 普通写法
package newdemo
type Foo struct {
   name string
   id int
   age int
   db interface{}
}
func NewFoo(name string, id int, age int, db interface{}) *Foo {
   return &Foo{
      name: name,
      id:   id,
      age:  age,
      db:   db,
   }
}

//使用Option的写法
type Foo struct {
    name string
    id int
    age int
    db interface{}
}
// FooOption 代表可选参数
type FooOption func(foo *Foo)
// WithName 代表Name为可选参数
func WithName(name string) FooOption {
   return func(foo *Foo) {
      foo.name = name
   }
}
// WithAge 代表age为可选参数
func WithAge(age int) FooOption {
   return func(foo *Foo) {
      foo.age = age
   }
}
// WithDB 代表db为可选参数
func WithDB(db interface{}) FooOption {
   return func(foo *Foo) {
      foo.db = db
   }
}
// NewFoo 代表初始化
func NewFoo(id int, options ...FooOption) *Foo {
   foo := &Foo{
      name: "default",
      id:   id,
      age:  10,
      db:   nil,
   }
   for _, option := range options {
      option(foo)
   }
   return foo
}

MustXXX

XXX()和MustXXX()的函数命名方式,是一种 Go 代码设计技巧。
例如 Go 标准库中regexp包提供的Compile和MustCompile函数。和XXX相比,MustXXX 会在某种情况不满足时 panic。
因此使用MustXXX的开发者看到函数名就会有一个心理预期:使用不当,会造成程序 panic。