美文网首页
2018-03-13 Twelve Go Best Practi

2018-03-13 Twelve Go Best Practi

作者: 四火流年 | 来源:发表于2018-03-13 17:42 被阅读20次

    Best practices

    From Wikipedia:

    "A best practice is a method or technique that has consistently shown results superior to those achieved with other means"

    • simple
    • readable
    • maintainable

    Code sample

    type Gopher struct {
        Name        string
        AgeYears  int
    }
    func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
        err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
        if err == nil {
            size += 4
            var n int
            n, err = w.Write([]byte(g.Name))
            size += int64(n)
            if err == nil {
                err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
                if err == nil {
                    size += 4
                }
                return
            }
            return
        }
        return
    }
    

    1. 先处理错误,来避免嵌套(avoid nesting by handling errors first)

    fun (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
        err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
        if err != nil {
            return
        }
        size += 4
        n, err := w.Write([]byte(g.Name))
        size += int64(n)
        if err != nil {
            return
        }
        err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
        if err == nil {
            size += 4
        }
        return
    }
    

    2. 避免重复(avoid repetition when possible)

    type binWriter struct {
        w      io.Writer
        size  int64
        err    error
    }
    func (w *binWriter) Write(v interface{}) {
        if w.err != nil {
            return
        }
        if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
            w.size += int64(binary.Size(v))
        }
    }
    func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
        bw := &binWriter{w: w}
        bw.Write(int32(len(g.Name)))
        bw.Write([]byte(g.Name))
        bw.Write(int64(g.AgeYears))
        return bw.size, bw.err
    }
    

    处理特殊类型的数据(Type switch to handle special cases)

    func (w *binWriter) Write(v interface{}) {
        if w.err != nil {
            return
        }
        switch v.(type) {
        case string:
            s := v.(string)
            w.Write(int32(len(s))
            w.Write([]byte(s))
        case int:
            i := v.(int)
            w.Write(int64(i))
        default:
            if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
                w.size += int64(binary.Size(v))
            }
        }
    }
    func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
        bw := &binWriter{w: w}
        bw.Write(g.Name)
        bw.Write(g.AgeYears)
        return bw.size, bw.err
    }
    

    再进一步优化,变量名变短(Type switch with short variable declaration)

    func (w *binWriter) Write(v interface{}) {
        if w.err != nil {
            return
        }
        switch x := v.(type) {
        case string:
            w.Write(int32(len(x)))
            w.Write([]byte(x))
        case int:
            w.Write(int64(x))
        default:
            if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
                w.size += int64(binary.Size(v))
            }
        }
    }
    

    Write everything or nothing

    type binWriter struct {
        w    io.Writer
        buf  bytes.Buffer
        err  error
    }
    func (w *binWriter) Write(v interface{}) {
        if w.err != nil {
            return
        }
        switch x := v.(type) {
        case string:
            w.Write(int32(len(x)))
            w.Write([]byte(x))
        case int:
            w.Write(int64(x))
        default:
            w.err = binary.Write(&w.buf, binary.LittleEndian, v)
        }
    }
    
    // Flush writes any pending values into the writer if no error has occurred.
    // If an error has occurred, earlier or with a write by Flush, the error is
    // returned.
    func (w *binWriter) Flush() (int64, error) {
        if w.err != nil {
            return 0, w.err
        }
        return w.buf.WriteTo(w.w)
    }
    
    func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
        bw := &binWriter{w: w}
        bw.Write(g.Name)
        bw.Write(g.AgeYears)
        return bw.Flush()
    }
    

    适配器(Function adapters)

    func init() {
        http.HandleFunc("/", handler)
    }
    func handler(w http.ResponseWriter, r *http.Request) {
        err := doThis()
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            log.Printf("handling %q: %v", r.RequestURI, err)
            return
        }
        err = doThat()
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            log.Printf("handling %q: %v", r.RequestURI, err)
            return
        }
    }
    
    Better
    func init() {
        http.HandleFunc("/", errorHandler(betterHandler))
    }
    
    func errorHandler(f func(http.RequestWriter, *http.Request) error) http.HandleFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            err := f(w, r)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                log.Printf("handing %q: %v", r.RequestURI, err)
            }
        }
    }
    
    func betterHandler(w http.ResponseWriter, r *http.Request) error {
        if err := doThis(); err != nil {
            return fmt.Errorf("doing this: %v", err)
        }
        if err := doThat(); err != nil {
            return fmt.Errorf("doing that: %v", err)
        }
        return nil
    }
    

    3. 优先重要的代码(Important code goes first)

    • license information
    • build tags
    • package documentation
    • import statements, related groups separated by blank lines
    import (
        "fmt"
        "io"
        "log"
    
        "golang.org/x/net/websocket"
    )
    

    4. Document your code

    package name

    // Package playground registers an HTTP handler at "/complie" that
    // proxies requests to the golang.org playground service
    package playground
    

    exported identifiers appear in godoc, they should be documented correctly

    // Author represents the person who wrote and/or is presenting the document.
    type Author struct {
        Elem []Elem
    }
    
    // TextElem returns the first text elements of the author details.
    // This is used to display the author' name, job title, and company
    // without the contact details.
    func (p *Author) TextElem() (elems []Elem) {
    

    5. Shorter is better

    or at least longer is not always better

    • Try to find the shortest name that is self explanatory
      • Prefer MarshaIndent to MarshalWithIndentation
    • Don't forget that the package name will appear before the identifier you chose
      • In package encoding/json we find the type Encoder, not JSONEncoder
      • It is referred as json.Encoder
        也就是说在 encoding/json 里,Encoder 为啥不需要被称作 JSONEncoder呢,是因为调用这个方法的时候,是 json.Encoder,没必要 json.JSONEncoder

    6. Packages with multiple files

    • 避免大文件,所以要拆分
    • 测试代码分离
    • package 里包含多个文件时,要写一个 doc.go 文件作为文档说明

    7. Make your packages "go getable"

    example

    github.com/peterbourgon/foo/
        circle.yml
        Dockerfile
        cmd/
            foosrv/
                main.go
            foocli/
                main.go
        pkg/
            fs/
                fs.go
                fs_test.go
                mock.go
                mock_test.go
            merge/
                merge.go
                merge_test.go
            api/
                api.go
                api_test.go
    

    8. Ask for what you need

    相比于写一个具体的类型,使用接口(interface{})更方便测试

    // 不方便测试
    func (g *Gopher) WriteToFile(f *os.File) (int64, error) {
    // 用接口好一些
    func (g *Gopher) WriteToReadWriter(rw io.ReadWriter) (int64, error) {
    // 需要多少就用多少
    func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {
    

    9. Keep independent packages independent

    代码样例

    import (
        "golang.org/x/talks/2013/bestpractices/funcdraw/drawer"
        "golang.org/x/talks/2013/bestpractices/funcdraw/parser"
    )
    
    // Parse the text into an executable function.
    f, err := parser.Parse(text)
    if err != nil {
        log.Fatalf("parse %q: %v", text, err)
    }
    // Create an image plotting the function.
    m := drawer.Draw(f, *width, *height, *xmin, *xmax)
    // Encode the image into the standard output.
    err = png.Encode(os.Stdout, m)
    if err != nil {
        log.Fatalf("encode image: %v", err)
    }
    

    拆分成

    Parsing
    type ParsedFunc struct {
        text  string
        eval func(float64) float64
    }
    func Parse(text string) (*ParsedFunc, error) {
        f, err := parse(text)
        if err != nil {
            return nil, err
        }
        return &ParsedFunc{text: text, eval: f}, nil
    }
    
    func (f *ParsedFunc) Eval(x float64) float64 { return f.eval(x) }
    func (f *ParsedFunc) String() string              { return f.text }
    
    Drawing
    import (
        "image"
     
       "golang.org/x/talks/2013/bestpractices/funcdraw/parser"
    )
    
    // Draw draws an image showing a rendering of the passed Function
    func DrawParsedFunc(f parser.ParsedFunc) image.Image {
    
    Avoid dependency by using an interface
    import "image"
    // Function represent a drawable mathematical function.
    type Function interface {
        Eval(float64) float64
    }
    
    // Draw draws an image showing a rendering of the passed Function
    func Draw(f Function) image.Image {
    
    testing

    用接口而不是一个实际类型的好处也包括方便测试

    package drawer
    
    import (
        "math"
        "testing"
    )
    
    type TestFunc func(float64) float64
    func (f TestFunc) Eval(x float64) float64 { return f(x) }
    var (
        ident = TestFunc(func(x float64) float64 { return x })
        sin    = TestFunc(math.Sin)
    )
    
    func TestDraw_Ident(t *testing.T) {
        m := Draw(ident)
        // Verify obtained image.
    ...
    

    10. Avoid concurrency in your API

    API里就不用并行了
    以下是不好的例子

    func doConcurrently(job string, err chan error) {
        go func() {
            fmt.Println("doing job", job)
            time.Sleep(1 * time.Second)
            err <- errors.New("something went wrong!")
        }()
    }
    func main() {
        jobs := []string{"one", "two", "three"}
        errc := make(chan error)
        for _, job := range jobs {
            doConcurrently(job, errc)
        }
        for _ = range jobs {
            if err := <-errc; err != nil {
                fmt.Println(err)
            }
        }
    }
    

    以下是好的例子

    func do(job string) error {
        fmt.Println("doing job", job)
        time.Sleep(1 * time.Second)
        return errors.New("something went wrong!")
    }
    
    func main() {
        jobs := []string{"one", "two", "three"}
    
        errc := make(chan error)
        for _, job := range jobs {
            go func(job string) {
                errc <- do(job)
            }(job)
        }
        for _ = range jobs {
            if err := <-errc; err != nil {
                fmt.Println(err)
            }
        }
    }
    

    开发同步的API,这样并行调用它们是很简单的。

    11. Use goroutines to manage state

    Use a chan or a struct with a chan to communicate with a goroutine
    package main
    import (
        "fmt"
        "time"
    )
    
    type Server struct{ quit chan bool }
    func NewServer() *Server {
        s := &Server{make(chan bool)}
        go s.run()
        return s
    }
    
    func (s *Server) run() {
        for {
            select {
            case <- s.quit:
                fmt.Println("finishing task")
                time.Sleep(time.Second)
                fmt.Println("task done")
                s.quit <- true
                return
            case <- time.After(time.Second):
                fmt.Println("running task")
            }
        }
    }
    
    func (s *Server) Stop() {
        fmt.Println("server stopping")
        s.quit <- true
        <- s.quit
        fmt.Println("server stopped")
    }
    
    func main() {
        s := NewServer()
        time.Sleep(2 * time.Second)
        s.Stop()
    }
    

    12. Avoid goroutine leaks

    Use a chan or a struct with a chan to communicate with a goroutine

    func sendMsg(msg, addr string) error {
        conn, err := net.Dial("tcp", addr)
        if err != nil  {
            return err
        }
        defer conn.Close()
        _, err = fmt.Fprint(conn, msg)
        return err
    }
    
    func main() {
        addr := []string{"localhost:8080",  "http://google.com"}
        err := broadcastMsg("hi", addr)
    
        time.Sleep(time.Second)
    
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println("everything went fine")
    }
    
    func broadcastMsg(msg string, addrs []string) error {
        errc := make(chan error)
        for _, addr := range addrs {
            go func(addr string) {
                errc <- sendMsg(msg, addr)
                fmt.Println("done")
            }(addr)
        }
        for _ = range addrs {
            if err := <- errc; err != nil {
                return err
            }
        }
        return nil
    }
    

    上面的问题是:

    • the goroutine is blocked on the chan write
    • the goroutine holds a reference to the chan
    • the chan will never be garbage collected
      修正:(给 errc 加了一个长度
    func broadcastMsg(msg string, addrs []string) error {
        errc := make(chan error, len(addrs))
        for _, addr := range addrs {
            go func(addr string) {
                errc <- sendMsg(msg, addr)
                fmt.Println("done")
            }(addr)
        }
        for _ = range addrs {
            if err := <- errc; err != nil {
                return err
            }
        }
        return nil
    }
    

    这里还有个问题,如果我们不知道 addrs 的长度,或者说无法预测 errc 的长度,要怎么办?
    改进:(引入 quit channal)

    func broadcastMsg(msg string, addrs []string) error {
        errc := make(chan error)
        quit := make(chan struct{})
        defer close(quit)
    
        for _, addr := range addrs {
            go func(addr string) {
                select {
                case errc <- sendMsg(msg, addr):
                    fmt.Println("done")
                case <-quit:
                    fmt.Println("quit")
                }
            }(addr)
        }
        for _ = range addrs {
            if err := <- errc; err != nil {
                return err
            }
        }
        return nil
    }
    

    这里使用了 select 来block channel

    The select statement lets a goroutine wait on multiple communication operations.
    A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

    OVER

    相关文章

      网友评论

          本文标题:2018-03-13 Twelve Go Best Practi

          本文链接:https://www.haomeiwen.com/subject/zbnzfftx.html