关于Go你不得不知道的小技巧
admin
2024-02-03 16:35:05
0

文章目录

      • Go 箴言
      • Go 之禅
      • 代码
        • 使用 `go fmt` 格式化
        • 多个 if 语句可以折叠成 switch
        • 用 `chan struct{}` 来传递信号, `chan bool` 表达的不够清楚
        • `30 * time.Second` 比 `time.Duration(30) * time.Second` 更好
        • 用 `time.Duration` 代替 `int64` + 变量名
        • 按类型分组 `const` 声明,按逻辑和/或类型分组 `var`
        • 不要在你不拥有的结构上使用 `encoding/gob`
        • 不要依赖于计算顺序,特别是在 return 语句中。
        • 防止结构体字段用纯值方式初始化,添加 `_ struct {}` 字段:
        • 为了防止结构比较,添加 `func` 类型的空字段
        • `http.HandlerFunc` 比 `http.Handler` 更好
        • 移动 `defer` 到顶部
        • JavaScript 解析整数为浮点数并且你的 int64 可能溢出
      • 并发
      • 性能
        • 为了帮助编译器删除绑定检查,请参见此模式 `_ = b [7]`
      • 构建
      • 测试
      • 工具
      • 其他

Go 箴言

  • 不要通过共享内存进行通信,通过通信共享内存
  • 并发不是并行
  • 管道用于协调;互斥量(锁)用于同步
  • 接口越大,抽象就越弱
  • 利用好零值
  • 空接口 interface{} 没有任何类型约束
  • Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
  • 允许一点点重复比引入一点点依赖更好
  • 系统调用必须始终使用构建标记进行保护
  • 必须始终使用构建标记保护 Cgo
  • Cgo 不是 Go
  • 使用标准库的 unsafe 包,不能保证能如期运行
  • 清晰比聪明更好
  • 反射永远不清晰
  • 错误是值
  • 不要只检查错误,还要优雅地处理它们
  • 设计架构,命名组件,(文档)记录细节
  • 文档是供用户使用的
  • 不要(在生产环境)使用 panic()

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.Secondtime.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.Beforetime.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。

不要依赖于计算顺序,特别是在 return 语句中。

  // 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.HandlerFunchttp.Handler 更好

http.HandlerFunc 你仅需要一个 func,http.Handler 需要一个类型。

移动 defer 到顶部

这可以提高代码可读性并明确函数结束时调用了什么。

JavaScript 解析整数为浮点数并且你的 int64 可能溢出

json:"id,string" 代替

type Request struct {ID int64 `json:"id,string"`
}

并发

  • ** 以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once

    • 不要用 flags, mutexes, channels or atomics
  • ** 永远不要使用 select{}, 省略通道, 等待信号

  • ** 不要关闭一个发送(写入)管道,应该由创建者关闭

    • 往一个关闭的 channel 写数据会引起 panic
  • ** math/rand 中的 func NewSource(seed int64) Source 不是并发安全的,默认的 lockedSource 是并发安全的。

  • ** 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value

性能

  • ** 不要省略 defer

    • 在大多数情况下 200ns 加速可以忽略不计
  • ** 总是关闭 http body defer r.Body.Close()

    • 除非你需要泄露 goroutine
  • ** 过滤但不分配新内存

  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.MustCompileregexp.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)

    • HTTP 客户端的传输不会重用连接,直到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 := 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:

    • 重用 map 内存 (但是也要注意 m 的回收)
  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 镜像

    • https://twitter.com/bbrodriges/status/873414658178396160
    • 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_testpackage 要好
  • ** 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 文件中。

其他

  • ** dump goroutines
  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

相关内容

热门资讯

制作安卓系统主题软件,安卓系统... 你有没有想过,给你的安卓手机换一个全新的面貌?没错,就是那种一打开手机,就能感受到完全不同的风格和氛...
安卓系统平板怎么截屏,操作指南... 亲爱的平板用户,你是不是也和我一样,有时候想记录下平板上的精彩瞬间,却发现截屏功能有点神秘?别担心,...
安卓系统不推送更新,揭秘背后的... 最近是不是发现你的安卓手机有点儿“懒”啊?更新推送总是慢吞吞的,让人等得花儿都谢了。别急,今天就来给...
ape格式转换安卓系统,享受音... 你有没有想过,你的安卓手机里的ape格式音乐文件,竟然可以通过一个小小的转换,焕发出全新的生命力?没...
获取安卓系统加载器,核心功能与... 你有没有想过,你的安卓手机里那些神奇的软件和游戏是怎么被安装到你的设备上的呢?没错,就是通过一个叫做...
安卓系统文件夹在哪,安卓系统文... 你有没有遇到过这样的情况:手机里乱糟糟的,想找个文件却找不到?别急,今天就来给你揭秘安卓系统文件夹的...
安卓手感最好的裸机系统,安卓手... 安卓手感最好的裸机系统:探索极致体验的秘密武器在数字世界中,我们常常被各种功能和复杂操作所包围,尤其...
nas如何刷回安卓系统,轻松刷... 你有没有想过,你的NAS(网络附加存储)突然间变成了一个安卓的小天地?别急,这可不是什么天方夜谭,而...
荣耀沿用的安卓系统吗,打造个性... 你有没有注意到,最近荣耀的新机发布,大家都在热议一个问题:荣耀沿用的安卓系统吗?这可是个让人好奇不已...
快麦erp系统安卓下载,一键下... 你有没有听说最近一款叫做快麦ERP系统的软件在安卓平台上大受欢迎呢?没错,就是那个能让你企业管理如虎...
华为安卓系统下载app,一步到... 你有没有发现,最近华为手机的用户们都在忙活一件大事儿?没错,那就是下载安卓系统上的各种app啦!这可...
原生安卓系统游戏模式,畅享沉浸... 亲爱的手机游戏爱好者们,你是否曾为手机游戏运行不畅而烦恼?又或者,你是否渴望在游戏中获得更极致的体验...
安卓9改系统语言设置,轻松切换... 你有没有发现,手机里的语言设置有时候真的让人头疼?比如说,你突然想用一下安卓9的系统语言设置,结果发...
怎么升级安卓最新系统,畅享安卓... 亲爱的手机控们,你是不是也和我一样,对安卓系统的更新充满了期待?每次系统升级,都仿佛给我们的手机带来...
安卓系统电视跳舞毯,家庭娱乐新... 你有没有想过,家里的电视除了用来追剧、看电影,还能变成一个充满活力的娱乐中心?没错,我要给你介绍的就...
安卓系统维护周期,全方位守护您... 亲爱的手机控们,你是不是也和我一样,对安卓系统的维护周期充满了好奇呢?毕竟,我们的手机可是我们日常生...
安卓系统电脑怎么往下滑,一扫即... 你有没有发现,用安卓系统电脑的时候,有时候屏幕上会出现一些小图标或者应用,你想要快速浏览或者切换,却...
手机中判断安卓系统苹果系统js... 你有没有想过,你的手机里到底装的是安卓系统还是苹果系统呢?这可不是一个小问题哦,因为不同的系统,就像...
window系统和安卓系统还原... 你有没有遇到过手机或电脑突然卡顿,或者不小心删掉了重要的文件?别急,今天就来给你详细说说如何让win...
安卓系统打电话变声器,轻松实现... 安卓系统打电话变声器:探索数字时代的通信革新在数字化浪潮中,智能手机已经成为我们生活中不可或缺的一部...