前几个月就看到 Google 有了 Golang 这个规范,但是一直没有时间去看。最近仔细看了一下,其中有几个点,之前搬砖的时候还没有注意到,所以记录一下。
本文仅针对于我个人针对这个规范的小结,建议有时间的同学去看看原文,毕竟每个人查缺补漏的地方不一样。

包名称全小写

包名称Package names 不应该有下划线,例如,包 tabwriter 不应该命名为 tabWriterTabWritertab_writerLink

我们有时候会不得不出现包名需要两个单词来描述的情况。在没有了解到这个规则之前,确实我很多命名的时候还是会选择使用下划线进行分隔来命名包名。
原因有两个,一个是之前 C 的影响,一个是由于全小写难以辨认,故会使用下划线。所以,这个规则以后还是要多注意。

针对方法的命名

函数和方法名称不应使用 Getget 前缀,除非底层概念使用单词“get”(例如 HTTP GET)。此时,更应该直接以名词开头的名称,例如使用 Counts 而不是 GetCounts

如果该函数涉及执行复杂的计算或执行远程调用,则可以使用ComputeFetch等不同的词代替Get,以使读者清楚函数调用可能需要时间,并有可能会阻塞或失败。

Link

我之前就经常会写的类似方法名称就是:GetUserByID 这样的,这个规则以后也要注意。

错误字符串

错误字符串不应大写(除非以导出名称、专有名词或首字母缩写词开头)并且不应以标点符号结尾。

这个之前经常会被 lint 查出来,多数情况都是由于用 copilot 生成代码的时候,自动加了标点符号。

Go 的格式函数(fmt.Printf 等)有一个 %q 动词,它在双引号内打印字符串。

1
2
3
4
5
6
// Good:  
fmt.Printf("value %q looks like English text", someText)
// Bad:
fmt.Printf("value \"%s\" looks like English text", someText)
// Avoid manually wrapping strings with single-quotes too:
fmt.Printf("value '%s' looks like English text", someText)

这个之前没有注意到,虽然很多时候不会去手写单引号或者双引号,但是会经常去书写 [%s]
原因是,有时候打印的内容可能是空字符串或者空格,如果不加符号很难看出来。下次可以使用 %q 来代替。

定义为值接受者

接收者是map, functionchannel,使用值类型,而不是指针。

1
2
3
4
5
// Good:  
// See https://pkg.go.dev/net/http#Header.
type Header map[string][]string

func (h Header) Add(key, value string) { /* omitted */ }

这个之前没有仔细去注意,可能写的非常随意,针对与值和指针接受者的选择往往就只是关注在结构体是否需要修改上。

重复命名

Link

不要重复命名的规则体现在下面:

  • 重复包名:
    • widget.NewWidget
    • widget.New
  • 重复接收器
    • func (p *Project) ProjectName() string
    • func (p *Project) Name() string
  • 重复入参
    • func OverrideFirstWithSecond(dest, source *Config) error
    • func Override(dest, source *Config) error
  • 重复返回值
    • func TransformYAMLToJSON(input *Config) *jsonconfig.Config
    • func Transform(input *Config) *jsonconfig.Config

由于 Golang 与 Java 不同,Golang 不支持方法重载,所以会出现类似方法效果不同,但参数不一致的情况。 此时为了区分,才会使用重复命名的方式,将参数或者必要的信息加入到方法名称中。如:

1
2
func (c *Config) WriteText(s string)  
func (c *Config) WriteNumber(num int)

反序列化选择使用零值声明

1
2
3
// Good:  
var coords Point
if err := json.Unmarshal(data, &coords); err != nil {

这也是一开始一个习惯问题,我常常还会使用 coords := &Point{} 来声明,然后进行反序列化。可能是对于空指针的忌惮吧

不要 panic

使用 https://pkg.go.dev/github.com/golang/glog#Fatal 而不是 panic Link

之前针对与一些启动时的异常,如读取配置文件失败,导致程序无法正常启动的时候往往会使用 panic 来处理。 但是这里给出了一个更好的方式,glog 是 google 开源的一个日志库,可以使用 Fatal 来处理异常。

subcommands

https://pkg.go.dev/github.com/google/subcommands

之前在编写的命令行工具的时候往往需要一些子命令。而官方只有 flag 包,只能使用 flag 参数来实现。
而我要的不是 ./cmd -flag1 -flag2,而是 ./cmd subcommand 。所以当需要使用子命令的时候,会直接毫不犹豫的使用 cobra 来实现,但是有时候只为了一个子命令引入确实有点大材小用。

subcommands 就是一个不错的替代,更加轻量,能帮助我们快速实现子命令,以后小东西可以考虑使用这个。

总结

总的来说,有了 lint 和 gofmt/goimports,以及一些 IDE 的帮助,Golang 代码风格还是比较统一的。主要的问题还是在于命名和方法的使用上。