最佳实践
维基百科的定义是:
“最佳实践是一种方法或技术,其结果始终优于其他方式。”
写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) {
越简洁越好
或者 长代码往往不是最好的.
试着使用能自解释的最短的变量名.
- 用
MarshalIndent
,别用MarshalWithIndentation
.
别忘了包名会出现在你选择的标识符前面
- In package
encoding/json
we find the typeEncoder
, notJSONEncoder
.
- 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 首页 golang.org
- Go 交互式体验 tour.golang.org
其他演讲
相关推荐
Go 编程语言的 12 条最佳实践
以下是对"改善Go语言编程质量的50个有效实践"这一主题的详细解读,旨在帮助程序员提升Go语言编程水平。 1. **理解Go的核心特性**:熟悉Go的语法、类型系统、内存管理、goroutines和channels,这些是编写高效代码的...
通过这份笔记,读者不仅可以学习到Go语言的基本语法和编程技巧,还能了解到Go语言在实际开发中的最佳实践和陷阱,这对于成为一名合格的Go语言开发者至关重要。同时,由于是基于韩顺平老师的课程整理,因此笔记应该会...
遵守Go语言的最佳实践,例如避免全局变量、及时关闭文件描述符、正确处理网络连接等。熟悉并应用如OWASP(开放网络应用安全项目)提供的安全编码指南。 通过理解和应用这些安全编码实践,Go语言开发者能够编写出更...
对Go语言并发编程探讨深入,讲解细腻,同时这本书也非常适合作为Go语言的入门教材,即便是对Go语言了解不深的人也能从中获益。书中标例非常有价值,它们贴切地展现了用Go语言进行编程的方法和技巧。
适合人群:适用于有一定编程经验的技术人员,尤其是希望深入了解Go语言并提升编码能力的开发者。 使用场景及目标:帮助读者理解Go的设计理念和常见设计模式,学会如何编写高效、规范的Go代码。此外,书中还提供了...
理解如何正确地使用goroutine和channel是提升Go语言编程能力的关键。 2. **接口(Interface)**:Go语言的接口设计十分灵活,它是一种动态类型检查机制。通过接口,我们可以实现多态性,编写出更加通用和可复用的...
简介:Go 语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容 Go 语言: Go = C + Python ,
通过深入阅读"Go语言编程"这本书和分析配套的源代码,学习者不仅可以掌握Go语言的基本语法,还能了解到高级特性的使用和最佳实践,进一步提升Go语言的编程能力。对于网络编程的爱好者来说,Go语言提供了一个高效且...
Go语言基础语法 Go语言进阶特性 Go语言并发编程 Go语言网络编程 Go语言Web开发基础 Go语言Web框架实战 Go语言数据库交互 Go语言性能优化 Go语言错误处理与测试 Go语言构建工具与包管理 ...Go语言社区与最佳实践
标题“go语言编程”揭示了本文将讨论的主题是关于Go语言(通常被称为Golang)的编程知识。Go语言是一种编程语言,由Google在2009年发布,旨在提高多处理器系统应用程序的编程效率和速度。Go语言以其编译后的程序运行...
Introduction: 介绍了 Go 语言的背景和起源,以及 Go 语言的设计目标和...Concurrency: 重点介绍了 Go 语言的并发编程模型和工具,包括 goroutines、channels、select 语句等,并讲解了如何使用这些工具构建并发程序。
通过阅读《Go语言编程v2.0.pdf》和《go语言编程.pdf》这两本书籍,你将深入学习Go语言的各个方面,包括基础语法、高级特性、并发编程的最佳实践以及如何利用Go语言进行实际项目开发。不断实践和探索,你将成为Go语言...
Golang(Go语言)是一种开源的编程语言,它以其简洁的语法、高效的性能以及现代软件工程的最佳实践而受到广泛欢迎。在学习Golang时,可以重点关注以下几个方面: - **基础语法**:包括变量、数据类型、控制结构等...
《Go语言编程》这本书是学习Go语言的重要资源,其高清完整版电子书为读者提供了全面而深入的学习体验。 该书首先会引导读者了解Go语言的基础语法,包括变量、常量、数据类型、控制结构(如if语句、for循环、switch...
《Go语言编程》是针对Go语言的一份详细学习资料,主要涵盖了Go语言的基础知识、语法特性、并发模型、网络编程以及标准库等多个方面。Go语言,又称为Golang,是由Google开发的一种静态类型、编译型、并发型且具有垃圾...
在标签“Go开发-其它杂项”中,“Go开发”指的是使用Go语言进行软件开发的相关实践,包括但不限于语言特性、工具链、框架、库和最佳实践。而“其它杂项”可能涵盖与Go开发相关的各种非特定主题,如编程技巧、调试...
谷歌的Go语言,也被称为Golang,是由Google在2009年推出的一种开源编程语言。它被设计为简洁、高效、并发性好,并且具有内置的垃圾回收机制,使得开发人员可以更专注于编写可维护的代码。这个“谷歌GO语言教程PDF”...