Uber 《Go语言编程规范》学习笔记(二)

本文介绍了Go语言中的包命名规范、结构体嵌入的最佳实践、字符串处理技巧及Printf函数的正确使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

规范

包名

当命名包时,请按下面规则选择一个名称:

  • 全部小写。没有大写或下划线。
  • 大多数使用命名导入的情况下,不需要重命名。
  • 简短而简洁。适当使用缩写。
  • 不用复数。例如net/url,而不是net/urls
  • 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。

另请参阅 Package NamesGo 包样式指南.

对于未导出的顶层常量和变量,使用_作为前缀

在未导出的顶级varsconsts, 前面加上前缀_,以使它们在使用时明确表示它们是全局符号。

例外:未导出的错误值,应以err开头。

基本依据:顶级变量和常量具有包范围作用域。使用通用名称可能很容易在其他文件中意外使用错误的值。

Bad

// foo.go

const (
  defaultPort = 8080
  defaultUser = "user"
)

// bar.go

func Bar() {
  defaultPort := 9090
  ...
  fmt.Println("Default port", defaultPort)

  // We will not see a compile error if the first line of
  // Bar() is deleted.
}

Good

// foo.go

const (
  _defaultPort = 8080
  _defaultUser = "user"
)

结构体中的嵌入

嵌入式类型(例如 mutex)应位于结构体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。

Bad

type Client struct {
  version int
  http.Client
}

Good

type Client struct {
  http.Client

  version int
}

内嵌应该提供切实的好处,比如以语义上合适的方式添加或增强功能。 它应该在对用户不利影响的情况下完成这项工作(另请参见:避免在公共结构中嵌入类型Avoid Embedding Types in Public Structs)。

嵌入 不应该:

  • 纯粹是为了美观或方便。
  • 使外部类型更难构造或使用。
  • 影响外部类型的零值。如果外部类型有一个有用的零值,则在嵌入内部类型之后应该仍然有一个有用的零值。
  • 作为嵌入内部类型的副作用,从外部类型公开不相关的函数或字段。
  • 公开未导出的类型。
  • 影响外部类型的复制形式。
  • 更改外部类型的API或类型语义。
  • 嵌入内部类型的非规范形式。
  • 公开外部类型的实现详细信息。
  • 允许用户观察或控制类型内部。
  • 通过包装的方式改变内部函数的一般行为,这种包装方式会给用户带来一些意料之外情况。

简单地说,有意识地和有目的地嵌入。

一种很好的测试体验是, “是否所有这些导出的内部方法/字段都将直接添加到外部类型” 如果答案是someno,不要嵌入内部类型-而是使用字段。

Bad

type Book struct {
    // Bad: 指针更改零值的有用性
    io.ReadWriter
    // other fields
}
// later
var b Book
b.Read(...)  // panic: nil pointer
b.String()   // panic: nil pointer
b.Write(...) // panic: nil pointer

// 左边嵌入的 io.ReadWriter ,他的零值是没意义的,
// 因为他是 interface,没有具体的 Read 方法实现

// 如果我们嵌入一个东西,但嵌入后还得自己去实现它的方法才能用,那么这个嵌入其实是没有任何意义的
type Client struct {
    sync.Mutex
    sync.WaitGroup
    bytes.Buffer
    url.URL
}

Good

type Book struct {
    // Good: 有用的零值
    bytes.Buffer
    // other fields
}
// later
var b Book
b.Read(...)  // ok
b.String()   // ok
b.Write(...) // ok

// 右边嵌入的那个,他的零值是有意义的,
// 因为他是个 struct,并且实现了Read方法

// 如果我们嵌入一个东西,但嵌入后还得自己去实现它的方法才能用,那么这个嵌入其实是没有任何意义的
type Client struct {
    mtx sync.Mutex
    wg  sync.WaitGroup
    buf bytes.Buffer
    url url.URL
}

使用原始字符串字面值,避免转义

Go 支持使用 原始字符串字面值,也就是 " ` " 来表示原生字符串,在需要转义的场景下,我们应该尽量使用这种方案来替换。

可以跨越多行并包含引号。使用这些字符串可以避免更难阅读的手工转义的字符串。

Bad

wantError := "unknown name:\"test\""

Good

wantError := `unknown error:"test"`

Printf 相关

go vet 可以检查 Printf 相关函数的类型是否对应,相见:go-vet-printf-family-check

字符串 string format

如果你在函数外声明Printf-style 函数的格式字符串,请将其设置为const常量。

这有助于go vet对格式字符串执行静态分析(go vet 会分析)。

Bad

msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Good

const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

命名 Printf 样式的函数

声明Printf-style 函数时,请确保go vet可以检测到它并检查格式字符串。

这意味着您应尽可能使用预定义的Printf-style 函数名称。go vet将默认检查这些。有关更多信息,请参见 Printf 系列

如果不能使用预定义的名称,请以 f 结束选择的名称:Wrapf,而不是Wrapgo vet可以要求检查特定的 Printf 样式名称,但名称必须以f结尾。

go vet -printfuncs=wrapf,statusf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值