本文来自 Google 工程师 Francesc Campoy Flores 分享的幻灯片。内容包括:代码组织、API、并发最佳实践和一些推荐的相关资源。 最佳实践维基百科的定义是: “最佳实践是一种方法或技术,其结果始终优于其他方式。” 写Go代码的目标就是: 样例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
}
|
避免嵌套的处理错误1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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)
}
|
减少嵌套意味着提高代码的可读性 尽可能避免重复功能单一,代码更简洁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | type binWriter struct {
w io.Writer
err error
}
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
}
|
使用类型推断来处理特殊情况1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
}
|
类型推断的变量声明要短1 2 3 4 5 6 7 8 9 10 11 12 13 | 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)
}
}
|
函数适配器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 | 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 声明,相关的包连起来构成组,组与组之间用空行隔开.。 1 2 3 4 5 6 7 | import (
"fmt"
"io"
"log"
"code.google.com/p/go.net/websocket"
)
|
接下来代码以最重要的类型开始,以工具函数和类型结束。 如何编写文档包名之前要写相关文档 导出的标识符(译者按:大写的标识符为导出标识符)会出现在 godoc 中,所以要正确的编写文档。 1 2 3 4 5 6 7 8 9 | type Author struct {
Elem []Elem
}
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类型为例 1 2 3 4 5 | type Gopher struct {
Name string
Age int32
FurColor color.Color
}
|
我们可以定义这个方法 1 | func (g *Gopher) DumpToFile(f *os.File) error {
|
但是使用一个具体的类型会让代码难以测试,因此我们使用接口. 1 | func (g *Gopher) DumpToReadWriter(rw io.ReadWriter) error {
|
进而,由于使用的是接口,我们可以只请求我们需要的. 1 | func (g *Gopher) DumpToWriter(f io.Writer) error {
|
让独立的包彼此独立1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import (
"code.google.com/p/go.talks/2013/bestpractices/funcdraw/drawer"
"code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
)
f, err := parser.Parse(text)
if err != nil {
log.Fatalf( "parse %q: %v" , text, err)
}
m := drawer.Draw(f, *width, *height, *xmin, *xmax)
err = png.Encode(os.Stdout, m)
if err != nil {
log.Fatalf( "encode image: %v" , err)
}
|
解析1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 }
|
描绘1 2 3 4 5 6 7 8 | import (
"image"
"code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
)
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 { 测试使用接口而不是具体类型让测试更简洁. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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)
|
在接口中避免并发1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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)
}
}
}
|
如果我们想串行的使用它会怎样? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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通信 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 | 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内存泄漏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 | 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永远不会垃圾回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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内存泄漏1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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内存泄漏 一些链接资源 其他演讲 谢谢 原文链接: Francesc Campoy Flores 翻译: 伯乐在线 - Codefor 译文链接: http://blog.jobbole.com/44608/ |