IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Effective Go 要点速记

    edony发表于 2023-12-09 14:33:55
    love 0
    💡
    「要写好 Go 了解它的属性和习语很重要,了解 Go 编程的既定约定也很重要,例如命名、格式、程序构造等,以便编写的程序易于其他 Go 程序员理解」,这是 Effective Go 这份文档的初衷,但是 Effective Go 是于 2009 年编写发布的,此后没有进行重大更新。虽然它是理解如何使用语言本身的一个很好的指南,但由于语言的稳定性,它很少提到库,也没有提到自编写以来 Go 生态系统的重大变化,例如构建系统、测试、模块和多态性。所以,Effective Go 仍然有用,但需要清楚的是它远非完整的指南。

    1. 代码格式

    Effective Go 要点速记

    运行 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
    }
    

    References

    1. Effective Go - The Go Programming Language
    2. golang 要点速记


沪ICP备19023445号-2号
友情链接