`
sunxboy
  • 浏览: 2821798 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Go 编程语言的 12 条最佳实践

 
阅读更多

最佳实践

维基百科的定义是:

“最佳实践是一种方法或技术,其结果始终优于其他方式。”

写Go代码的目标就是:

  • 简洁
  • 可读性强
  • 可维护性好

样例代码

 

type Gopher struct {
    Name     string
    Age      int32
    FurColor color.Color
}

func (g *Gopher) DumpBinary(w io.Writer) error {
    err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err == nil {
        _, err := w.Write([]byte(g.Name))
        if err == nil {
            err := binary.Write(w, binary.LittleEndian, g.Age)
            if err == nil {
                return binary.Write(w, binary.LittleEndian, g.FurColor)
            }
            return err
        }
        return err
    }
    return err
}

 

 

避免嵌套的处理错误

 

func (g *Gopher) DumpBinary(w io.Writer) error {
    err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err != nil {
        return err
    }
    _, err = w.Write([]byte(g.Name))
    if err != nil {
        return err
    }
    err = binary.Write(w, binary.LittleEndian, g.Age)
    if err != nil {
        return err
    }
    return binary.Write(w, binary.LittleEndian, g.FurColor)
}

 减少嵌套意味着提高代码的可读性

 

尽可能避免重复

功能单一,代码更简洁

 

type binWriter struct {
    w   io.Writer
    err error
}

// Write writes a value into its writer using little endian.
func (w *binWriter) Write(v interface{}) {
    if w.err != nil {
        return
    }
    w.err = binary.Write(w.w, binary.LittleEndian, v)
}

func (g *Gopher) DumpBinary(w io.Writer) error {
    bw := &binWriter{w: w}
    bw.Write(int32(len(g.Name)))
    bw.Write([]byte(g.Name))
    bw.Write(g.Age)
    bw.Write(g.FurColor)
    return bw.err
}

 

 

使用类型推断来处理特殊情况

 

// Write writes a value into its writer using little endian.
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))
    default:
        w.err = binary.Write(w.w, binary.LittleEndian, v)
    }
}

func (g *Gopher) DumpBinary(w io.Writer) error {
    bw := &binWriter{w: w}
    bw.Write(g.Name)
    bw.Write(g.Age)
    bw.Write(g.FurColor)
    return bw.err
}

 

 

类型推断的变量声明要短

 

// Write write the given value into the writer using little endian.
func (w *binWriter) Write(v interface{}) {
    if w.err != nil {
        return
    }
    switch v := v.(type) {
    case string:
        w.Write(int32(len(v)))
        w.Write([]byte(v))
    default:
        w.err = binary.Write(w.w, binary.LittleEndian, v)
    }
}

 

 

函数适配器

 

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

func init() {
    http.HandleFunc("/", errorHandler(betterHandler))
}

func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        err := f(w, r)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            log.Printf("handling %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
}

 

如何组织代码

 

将重要的代码放前面

版权信息,构建信息,包说明文档

Import 声明,相关的包连起来构成组,组与组之间用空行隔开。

 

import (
    "fmt"
    "io"
    "log"

    "code.google.com/p/go.net/websocket"
)

 接下来代码以最重要的类型开始,以工具函数和类型结束。

 

如何编写文档

包名之前要写相关文档

 

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

 导出的标识符(译者按:大写的标识符为导出标识符)会出现在 godoc中,所以要正确的编写文档。

 

 

// 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) {

 生成的文档示例

 

Gocode: 文档化Go代码

 

越简洁越好

或者 长代码往往不是最好的.

试着使用能自解释的最短的变量名.

  • 用 MarshalIndent ,别用 MarshalWithIndentation.

别忘了包名会出现在你选择的标识符前面

  • In package encoding/json we find the type Encoder, not JSONEncoder.
  • It is referred as json.Encoder.

 

有多个文件的包

需要将一个包分散到多个文件中吗?

  • 避免行数非常多的文件

标准库中 net/http 包有47个文件,共计 15734 行.

  • 拆分代码并测试

net/http/cookie.go 和 net/http/cookie_test.go 都是 http 包的一部分.

测试代码 只有 在测试时才会编译.

  • 多文件包的文档编写

如果一个包中有多个文件, 可以很方便的创建一个 doc.go 文件,包含包文档信息.

让包可以”go get”到

一些包将来可能会被复用,另外一些不会.

定义了一些网络协议的包可能会在开发一个可执行命令时复用.

github.com/bradfitz/camlistore

 

接口

你需要什么

让我们以之前的Gopher类型为例

 

type Gopher struct {
    Name     string
    Age      int32
    FurColor color.Color
}

 我们可以定义这个方法

 

 

func (g *Gopher) DumpToFile(f *os.File) error {

 但是使用一个具体的类型会让代码难以测试,因此我们使用接口.

 

 

func (g *Gopher) DumpToReadWriter(rw io.ReadWriter) error {

 进而,由于使用的是接口,我们可以只请求我们需要的.

 

 

func (g *Gopher) DumpToWriter(f io.Writer) error {

 

 

让独立的包彼此独立

 

import (
    "code.google.com/p/go.talks/2013/bestpractices/funcdraw/drawer"
    "code.google.com/p/go.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)
    }

 

 

解析

 

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 }

 

 

描绘

 

import (
    "image"

    "code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
)

// Draw draws an image showing a rendering of the passed ParsedFunc.
func DrawParsedFunc(f parser.ParsedFunc) image.Image {

 使用接口来避免依赖.

 

 

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 {

 

 

测试

使用接口而不是具体类型让测试更简洁.

 

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.

 

 

在接口中避免并发

 

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)
        }
    }
}

 暴露同步的接口,这样异步调用这些接口会简单.

 

并发的最佳实践

使用goroutines管理状态

使用chan或者有chan的结构体和goroutine通信

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()
}

 

使用带缓存的chan,来避免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
}

 

  • goroutine阻塞在chan写操作
  • goroutine保存了一个chan的引用
  • chan永远不会垃圾回收
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
}

 如果我们不能预测channel的容量呢?

使用quit chan避免goroutine内存泄漏

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
}

 

12条最佳实践

1. 避免嵌套的处理错误
2. 尽可能避免重复
3. 将重要的代码放前面
4. 为代码编写文档
5. 越简洁越好
6. 讲包拆分到多个文件中
7. 让包”go get”到
8. 按需请求
9. 让独立的包彼此独立
10. 在接口中避免并发
11. 使用goroutine管理状态
12. 避免goroutine内存泄漏

一些链接

资源

其他演讲

  • 用go做词法扫描 video
  • 并发不是并行 video
  • Go并发模式 video
  • Go高级并发模式 video

 

 

分享到:
评论

相关推荐

    Go语言最佳实践

    对Go语言并发编程探讨深入,讲解细腻,同时这本书也非常适合作为Go语言的入门教材,即便是对Go语言了解不深的人也能从中获益。书中标例非常有价值,它们贴切地展现了用Go语言进行编程的方法和技巧。

    是一本全面介绍 Go 编程语言的权威指南 它涵盖了 Go 语言的语法、特性、标准库和最佳实践,适合新手和有经验的开发者阅读

    Introduction: 介绍了 Go 语言的背景和起源,以及 Go 语言的设计目标和...Concurrency: 重点介绍了 Go 语言的并发编程模型和工具,包括 goroutines、channels、select 语句等,并讲解了如何使用这些工具构建并发程序。

    陈硕 网络编程实践

    本课程从网络编程的基本原理入手,基于Linux系统平台,通过大量的实践案例,帮助学习者掌握服务端网络编程的核心机制、编程模型、以及最佳实践。同时交叉演练使用C++,Python和Go语言进行编码。 课程亮点 1.Google...

    golang并发编程实战

    golang并发编程实战,channel原理实现过程,最佳实践,goroutine原理和go并发的实战,从实际出发展开对go并发编程的说明和示例,充分说明go语言再并发编程的优势

    火爆,Github标星240K的编程学习路线图,适合所有程序员!

    推荐一个涵盖开发、运维、产品设计的学习路线图,在Github已经start超过240K,包括各门编程语言! 该项目涵盖了非常全面的学习路线图: 前端路线图 后端路线图 ASP.NET Core路线图 Vue 路线图 JavaScript 路线...

    go-magic:用于 Go 编程语言的 libmagic 的简单接口

    为 Go 编程语言的提供简单的接口。 目录 贡献 有关设置开发环境的最佳实践和说明,请参见 。 版本控制 该项目遵循语义版本控制。 有关可用版本,请参阅此存储库上的标签。 作者 Krzysztof Wilczyński ( kw@linux....

    MasteringGO中文版.pdf

    它从基础知识到实践经验,全方位地介绍了Go语言的各个方面,并提供了许多代码示例和最佳实践,使读者能够更好地理解和应用这门语言。 中文版的Mastering Go为中文读者提供了更加便捷的阅读体验,同时也弥补了在国内...

    Go语言实战

     第 2 章引导你完成一个完整的 Go 程序,并教你 Go 作为一门编程语言必须提供的特性。  第 3 章介绍打包的概念,以及搭建 Go 工作空间和开发环境的最佳实践。这一章还会展 示如何使用 Go 语言的工具链,包括获取...

    javasnmp源码-go-best-practice:最佳实践

    终于翻译完了Dave大神的这一篇《Go语言最佳实践》 耗时两周的空闲时间 翻译的同时也对Go语言的开发与实践有了更深层次的了解 有兴趣的同学可以翻阅Dave的另一篇博文(第六章节也会提到) 同时在这里也推荐一个Telegram...

    Go容器化微服务系统实战(全套视频+资料).rar

    教程内容涵盖了从微服务架构设计到Docker容器化部署的全过程,通过实例演示和详细讲解,带领学习者逐步掌握Go语言在微服务开发中的应用技巧和最佳实践。 适用人群: 对Go语言和容器化微服务开发感兴趣的开发者和...

    developer-roadmap:开发者学习路线图

    Go语言高级编程内容适合进阶 go语言原本欧神出品,虽然号称进度只有9.9%/ 100%,但不阻塞它的优秀,值得一看 golang设计模式设计模式Golang实现,《研磨设计模式》的golang实现 Go实战开发作者是著名的Go开源项目...

    最新IT书籍G3期

    《Go语言并发之道》_于畅等译_2018-12-01 《Java 8函数式编程》_王群锋译_2015-04-01 《Java JDK 9学习笔记》_林信良_2018-06-01 《Neo4j 3.x入门经典》_张帜等_2019-02-01 《分布式系统常用技术及案例分析(第2版)...

    LetsGo:Golang 小视频教程,零散时间学习 Golang

    目的 通过连载短视频和文章的形式帮助有一定其他语言编程基础的人快速学习和...本电子书主要面向有一定其他编程语言开发经验的开发者快速上手 go,不会涉及到一些非常具体和细节的问题,非零基础教程。 比如如何下载 ID

    很棒的惯用代码资源列表。 Rust:crab :, Go,Ruby:gem :, Pony:horse :, Ocaml:camel :, Erlang等-Python开发

    语言特定资源Rust Go Python Ruby OCaml Haskell Erlang Elixir Common Lisp C#Java Scala D Clojure Nim Pony JavaScript C ++ Dart General API设计和工具这是语言特定资源的集合,可用于查找遵循的最佳实践由...

    Go-Programming-Cookbook-Second-Edition:Packt出版的《 Go Programming Cookbook –第二版》

    Go(或Golang)是Google开发的一种静态类型的编程语言。它以其庞大的标准库而闻名,它还提供诸如垃圾收集,类型安全,动态键入功能以及其他内置类型之类的功能。本书将在实现Go功能以构建自己的应用程序时作为参考...

    GO新闻-非原创

    GoCN每日新闻(2019-12-27) 1. Go 开发关键技术指南 | Go 面向失败编程 ...3. Golang 错误处理最佳实践 https://medium.com/@dche423/golang-error-handling-best-practice-cn-42982bd7

    fabric-application-examples:Hyperledger Fabric的示例,最佳实践和开发人员工具

    面料应用实例 测试和演示存储库,显示编程模型的工作方式;...目的是添加其他语言(Golang,Java,Typescript .net)的匹配/等效合同和应用程序 IBM或Hyperledger Project均未正式支持或认可此存储库中的任何内容

    ideaIU-2019.1.3

    IDEA IU 最佳实践:代码审查和调试技巧,它支持各种编程语言,包括Java、Kotlin、Scala、Golang等。 智能代码编辑器:IdeaIU具有智能的代码编辑功能,包括代码补全、语法高亮、代码导航和自动重构等。它能够根据上...

    go-test-it-yourself

    然后,他将深入研究Go编程语言必须提供的测试功能,并提供具有有效代码库及其关联硬件的动手示例。 在研讨会结束时,您将通过执行数据转换和建立小型气象站来了解测试工具和技术。 您还将学习有关如何组织测试以及...

Global site tag (gtag.js) - Google Analytics