目录
Go 箴言
interface{}unsafepanic()
Go 之禅
- 每个 package 实现单一的目的
- 显式处理错误
- 尽早返回,而不是使用深嵌套
- 让调用者处理并发(带来的问题)
- 在启动一个 goroutine 时,需要知道何时它会停止
- 避免 package 级别的状态
- 简单很重要
- 编写测试以锁定 package API 的行为
- 如果你觉得慢,先编写 benchmark 来证明
- 适度是一种美德
- 可维护性
代码
使用 go fmt 格式化
让团队一起使用官方的 Go 格式工具,不要重新发明轮子。
尝试减少代码复杂度。 这将帮助所有人使代码易于阅读。
多个 if 语句可以折叠成 switch
// NOT BAD if foo() { // ... } else if bar == baz { // ... } else { // ... } // BETTER switch { case foo(): // ... case bar == baz: // ... default: // ... }
用 chan struct{} 来传递信号, chan bool 表达的不够清楚
chan bool
type Service struct { deleteCh chan bool // what does this bool mean? }
chan struct {}struct {}
type Service struct { deleteCh chan struct{} // ok, if event than delete something. }
30 * time.Second 比 time.Duration(30) * time.Second 更好
你不需要将无类型的常量包装成类型,编译器会找出来。
另外最好将常量移到第一位:
// BAD delay := time.Second * 60 * 24 * 60 // VERY BAD delay := 60 * time.Second * 60 * 24 // GOOD delay := 24 * 60 * 60 * time.Second
用 time.Duration 代替 int64 + 变量名
// BAD var delayMillis int64 = 15000 // GOOD var delay time.Duration = 15 * time.Second
按类型分组 const 声明,按逻辑和/或类型分组 var
// BAD const ( foo = 1 bar = 2 message = "warn message" ) // MOSTLY BAD const foo = 1 const bar = 2 const message = "warn message" // GOOD const ( foo = 1 bar = 2 ) const message = "warn message"
var
Stringerdefer
defer func() { err := ocp.Close() if err != nil { rerr = err } }()
checkErrpanic()os.Exit()
package main type Status = int type Format = int // remove `=` to have type safety const A Status = 1 const B Format = 1 func main() { println(A == B) }
_ = f()f()a := []T{}for _, c := range a[3:7] {...}for i := 3; i < 7; i++ {...}_
func f(a int, _ string) {}
time.Beforetime.Aftertime.Subctxfunc foo(ctx Context, ...)
func f(a int, b int, s string, p string)
func f(a, b int, s, p string)
var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } // Output: // [] 0 0 // nil!
var a []string b := []string{} fmt.Println(reflect.DeepEqual(a, []string{})) fmt.Println(reflect.DeepEqual(b, []string{})) // Output: // false // true
<><=>=
value := reflect.ValueOf(object) kind := value.Kind() if kind >= reflect.Chan && kind <= reflect.Slice { // ... }
%+vstruct{}
func f1() { var a, b struct{} print(&a, "\n", &b, "\n") // Prints same address fmt.Println(&a == &b) // Comparison returns false } func f2() { var a, b struct{} fmt.Printf("%p\n%p\n", &a, &b) // Again, same address fmt.Println(&a == &b) // ...but the comparison returns true }
errors.Wrap(err, "additional message to a given error")rangefor i := range afor i, v := range &aafor i, v := range aavalue := map["no_key"]value, ok := map["no_key"]os.MkdirAll(root, 0700)os.FileModeiota
const ( _ = iota testvar // testvar 将是 int 类型 )
vs
type myType int const ( _ myType = iota testvar // testvar 将是 myType 类型 )
不要在你不拥有的结构上使用 encoding/gob
在某些时候,结构可能会改变,而你可能会错过这一点。因此,这可能会导致很难找到 bug。
不要依赖于计算顺序,特别是在 return 语句中。
// BAD return res, json.Unmarshal(b, &res) // GOOD err := json.Unmarshal(b, &res) return res, err
防止结构体字段用纯值方式初始化,添加 _ struct {} 字段:
type Point struct { X, Y float64 _ struct{} // to prevent unkeyed literals }
Point {X:1,Y:1}Point {1,1}
./file.go:1:11: too few values in Point literal
_ struct{}go vet
为了防止结构比较,添加 func 类型的空字段
type Point struct { _ [0]func() // unexported, zero-width non-comparable field X, Y float64 }
http.HandlerFunchttp.Handler
http.HandlerFunchttp.Handler
移动 defer 到顶部
这可以提高代码可读性并明确函数结束时调用了什么。
JavaScript 解析整数为浮点数并且你的 int64 可能溢出
json:"id,string"
type Request struct { ID int64 `json:"id,string"` }
并发
sync.Onceselect{}math/randfunc NewSource(seed int64) SourcelockedSource
性能
deferdefer r.Body.Close()
b := a[:0] for _, x := range a { if f(x) { b = append(b, x) } }
_ = b [7]
time.Timetime.Locationtime.Timeregexp.MustCompileregexp.Compilefunc initfmt.Sprintffmt.Sprintf("%s%s", var1, var2)fmt.Sprintf("%x", var)hex.EncodeToStringstrconv.FormatInt(var, 16)io.Copy(ioutil.Discard, resp.Body)
res, _ := client.Do(req) io.Copy(ioutil.Discard, res.Body) defer res.Body.Close()
** 不要在循环中使用 defer,否则会导致内存泄露
- 因为这些 defer 会不断地填满你的栈(内存)
** 不要忘记停止 ticker, 除非你需要泄露 channel
ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
** 用自定义的 marshaler 去加速 marshaler 过程
- 但是在使用它之前要进行定制!
func (entry Entry) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") first := true for key, value := range entry { jsonValue, err := json.Marshal(value) if err != nil { return nil, err } if !first { buffer.WriteString(",") } first = false buffer.WriteString(key + ":" + string(jsonValue)) } buffer.WriteString("}") return buffer.Bytes(), nil }
sync.Mapsync.Pool
// noescape hides a pointer from escape analysis. noescape is // the identity function but escape analysis doesn't think the // output depends on the input. noescape is inlined and currently // compiles down to zero instructions. //go:nosplit func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) }
m := (*map[int]int)(atomic.LoadPointer(&ptr))
for k := range m { delete(m, k) }
- 分配新的
m = make(map[int]int)
构建
go build -ldflags="-s -w" ...// +build integrationgo test -v --tags integration .CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latesttravis 1diff -u <(echo -n) <(gofmt -d .)
测试
package_testpackagego test -short
func TestSomething(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } }
- ** 根据系统架构跳过测试
if runtime.GOARM == "arm" { t.Skip("this doesn't work under ARM") }
testing.AllocsPerRungo test -test.bench=. -count=20
工具
gofmt -w -l -r "panic(err) -> log.Error(err)" .go listgo list -f '{{ .Imports }}' packagego list -f '{{ .Deps }}' packagebenchstatgo mod why -m go.modGOGC=off go build ...GODEBUGgo mod why -m go.mod
其他
- ** dump goroutines
go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGQUIT) buf := make([]byte, 1<<20) for { <-sigs stacklen := runtime.Stack(buf, true) log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n" , buf[:stacklen]) } }()
var _ io.Reader = (*MyFastReader)(nil)
var hits struct { sync.Mutex n int } hits.Lock() hits.n++ hits.Unlock()
httputil.DumpRequestruntime.Callermap[string]interface{}{}CDPATHcd github.com/golang/gobashrcexport CDPATH=$CDPATH:$GOPATH/src[]string{"one", "two", "three"}[rand.Intn(3)]
总结
您可能感兴趣的文章: