Effective Go 要点速记
1. 代码格式
运行 gofmt
工具进行 go 代码的标准格式化,vs code 可以自己运行具体配置参考:Go with Visual Studio Code。
几个 go 代码格式习惯:
- Indentation,go 采用 Tab 进行缩紧,除非必要的时候才使用空格;
- line length,go 没有行长度限制,如果行太长为了可读性可以 Tab 缩进将其包起来;
- parentheses,go 少用括号包括 for、if、switch 等,另外操作符优先级通过空格长短控制,如
x<<8 + y<<16
2. 代码注释
- 块注释:
/* */
- 行注释:
//
- 所有代码之前的没有换行的注释被认为是文档自声明,例如
/* Copyright 2023 Alibaba.Inc */
3. 命名
3.1 package 命名
通常 package 名是小写字母、单字名、不采用下划线和大小写混合。由于 package name 的存在,我们在命名导出成员(exported names)的时候有一些注意点:
- 例如 package 导出的 reader 类型,命名为 Reader 就比较清晰简洁不需要 BufReader,因为在引用的时候是 bufio.Reader 这样就清晰的表达了,
bufio.BufReader
就略显重复; - 例如 package 导出的构造函数命名,以 ring 包的构造函数为例
ring.NewRing()
、ring.Ring()
,、ring.New()
,New 命名就清晰简洁的;
3.2 getter&setter 命名
go 没有 getter,setter 装饰器,以 obj.Owner
为例,通常会有类似 SetOwner 和 GetOwner,但是以根据命名清晰简洁的原则可以命名为:obj.Owner(), obj.SetOwner()
。
3.3 interface 命名
- 只有一个 method 的 interface 的命名可以是
method name + "er"
,例如 Reader - 采用规范、有特殊含义的命名,例如 Read, Write, Close, Flush, String 等
4. 分号
go 不像 C 那样需要分号作为分隔,由此带来的问题 if、for、switch、select 这种控制结构的括号不能换行:
if i < f() {
}
if i < f() // wrong
{ // wrong
}
5. 控制结构
5.1 if
if x > 0 {
return y;
}
if err := file.Chmod(0644); err != nil {
fmt.Println(err)
return err
}
f, err := os.Open(filename)
if err != nil {
return err
} // no need else
d, err := f.Stat() // reassignment and redeclatation
if err != nil {
f.Clos()
return err
}
5.2 for
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
// normal for loop
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
// array
for key, value := range oldMap {
newMap[key] = value
}
// drop the second
for key := range m {
if key.expired() {
delete(m, key)
}
}
// blank identifier
sum := 0
for _, value := range array {
sum += value
}
5.3 switch
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
}
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
6. 函数
6.1 多返回值
func nextInt(b []byte, i int) (int, int) {
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i]) - '0'
}
return x, i
}
for i := 0; i < len(b); {
x, i = nextInt(b, i)
fmt.Println(x)
}
6.2 返回值命名
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
6.3 defer
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
// defer 遵循 LIFO
7. 数据
7.1 new
new 给分配内存但是不做初始化,new(T)
为类型 T 的变量分配全0的存储空间并返回其地址,返回类型是 *T
。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer
// 构造函数增强 new 初始化
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
7.2 make
make 为特性类型做初始化,它们包括:map、slice、channel,make 初始化的内存不是全0,返回的类型是 T
。
make([]int, 10, 100)
make(chan, int)
// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
picture[i] = make([]uint8, XSize)
}
m := make(map[string]int)
m["k1"] = 7
7.3 array
go array 就是一个固定长度的 slice,go 的数组与 c 的数组有几点区别:
- Arrays are values. Assigning one array to another copies all the elements.
- In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
- The size of an array is part of its type. The types
[10]int
and[20]int
are distinct.
7.4 slice
切片包装阵列可为数据序列提供更通用,功能强大和方便的接口。除了具有明确维度(例如变换矩阵)的项目外,Go 中的大多数数组编程都是用切片而不是简单的数组完成的。
var buf []byte
n, err := f.Read(buf[0:32])
var n int
var err error
for i := 0; i < 32; i++ {
nbytes, e := f.Read(buf[i:i+1]) // Read one byte.
n += nbytes
if nbytes == 0 || e != nil {
err = e
break
}
}
7.5 two-dimensional slice
type Transform [3][3]float64 // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte // A slice of byte slices.
7.6 maps
Maps are a convenient and powerful built-in data structure that associate values of one type (the key) with values of another type (the element or value).
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
"MST": -7*60*60,
"PST": -8*60*60,
}
// test if the map has the member
value, present := timeZone[tz]
if present {
return True
}
7.7 print
fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))
fmt.Printf("%v\n", timeZone) // or just fmt.Println(timeZone)
type T struct {
a int
b float64
c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", timeZone)
/* output:
&{7 -2.35 abc def}
&{a:7 b:-2.35 c:abc def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string]int{"CST":-21600, "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}
*/
7.8 append
append 函数原型:
func append(slice []T, elements ...T) []T
// append example
x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)
8. 初始化
8.1 constant
Go 中使用 iota
进行枚举的自动初始化创建:
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
// 这里使用 Sprintf("%f") 避免了使用 format string 引发的无限递归的问题,format string 转换成 string 的时候会调用 String()
8.2 vars
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
8.3 init 函数
每个源码文件都可以定义一个 init 函数,init 函数没有参数(niladic init function)。另外一个关键点 init 函数的执行时机:在包中的所有变量声明都对其初始化器求值之后调用Init,并且只有在所有导入的包都初始化之后才对这些初始化器求值。
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath may be overridden by --gopath flag on command line.
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}
9. 方法
9.1 指针与值
Go 默认是值传递,除了指针和 interface。
type ByteSlice []byte
// value receiver
func (slice ByteSlice) Append(data []byte) []byte {
// Body exactly the same as the Append function defined above.
}
// pointer receiver
func (p *ByteSlice) Append(data []byte) {
slice := *p
// Body as above, without the return.
*p = slice
}
9.2 interface
interface 在 Go 用来为某个对象 object 指定行为。
type Sequence []int
// Methods required by sort.Interface.
func (s Sequence) Len() int {
return len(s)
}
func (s Sequence) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Copy returns a copy of the Sequence.
func (s Sequence) Copy() Sequence {
copy := make(Sequence, 0, len(s))
return append(copy, s...)
}
// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
s = s.Copy() // Make a copy; don't overwrite argument.
sort.Sort(s)
str := "["
for i, elem := range s { // Loop is O(N²); will fix that in next example.
if i > 0 {
str += " "
}
str += fmt.Sprint(elem)
}
return str + "]"
}
9.3 类型转换
type Stringer interface {
String() string
}
var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}
// type assertion
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
10. blank identifier
blank identifier 可以是任意类型任意值。
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}
package main
import (
"fmt"
"io"
"log"
"os"
)
// unused imports and variables
var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader // For debugging; delete when done.
func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
_ = fd
}
// import for side affects without any explicit use
import _ "net/http/pprof"
// interface check
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}
11. embedding
// embedding type
type Job struct {
Command string
*log.Logger
}
job := &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)}
// use embedding type direct
func (job *Job) Printf(format string, args ...interface{}) {
job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}
12. concurrency
12.1 goroutine
go list.Sort() // run list.Sort concurrently; don't wait for it.
func Announce(message string, delay time.Duration) {
go func() {
time.Sleep(delay)
fmt.Println(message)
}() // Note the parentheses - must call the function.
}
12.2 channel
ci := make(chan int) // unbuffered channel of integers
cj := make(chan int, 0) // unbuffered channel of integers
cs := make(chan *os.File, 100) // buffered channel of pointers to Files
c := make(chan int) // Allocate a channel.
// Start the sort in a goroutine; when it completes, signal on the channel.
go func() {
list.Sort()
c <- 1 // Send a signal; value does not matter.
}()
doSomethingForAWhile()
<-c // Wait for sort to finish; discard sent value.
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
sem <- 1 // Wait for active queue to drain.
process(r) // May take a long time.
<-sem // Done; enable next request to run.
}
func Serve(queue chan *Request) {
for {
req := <-queue
go handle(req) // Don't wait for handle to finish.
}
}
12.3 channels of channels
type Request struct {
args []int
f func([]int) int
resultChan chan int
}
/* cleint */
func sum(a []int) (s int) {
for _, v := range a {
s += v
}
return
}
request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// Send request
clientRequests <- request
// Wait for response.
fmt.Printf("answer: %d\n", <-request.resultChan)
/* server */
func handle(queue chan *Request) {
for req := range queue {
req.resultChan <- req.f(req.args)
}
}
12.4 并行
type Vector []float64
// Apply the operation to v[i], v[i+1] ... up to v[n-1].
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
for ; i < n; i++ {
v[i] += u.Op(v[i])
}
c <- 1 // signal that this piece is done
}
const numCPU = 4 // number of CPU cores
func (v Vector) DoAll(u Vector) {
c := make(chan int, numCPU) // Buffering optional but sensible.
for i := 0; i < numCPU; i++ {
go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c)
}
// Drain the channel.
for i := 0; i < numCPU; i++ {
<-c // wait for one task to complete
}
// All done.
}
// runtime will return numbers of CPU
runtime.NumCPU() // use this to replace numCPU=4
runtime.GOMAXPROCS(0) // query the value of user-specified cores number, defaults to runtime.NumCPU()
runtiime.GOMAXPROCS(4) // override runtime.NumCPU as 4
12.5 buffer 泄漏
// client
var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)
func client() {
for {
var b *Buffer
// Grab a buffer if available; allocate if not.
select {
case b = <-freeList:
// Got one; nothing more to do.
default:
// None free, so allocate a new one.
b = new(Buffer)
}
load(b) // Read next message from the net.
serverChan <- b // Send to server.
}
}
// server
func server() {
for {
b := <-serverChan // Wait for work.
process(b)
// Reuse buffer if there's room.
select {
case freeList <- b:
// Buffer on free list; nothing more to do.
default:
// Free list full, just carry on.
}
}
}
13. Error
13.1 error
error built-in interface:
type error interface {
Error() string
}
所以开发者可以实现自定义 Error 从而提供非常详细的错误信息,例如 os.PathError
:
// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
/* error info:
* open /etc/passwx: no such file or directory
*/
// developer can be able handling the specific error
for try := 0; try < 2; try++ {
file, err = os.Create(filename)
if err == nil {
return
}
// type assertion to handle the specific PathError
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
deleteTempFiles() // Recover some space.
continue
}
return
}
13.2 panic
当程序运行时遇到不可恢复的错误时,go 提供了一个内置函数 panic
用于创建一个运行时错误同时终止程序的运行。
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
13.3 recover
上一节中提到的 panic
被调用之后会立刻终止程序执行,并且开始释放 goroutine 栈并且执行 defer
函数直到栈顶之后程序就结束运行了。在这个过程中可以尝试使用内置的 recover 函数来重新获取 goroutine 的控制权从而恢复正常的运行。
// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
return string(e)
}
// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
// doParse will panic if there is a parse error.
defer func() {
if e := recover(); e != nil {
regexp = nil // Clear return value.
err = e.(Error) // Will re-panic if not a parse error.
}
}()
return regexp.doParse(str), nil
}