go-EffectiveGo_阅读笔记

go-EffectiveGo_阅读笔记, 在线文档: https://www.kancloud.cn/kancloud/effective/72199


程序包名

程序包使用小写,一个单词的名字;不需要使用下划线或者混合大小写。要力求简短

程序包名只是导入的缺省名字;其不需要在所有源代码中是唯一的。

另一种约定是,程序包名为其源目录的基础名;在 src/pkg/encoding/base64 中的程序包,是作为”encoding/base64” 来导入的,但是名字为 base64 ,而不是 encoding_base64 或 encodingBase64


Get方法

如果你有一个域叫
做 owner (小写,不被导出),则Get方法应该叫做 Owner (大写,被导出),而不是 GetOwner 。对于要导出的,使用大写名字,提供了区别域和方法的钩子。Set方法,如果需要,则可以叫做 SetOwner


接口名

单个方法的接口使用方法名加上“er”后缀来命名,或者类似的修改来构造一个施动者名词:
Reader , Writer , Formatter , CloseNotifier 等 , 以及它们所体现的函数名字。 Read , Write , Close, Flush , String 等,都具有规范的签名和含义


混合大小写

Go约定使用 MixedCaps 或者 mixedCaps 的形式,而不是下划线来书写多个单词的名字


重新声明和重新赋值

1
2
f, err := os.Open(name)
d, err := f.Stat()

注意 err 在两条语句中都出现了。这种重复是合法的: err 是在第一条语句中被声明,而在第二条语句中 只是被重新赋值 。这意味着使用之前已经声明过的 err 变量调用f.Stat ,只会是赋给其一个新的值, 并没有开辟一个新的寄存器.

在 := 声明中,变量 v 即使已经被声明过,也可以出现,前提是:

  • 该声明和 v 已有的声明在相同的作用域中(如果 v 已经在外面的作用域里被声明了,则该声明将会创建一个新的变量 §)
  • 初始化中相应的值是可以被赋给 v 的
  • 并且,声明中至少有其它一个变量将被声明为一个新的变量

For

Go的 for 循环类似于—但又不等同于—C的。它统一了 for 和 while ,并且没有 do-while 。有三种形式, 其中只有一个具有分号。

1
2
3
4
5
6
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }

对于字符串,range会做更多的事情,通过解析UTF-8来拆分出单个的Unicode编码点。错误的编码会消耗一个字节,产生一个替代的符文(rune)U+FFFD。(名字(与内建类型相关联的)rune是Go的术语,用于指定一个单独的Unicode编码点。详情参见the language specification)循环


Switch

Go的switch要比C的更加通用。表达式不需要为常量,甚至不需要为整数,case是按照从上到下的顺序进行求值,直到找到匹配的。如果switch没有表达式,则对true进行匹配。因此,可以—按照语言习惯—将if-else-if-else链写成一个switch

1
2
3
4
5
6
7
8
9
10
11
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}

switch不会自动从一个case子句跌落到下一个case子句。但是case可以使用逗号分隔的列表。

1
2
3
4
5
6
7
8
9
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
case '0':
return false
}
return false
}

new 和 make

  • new : new(T)会为T类型的新项目,分配被置零的存储,并且返回它的地址,一个类型为*T的值

    分配或者声明之后直接使用。在下一个片段中,pv都不需要进一步的处理便可以正确地工作。

    1
    2
    p := new(SyncedBuffer)  // type *SyncedBuffer
    var v SyncedBuffer // type SyncedBuffer
  • make : 它只用来创建 slice,map 和 channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)

这些例子阐释了newmake之间的差别。

1
2
var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints

空白标识符 _

  • 空白标识符在多赋值语句中的使用

    1
    if _, err := os.Stat(path); os.IsNotExist(err) { // 占位
  • 未使用的导入和变量

    1
    2
    3
    4
    5
    import (
    "fmt"
    )

    var _ = fmt.Printf // 只为 引入 fmt 包
  • 副作用式导入

    1
    import _ "net/http/pprof" // 导入包, 只为让包内执行 init 函数

Channels

如果在创建channel时提供一个可选的整型参数,会设置该channel的缓冲区大小

1
cs := make(chan *os.File, 100)

如果channel是无缓冲的,发送方会一直阻塞直到接收方将数据取出。

如果channel带有缓冲区,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

并发控制

1
2
3
4
5
6
7
var sem = make(chan int, MaxOutstanding) // MaxOutstanding 为并发上限

func handle(r *Request) {
<-sem // Wait for active queue to drain.
process(r) // May take a long time.
sem <- 1 // Done; enable next request to run.
}