Go 语言 12 条最佳实践.docx
《Go 语言 12 条最佳实践.docx》由会员分享,可在线阅读,更多相关《Go 语言 12 条最佳实践.docx(19页珍藏版)》请在冰点文库上搜索。
Go语言12条最佳实践
本文来自Google工程师FrancescCampoyFlores分享的幻灯片。
内容包括:
代码组织、API、并发最佳实践和一些推荐的相关资源。
最佳实践
维基百科的定义是:
“最佳实践是一种方法或技术,其结果始终优于其他方式。
”
写Go代码的目标就是:
∙简洁
∙可读性强
∙可维护性好
样例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typeGopherstruct{
Name string
Age int32
FurColorcolor.Color
}
func(g*Gopher)DumpBinary(wio.Writer)error{
err:
=binary.Write(w,binary.LittleEndian,int32(len(g.Name)))
iferr==nil{
_,err:
=w.Write([]byte(g.Name))
iferr==nil{
err:
=binary.Write(w,binary.LittleEndian,g.Age)
iferr==nil{
returnbinary.Write(w,binary.LittleEndian,g.FurColor)
}
returnerr
}
returnerr
}
returnerr
}
避免嵌套的处理错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func(g*Gopher)DumpBinary(wio.Writer)error{
err:
=binary.Write(w,binary.LittleEndian,int32(len(g.Name)))
iferr!
=nil{
returnerr
}
_,err=w.Write([]byte(g.Name))
iferr!
=nil{
returnerr
}
err=binary.Write(w,binary.LittleEndian,g.Age)
iferr!
=nil{
returnerr
}
returnbinary.Write(w,binary.LittleEndian,g.FurColor)
}
减少嵌套意味着提高代码的可读性
尽可能避免重复
功能单一,代码更简洁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typebinWriterstruct{
w io.Writer
errerror
}
//Writewritesavalueintoitswriterusinglittleendian.
func(w*binWriter)Write(vinterface{}){
ifw.err!
=nil{
return
}
w.err=binary.Write(w.w,binary.LittleEndian,v)
}
func(g*Gopher)DumpBinary(wio.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)
returnbw.err
}
使用类型推断来处理特殊情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Writewritesavalueintoitswriterusinglittleendian.
func(w*binWriter)Write(vinterface{}){
ifw.err!
=nil{
return
}
switchv.(type){
casestring:
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(wio.Writer)error{
bw:
=&binWriter{w:
w}
bw.Write(g.Name)
bw.Write(g.Age)
bw.Write(g.FurColor)
returnbw.err
}
类型推断的变量声明要短
1
2
3
4
5
6
7
8
9
10
11
12
13
//Writewritethegivenvalueintothewriterusinglittleendian.
func(w*binWriter)Write(vinterface{}){
ifw.err!
=nil{
return
}
switchv:
=v.(type){
casestring:
w.Write(int32(len(v)))
w.Write([]byte(v))
default:
w.err=binary.Write(w.w,binary.LittleEndian,v)
}
}
函数适配器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
funcinit(){
http.HandleFunc("/",handler)
}
funchandler(whttp.ResponseWriter,r*http.Request){
err:
=doThis()
iferr!
=nil{
http.Error(w,err.Error(),http.StatusInternalServerError)
log.Printf("handling%q:
%v",r.RequestURI,err)
return
}
err=doThat()
iferr!
=nil{
http.Error(w,err.Error(),http.StatusInternalServerError)
log.Printf("handling%q:
%v",r.RequestURI,err)
return
}
}
funcinit(){
http.HandleFunc("/",errorHandler(betterHandler))
}
funcerrorHandler(ffunc(http.ResponseWriter,*http.Request)error)http.HandlerFunc{
returnfunc(whttp.ResponseWriter,r*http.Request){
err:
=f(w,r)
iferr!
=nil{
http.Error(w,err.Error(),http.StatusInternalServerError)
log.Printf("handling%q:
%v",r.RequestURI,err)
}
}
}
funcbetterHandler(whttp.ResponseWriter,r*http.Request)error{
iferr:
=doThis();err!
=nil{
returnfmt.Errorf("doingthis:
%v",err)
}
iferr:
=doThat();err!
=nil{
returnfmt.Errorf("doingthat:
%v",err)
}
returnnil
}
如何组织代码
将重要的代码放前面
版权信息,构建信息,包说明文档
Import声明,相关的包连起来构成组,组与组之间用空行隔开.。
1
2
3
4
5
6
7
import(
"fmt"
"io"
"log"
"
)
接下来代码以最重要的类型开始,以工具函数和类型结束。
如何编写文档
包名之前要写相关文档
1
2
3
//PackageplaygroundregistersanHTTPhandlerat"/compile"that
//proxiesrequeststothegolang.orgplaygroundservice.
packageplayground
导出的标识符(译者按:
大写的标识符为导出标识符)会出现在 godoc中,所以要正确的编写文档。
1
2
3
4
5
6
7
8
9
//Authorrepresentsthepersonwhowroteand/orispresentingthedocument.
typeAuthorstruct{
Elem[]Elem
}
//TextElemreturnsthefirsttextelementsoftheauthordetails.
//Thisisusedtodisplaytheauthor'name,jobtitle,andcompany
//withoutthecontactdetails.
func(p*Author)TextElem()(elems[]Elem){
生成的文档示例
Gocode:
文档化Go代码
越简洁越好
或者 长代码往往不是最好的.
试着使用能自解释的最短的变量名.
∙用 MarshalIndent ,别用 MarshalWithIndentation.
别忘了包名会出现在你选择的标识符前面
∙Inpackage encoding/json wefindthetype Encoder,not JSONEncoder.
∙Itisreferredas json.Encoder.
有多个文件的包
需要将一个包分散到多个文件中吗?
∙避免行数非常多的文件
标准库中 net/http 包有47个文件,共计15734行.
∙拆分代码并测试
net/http/cookie.go 和 net/http/cookie_test.go 都是 http 包的一部分.
测试代码 只有 在测试时才会编译.
∙多文件包的文档编写
如果一个包中有多个文件,可以很方便的创建一个 doc.go 文件,包含包文档信息.
让包可以”goget”到
一些包将来可能会被复用,另外一些不会.
定义了一些网络协议的包可能会在开发一个可执行命令时复用.
接口
你需要什么
让我们以之前的Gopher类型为例
1
2
3
4
5
typeGopherstruct{
Name string
Age int32
FurColorcolor.Color
}
我们可以定义这个方法
1
func(g*Gopher)DumpToFile(f*os.File)error{
但是使用一个具体的类型会让代码难以测试,因此我们使用接口.
1
func(g*Gopher)DumpToReadWriter(rwio.ReadWriter)error{
进而,由于使用的是接口,我们可以只请求我们需要的.
1
func(g*Gopher)DumpToWriter(fio.Writer)error{
让独立的包彼此独立
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import(
"
"
)
//Parsethetextintoanexecutablefunction.
f,err:
=parser.Parse(text)
iferr!
=nil{
log.Fatalf("parse%q:
%v",text,err)
}
//Createanimageplottingthefunction.
m:
=drawer.Draw(f,*width,*height,*xmin,*xmax)
//Encodetheimageintothestandardoutput.
err=png.Encode(os.Stdout,m)
iferr!
=nil{
log.Fatalf("encodeimage:
%v",err)
}
解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typeParsedFuncstruct{
textstring
evalfunc(float64)float64
}
funcParse(textstring)(*ParsedFunc,error){
f,err:
=parse(text)
iferr!
=nil{
returnnil,err
}
return&ParsedFunc{text:
text,eval:
f},nil
}
func(f*ParsedFunc)Eval(xfloat64)float64{returnf.eval(x)}
func(f*ParsedFunc)String()string {returnf.text}
描绘
1
2
3
4
5
6
7
8
import(
"image"
"
)
//DrawdrawsanimageshowingarenderingofthepassedParsedFunc.
funcDrawParsedFunc(fparser.ParsedFunc)image.Image{
使用接口来避免依赖.
import"image"
//Functionrepresentadrawablemathematicalfunction.
typeFunctioninterface{
Eval(float64)float64
}
//DrawdrawsanimageshowingarenderingofthepassedFunction.
funcDraw(fFunction)image.Image{
测试
使用接口而不是具体类型让测试更简洁.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
packagedrawer
import(
"math"
"testing"
)
typeTestFuncfunc(float64)float64
func(fTestFunc)Eval(xfloat64)float64{returnf(x)}
var(
ident=TestFunc(func(xfloat64)float64{returnx})
sin =TestFunc(math.Sin)
)
funcTestDraw_Ident(t*testing.T){
m:
=Draw(ident)
//Verifyobtainedimage.
在接口中避免并发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
funcdoConcurrently(jobstring,errchanerror){
gofunc(){
fmt.Println("doingjob",job)
time.Sleep(1*time.Second)
err<-errors.New("somethingwentwrong!
")
}()
}
funcmain(){
jobs:
=[]string{"one","two","three"}
errc:
=make(chanerror)
for_,job:
=rangejobs{
doConcurrently(job,errc)
}
for_=rangejobs{
iferr:
=<-errc;err!
=nil{
fmt.Println(err)
}
}
}
如果我们想串行的使用它会怎样?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
funcdo(jobstring)error{
fmt.Println("doingjob",job)
time.Sleep(1*time.Second)
returnerrors.New("somethingwentwrong!
")
}
funcmain(){
jobs:
=[]string{"one","two","three"}
errc:
=make(chanerror)
for_,job:
=rangejobs{
gofunc(jobstring){
errc<-do(job)
}(job)
}
for_=rangejobs{
iferr:
=<-errc;err!
=nil{
fmt.Println(err)
}
}
}
暴露同步的接口,这样异步调用这些接口会简单.
并发的最佳实践
使用goroutines管理状态
使用chan或者有chan的结构体和goroutine通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typeServerstruct{quitchanbool}
funcNewServer()*Server{
s:
=&Server{make(chanbool)}
gos.run()
returns
}
func(s*Server)run(){
for{
select{
case<-s.quit:
fmt.Println("finishingtask")
time.Sleep(time.Second)
fmt.Println("taskdone")
s.quit<-true
return
case<-time.After(tim