interface{}
没有任何类型约束unsafe
包,不能保证能如期运行panic()
go fmt
格式化让团队一起使用官方的 Go 格式工具,不要重新发明轮子。
尝试减少代码复杂度。 这将帮助所有人使代码易于阅读。
// 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 = 1bar = 2message = "warn message"
)// MOSTLY BAD
const foo = 1
const bar = 2
const message = "warn message"// GOOD
const (foo = 1bar = 2
)const message = "warn message"
这个模式也适用于 var
。
** 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的
** 为整型常量值实现 Stringer
接口
** 检查 defer
中的错误
defer func() {err := ocp.Close()if err != nil {rerr = err}}()
** 不要在 checkErr
函数中使用 panic()
或 os.Exit()
** 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error
** 不要给枚举使用别名,因为这打破了类型安全
package maintype Status = inttype Format = int // remove `=` to have type safetyconst A Status = 1const B Format = 1func main() {println(A == B)}
**
如果你想省略返回参数,你最好表示出来
_ = f()
比 f()
更好**
我们用 a := []T{}
来简单初始化 slice
**
用 range 循环来进行数组或 slice 的迭代
for _, c := range a[3:7] {...}
比 for i := 3; i < 7; i++ {...}
更好**
多行字符串用反引号(`)
**
用 _
来跳过不用的参数
func f(a int, _ string) {}
time.Before
或 time.After
,不要使用 time.Sub
来获得 duration (持续时间),然后检查它的值。ctx
,形如:func foo(ctx Context, ...)
func f(a int, b int, s string, p string)
func f(a, b int, s, p string)
** 一个 slice 的零值是 nil
```
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
// Output:
// [] 0 0
// nil!
```
var a []stringb := []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 {// ...}
** 用 %+v
来打印数据的比较全的信息
** 注意空结构 struct{}
func f1() {var a, b struct{}print(&a, "\n", &b, "\n") // Prints same addressfmt.Println(&a == &b) // Comparison returns false}func f2() {var a, b struct{}fmt.Printf("%p\n%p\n", &a, &b) // Again, same addressfmt.Println(&a == &b) // ...but the comparison returns true}
**
errors.Wrap(err, "additional message to a given error")
**
在 Go 里面要小心使用 range
:
for i := range a
and for i, v := range &a
,都不是 a
的副本for i, v := range a
里面的就是 a
的副本**
从 map 读取一个不存在的 key 将不会 panic
value := map["no_key"]
将得到一个 0 值value, ok := map["no_key"]
更好**
不要使用原始参数进行文件操作
os.MkdirAll(root, 0700)
os.FileMode
**
不要忘记为 iota
指定一种类型
const (_ = iotatestvar // testvar 将是 int 类型)
vs
type myType intconst (_ myType = iotatestvar // testvar 将是 myType 类型)
encoding/gob
在某些时候,结构可能会改变,而你可能会错过这一点。因此,这可能会导致很难找到 bug。
// BADreturn res, json.Unmarshal(b, &res)// GOODerr := 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 fieldX, Y float64}
http.HandlerFunc
比 http.Handler
更好用 http.HandlerFunc
你仅需要一个 func,http.Handler
需要一个类型。
defer
到顶部这可以提高代码可读性并明确函数结束时调用了什么。
用 json:"id,string"
代替
type Request struct {ID int64 `json:"id,string"`
}
** 以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once
** 永远不要使用 select{}
, 省略通道, 等待信号
** 不要关闭一个发送(写入)管道,应该由创建者关闭
** math/rand
中的 func NewSource(seed int64) Source
不是并发安全的,默认的 lockedSource
是并发安全的。
** 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value
** 不要省略 defer
** 总是关闭 http body defer r.Body.Close()
** 过滤但不分配新内存
b := a[:0]for _, x := range a {if f(x) {b = append(b, x)}}
_ = b [7]
** time.Time
有指针字段 time.Location
并且这对 go GC 不好
time.Time
才(对性能)有意义,否则用 timestamp 代替** regexp.MustCompile
比 regexp.Compile
更好
func init
中初始化它** 请勿在你的热点代码中过度使用 fmt.Sprintf
. 由于维护接口的缓冲池和动态调度,它是很昂贵的。
fmt.Sprintf("%s%s", var1, var2)
, 考虑使用简单的字符串连接。fmt.Sprintf("%x", var)
, 考虑使用 hex.EncodeToString
or strconv.FormatInt(var, 16)
** 如果你不需要用它,可以考虑丢弃它,例如io.Copy(ioutil.Discard, resp.Body)
res, _ := client.Do(req)io.Copy(ioutil.Discard, res.Body)defer res.Body.Close()
** 不要在循环中使用 defer,否则会导致内存泄露
** 不要忘记停止 ticker, 除非你需要泄露 channel
ticker := time.NewTicker(1 * time.Second)defer ticker.Stop()
** 用自定义的 marshaler 去加速 marshaler 过程
func (entry Entry) MarshalJSON() ([]byte, error) {buffer := bytes.NewBufferString("{")first := truefor key, value := range entry {jsonValue, err := json.Marshal(value)if err != nil {return nil, err}if !first {buffer.WriteString(",")}first = falsebuffer.WriteString(key + ":" + string(jsonValue))}buffer.WriteString("}")return buffer.Bytes(), nil}
**
sync.Map
不是万能的,没有很强的理由就不要使用它。
**
在 sync.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:nosplitfunc noescape(p unsafe.Pointer) unsafe.Pointer {x := uintptr(p)return unsafe.Pointer(x ^ 0)}
**
对于最快的原子交换,你可以使用这个 m := (*map[int]int)(atomic.LoadPointer(&ptr))
**
如果执行许多顺序读取或写入操作,请使用缓冲 I/O
**
有 2 种方法清空一个 map:
for k := range m {delete(m, k)}
m = make(map[int]int)
** 用这个命令 go build -ldflags="-s -w" ...
去掉你的二进制文件
** 拆分构建不同版本的简单方法
// +build integration
并且运行他们 go test -v --tags integration .
** 最小的 Go Docker 镜像
CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
** run go format on CI and compare diff
** 用最新的 Go 运行 Travis-CI,用 travis 1
** 检查代码格式是否有错误 diff -u <(echo -n) <(gofmt -d .)
package_test
比 package
要好go 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.AllocsPerRun
跟踪你的内存分配
** 多次运行你的基准测试可以避免噪音。
go test -test.bench=. -count=20
**
快速替换 gofmt -w -l -r "panic(err) -> log.Error(err)" .
**
go list
允许找到所有直接和传递的依赖关系
go list -f '{{ .Imports }}' package
go list -f '{{ .Deps }}' package
**
对于快速基准比较,我们有一个 benchstat
工具。
**
go-critic linter 从这个文件中强制执行几条建议
**
go mod why -m
告诉我们为什么特定的模块在 go.mod
文件中。
**
GOGC=off go build ...
应该会加快构建速度 source
**
内存分析器每 512KB 记录一次分配。你能通过 GODEBUG
环境变量增加比例,来查看你的文件的更多详细信息。
**
go mod why -m
告诉我们为什么特定的模块是在 go.mod
文件中。
go func() {sigs := make(chan os.Signal, 1)signal.Notify(sigs, syscall.SIGQUIT)buf := make([]byte, 1<<20)for {<-sigsstacklen := runtime.Stack(buf, true)log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n" , buf[:stacklen])}}()
** 在编译期检查接口的实现
var _ io.Reader = (*MyFastReader)(nil)
** len(nil) = 0
** 匿名结构很酷
var hits struct {sync.Mutexn int}hits.Lock()hits.n++hits.Unlock()
**
httputil.DumpRequest
是非常有用的东西,不要自己创建
**
获得调用堆栈,我们可以使用 runtime.Caller
**
要 marshal 任意的 JSON, 你可以 marshal 为 map[string]interface{}{}
**
配置你的 CDPATH
以便你能在任何目录执行 cd github.com/golang/go
bashrc
(或者其他类似的) export CDPATH=$CDPATH:$GOPATH/src
**
从一个 slice 生成简单的随机元素
[]string{"one", "two", "three"}[rand.Intn(3)]
参考资料:
https://github.com/cristaloleg/go-advice/blob/master/README_ZH.md