1、Go 语言 12 条最佳实践本文来自 Google 工程师 Francesc Campoy Flores 分享的幻灯片。内容包括:代码组织、API、并发最佳实践和一些推荐的相关资源。最佳实践维基百科的定义是:“最佳实践是一种方法或技术,其结果始终优于其他方式。”写Go代码的目标就是: 简洁 可读性强 可维护性好样例代码123456789101112131415161718192021type Gopher struct Name stringAge int32FurColor color.Colorfunc (g *Gopher) DumpBinary(w io.Writer) error e
2、rr := 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 errreturn errreturn err避免嵌套的处理错误123456789101112131415func (
3、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 errerr = binary.Write(w, binary.LittleEndian, g.Age)if err != nil return errreturn binary.Write(w, binary.LittleEndian, g.Fu
4、rColor)减少嵌套意味着提高代码的可读性尽可能避免重复功能单一,代码更简洁123456789101112131415161718192021type binWriter struct w io.Writererr error/ Write writes a value into its writer using little endian.func (w *binWriter) Write(v interface) if w.err != nil returnw.err = binary.Write(w.w, binary.LittleEndian, v)func (g *Gopher)
5、DumpBinary(w io.Writer) error bw := &binWriterw: wbw.Write(int32(len(g.Name)bw.Write(byte(g.Name)bw.Write(g.Age)bw.Write(g.FurColor)return bw.err使用类型推断来处理特殊情况12345678910111213141516171819202122/ Write writes a value into its writer using little endian.func (w *binWriter) Write(v interface) if w.err
6、!= nil returnswitch 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 := &binWriterw: wbw.Write(g.Name)bw.Write(g.Age)bw.Write(g.FurColor)return bw.err类型推断的变量声明要短1234567891
7、0111213/ Write write the given value into the writer using little endian.func (w *binWriter) Write(v interface) if w.err != nil returnswitch v := v.(type) case string:w.Write(int32(len(v)w.Write(byte(v)default:w.err = binary.Write(w.w, binary.LittleEndian, v)函数适配器123456789101112131415161718192021222
8、3242526272829303132333435363738394041424344func 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)returnerr = doThat()if err != ni
9、l http.Error(w, err.Error(), http.StatusInternalServerError)log.Printf(handling %q: %v, r.RequestURI, err)returnfunc init() http.HandleFunc(/, errorHandler(betterHandler)func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc return func(w http.ResponseWriter, r *http.Re
10、quest) 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 !=
11、nil return fmt.Errorf(doing that: %v, err)return nil如何组织代码将重要的代码放前面版权信息,构建信息,包说明文档Import 声明,相关的包连起来构成组,组与组之间用空行隔开.。1234567import (fmtiolog)接下来代码以最重要的类型开始,以工具函数和类型结束。如何编写文档包名之前要写相关文档123/ Package playground registers an HTTP handler at /compile that/ proxies requests to the golang.org playground servi
12、ce.package playground导出的标识符(译者按:大写的标识符为导出标识符)会出现在godoc中,所以要正确的编写文档。123456789/ 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, an
13、d company/ without the contact details.func (p *Author) TextElem() (elems Elem) 生成的文档示例Gocode: 文档化Go代码越简洁越好或者长代码往往不是最好的.试着使用能自解释的最短的变量名. 用MarshalIndent,别用MarshalWithIndentation.别忘了包名会出现在你选择的标识符前面 In packageencoding/jsonwe find the typeEncoder, notJSONEncoder. It is referred asjson.Encoder.有多个文件的包需要将
14、一个包分散到多个文件中吗? 避免行数非常多的文件标准库中net/http包有47个文件,共计 15734 行. 拆分代码并测试net/http/cookie.go和net/http/cookie_test.go 都是http包的一部分.测试代码只有在测试时才会编译. 多文件包的文档编写如果一个包中有多个文件, 可以很方便的创建一个doc.go文件,包含包文档信息.让包可以”go get”到一些包将来可能会被复用,另外一些不会.定义了一些网络协议的包可能会在开发一个可执行命令时复用.接口你需要什么让我们以之前的Gopher类型为例12345type Gopher struct Name stri
15、ngAge int32FurColor color.Color我们可以定义这个方法1func (g *Gopher) DumpToFile(f *os.File) error 但是使用一个具体的类型会让代码难以测试,因此我们使用接口.1func (g *Gopher) DumpToReadWriter(rw io.ReadWriter) error 进而,由于使用的是接口,我们可以只请求我们需要的.1func (g *Gopher) DumpToWriter(f io.Writer) error 让独立的包彼此独立12345678910111213141516171819import ()/
16、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 lo
17、g.Fatalf(encode image: %v, err)解析123456789101112131415type ParsedFunc struct text stringeval func(float64) float64func Parse(text string) (*ParsedFunc, error) f, err := parse(text)if err != nil return nil, errreturn &ParsedFunctext: text, eval: f, nilfunc (f *ParsedFunc) Eval(x float64) float64 retu
18、rn f.eval(x) func (f *ParsedFunc) String() string return f.text 描绘12345678import (image)/ 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 in
19、terface Eval(float64) float64/ Draw draws an image showing a rendering of the passed Function.func Draw(f Function) image.Image 测试使用接口而不是具体类型让测试更简洁.12345678910111213141516171819package drawerimport (mathtesting)type TestFunc func(float64) float64func (f TestFunc) Eval(x float64) float64 return f(x)
20、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.在接口中避免并发123456789101112131415161718192021func doConcurrently(job string, err chan error) go func() fmt.Println(doing job, job)time.Sleep(1 * time.
21、Second)err - errors.New(something went wrong!)()func main() jobs := stringone, two, threeerrc := make(chan error)for _, job := range jobs doConcurrently(job, errc)for _ = range jobs if err := -errc; err != nil fmt.Println(err)如果我们想串行的使用它会怎样?123456789101112131415161718192021func do(job string) error
22、fmt.Println(doing job, job)time.Sleep(1 * time.Second)return errors.New(something went wrong!)func main() jobs := stringone, two, threeerrc := 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)暴露同步的接口,这样异步调用
23、这些接口会简单.并发的最佳实践使用goroutines管理状态使用chan或者有chan的结构体和goroutine通信1234567891011121314151617181920212223242526272829303132333435type Server struct quit chan bool func NewServer() *Server s := &Servermake(chan bool)go s.run()return sfunc (s *Server) run() for select case -s.quit:fmt.Println(finishing task)time.Sleep(time.Second)fmt.Println(task done)s.quit - truereturncase -time.After(tim
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2