目录
命名规则
包名称
接收者
常量
缩写词
Get方法Getters
变量名
单字母变量名
重复
包名 vs 可导出符号名
变量名 vs 类型
外部上下文 vs 本地名称
因为发现自己的变量命名可读性很差,所以整理了一下规范。
主要参考 Google Style Guides 和 Go Style ,并且结合了其他的资料和个人理解。
@
、$
、%
等。Man
和man
是两个不同的标识符。tabWriter
、TabWriter
或 tab_writer
。usercount
是比 count
更好的包名,因为 count
是常用变量名。_test
后缀,例如 mypackage_test_test
后缀作为 包级文档示例,例如 example_mypackage_testutil
、utility
、common
、helper
等。查看更多关于所谓的“实用程序包”。import foob "path/to/foo_go_proto"
),包的本地名称必须符合上述规则,因为本地名称决定了包中的符号在文件中的引用方式。如果给定的导入在多个文件中重命名,特别是在相同或附近的包中,则应尽可能使用相同的本地名称以保持一致性。长名称 | 更好命名 |
---|---|
func (tray Tray) |
func (t Tray) |
func (info *ResearchInfo) |
func (ri *ResearchInfo) |
func (this *ReportWriter) |
func (w *ReportWriter) |
func (self *Scanner) |
func (s *Scanner) |
常量名称必须像 Go 中的所有其他名称一样使用 混合大写字母MixedCaps。(导出常量以大写字母开头,而未导出的常量以小写字母开头)即使打破了其他语言的约定,这也是适用的。常量名称不应是其值的派生词,而应该解释值所表示的含义。
// Good:
const MaxPacketSize = 512
const (
ExecuteBit = 1 << iota
WriteBit
ReadBit
)
不要使用非混合大写常量名称或带有 K
前缀的常量。
// Bad:
const MAX_PACKET_SIZE = 512
const kMaxBufferSize = 1024
const KMaxUsersPergroup = 500
根据它们的角色而不是它们的值来命名常量。如果一个常量除了它的值之外没有其他作用,那么就没有必要将它定义为一个常量。
// Bad:
const Twelve = 12
const (
UserNameColumn = "username"
GroupColumn = "group"
)
名称中的首字母缩略词或单独的首字母缩略词(例如,“URL”和“NATO”)应该具有相同的大小写
URL
应显示为 URL
或 url
(如 urlPony
或 URLPony
),绝不能显示为 Url
。
ID
为 identifier 缩写:应显示为 appID
而不是 appId
。
XMLAPI
因为它包含 XML
和 API
),给定首字母缩写中的每个字母都应该具有相同的大小写,但名称中的每个首字母缩写不需要具有相同的大小写。DDoS
、iOS
、gRPC
),首字母缩写应该像在标准中一样出现,除非你需要为了满足 导出 而更改第一个字母。在这些情况下,整个缩写词应该是相同的情况(例如 ddos
、IOS
、GRPC
)。缩写词 | 范围 | 正确 | 错误 |
---|---|---|---|
XML API | Exported | XMLAPI |
XmlApi , XMLApi , XmlAPI , XMLapi |
XML API | Unexported | xmlAPI |
xmlapi , xmlApi |
iOS | Exported | IOS |
Ios , IoS |
iOS | Unexported | iOS |
ios |
gRPC | Exported | GRPC |
Grpc |
gRPC | Unexported | gRPC |
grpc |
DDoS | Exported | DDoS |
DDOS , Ddos |
DDoS | Unexported | ddos |
dDoS , dDOS |
函数和方法名称不应使用 Get
或 get
前缀,除非底层概念使用单词 get(例如 HTTP GET)。此时,更应该直接以名词开头的名称,例如使用 Counts
而不是 GetCounts
。
type User struct {
name string
}
// 直接访问导出字段(无需 Getter)
func (u *User) Name() string { return u.name }
// 冗余的 Getter 模式
func (u *User) GetName() string { return u.name }
如果该函数涉及执行复杂的计算或执行远程调用,则可以使用Compute
或 Fetch
等不同的词代替Get
,以使读者清楚函数调用可能需要时间,并有可能会阻塞或失败。
// 明确表示远程调用
func (c *Client) FetchUserData(id string) (*Data, error) { ... }
// 明确表示计算密集型操作
func CalculateHash(data []byte) (string, error) { ... }
// 模糊的命名,无法体现操作性质
func GetUserData(id string) (*Data, error) { ... }
一般的经验法则是,名称的长度应与其范围的大小成正比,并与其在该范围内使用的次数成反比。在文件范围内创建的变量可能需要多个单词,而单个内部块作用域内的变量可能是单个单词甚至只是一两个字符,以保持代码清晰并避免无关信息。
这是一条粗略的基础原则。这些数字准则不是严格的规则。要根据上下文、清晰 和[简洁](02 Guide | Google Style Guides简洁)来进行判断。
在小范围内可能非常清楚的名称(例如,c
表示计数器)在较大范围内可能不够用,并且需要澄清以提示进一步了解其在代码中的用途。一个作用域中有很多变量,或者表示相似值或概念的变量,那就可能需要比作用域建议的采用更长的变量名称。
概念的特殊性也有助于保持变量名称的简洁。例如,假设只有一个数据库在使用,像db
这样的短变量名通常可能保留给非常小的范围,即使范围非常大,也可能保持完全清晰。在这种情况下,根据范围的大小,单个词database
可能是可接受的,但不是必需的,因为db
是该词的一种非常常见的缩写,几乎没有其他解释。
局部变量的名称应该反映它包含的内容以及它在当前上下文中的使用方式,而不是值的来源。例如,通常情况下最佳局部变量名称与结构或协议缓冲区字段名称不同。
一般来说:
像 count
或 options
这样的单字名称是一个很好的起点。
可以添加其他词来消除相似名称的歧义,例如 userCount
和 projectCount
。
不要简单地省略字母来节省打字时间。例如,Sandbox
优于 Sbx
,特别是对于导出的名称。
大多数变量名可省略 类型和类似类型的词
userCount
是比 numUsers
或 usersInt
更好的名称。users
是一个比 userSlice
更好的名字。ageString
中,并使用 age
作为解析值。省略上下文中清楚的单词。例如,在 UserCount 方法的实现中,名为 userCount 的局部变量可能是多余的, count
、users
甚至 c
都具有可读性。
单字母变量名是可以减少 重复 的有用工具,但也可能使代码变得不透明。将它们的使用限制在完整单词很明显以及它会重复出现以代替单字母变量的情况。
一般来说:
r
用于 io.Reader
或 *http.Request
w
用于 io.Writer
或 http.ResponseWriter
i
)和坐标(例如,x
和 y
)。for _, n := range nodes { ... }
。一段 Go 源代码应该避免不必要的重复。一个常见的情形是重复名称,其中通常包含不必要的单词或重复其上下文或类型。如果相同或相似的代码段在很近的地方多次出现,代码本身也可能是不必要的重复。
重复命名可以有多种形式,包括:
当命名导出的符号时,包的名称始终在包外可见,因此应减少或消除两者之间的冗余信息。如果一个包如果需要仅导出一种类型并且以包本身命名,则构造函数的规范名称是New
(如果需要的话)。
实例: 重复的名称 -> 更好的名称
widget.NewWidget
->widget.New
widget.NewWidgetWithName
->widget.NewWithName
db.LoadFromDatabase
->db.Load
goatteleportutil.CountGoatsTeleported
->gtutil.CountGoatsTeleported
orgoatteleport.Count
myteampb.MyTeamMethodRequest
->mtpb.MyTeamMethodRequest
ormyteampb.MethodRequest
包名在外部使用时始终可见,因此导出符号名中无需重复包名隐含的信息。
// 冗余:包名 `widget` 已隐含类型信息
widget.NewWidget → widget.New
// 冗余:包名 `db` 已关联数据库
db.LoadFromDatabase → db.Load
若导出类型名与包名无关,需保留类型名以避免歧义。
// 包 `graph` 导出类型 `Node` 和 `Edge`
func NewNode() *Node { ... }
func NewEdge() *Edge { ... }
若包仅导出一个核心类型且类型名与包名相同,构造函数应命名为 New
// 包 `widget` 导出类型 `Widget`
func New() *Widget { ... }
如果包导出多个类型,构造函数需明确类型名以区分。
// 包 `stream` 导出 `Reader` 和 `Writer`
func NewReader() *Reader { ... }
func NewWriter() *Writer { ... }
编译器总是知道变量的类型,并且在大多数情况下,阅读者也可以通过变量的使用方式清楚地知道变量是什么类型。只有当一个变量的值在同一范围内出现两次时,才有需要明确变量的类型。
重复的名称 | 更好的名称 |
---|---|
var numUsers int |
var users int |
var nameString string |
var name string |
var primaryProject *Project |
var primary *Project |
如果该值以多种形式出现,这可以通过额外的词(如raw
和parsed
)或底层表示来澄清:
// Good:
limitStr := r.FormValue("limit")
limit, err := strconv.Atoi(limitStr)
// Good:
limitRaw := r.FormValue("limit")
limit, err := strconv.Atoi(limitRaw)
状态标识 | 含义 | 特点 | 典型场景 |
---|---|---|---|
raw |
原始数据,未经任何处理 | 不可信、未验证、可能存在安全风险 | HTTP 参数、文件/网络原始字节、用户输入 |
parsed |
从原始数据中提取的结构化信息 | 已初步处理,但可能未完全验证 | 类型转换、正则匹配、字符串分解 |
decoded |
从编码格式转换回业务对象的数据 | 已结构化,可直接使用但需校验 | JSON/XML 反序列化、Base64 解码 |
包含来自周围上下文信息的名称通常会产生额外的噪音,而没有任何好处。包名、方法名、类型名、函数名、导入路径,包含来自其上下文信息的名称。
// In package "ads/targeting/revenue/reporting"
// Bad:
type AdsTargetingRevenueReport struct{}
func (p *Project) ProjectName() string
// Good:
type Report struct{}
func (p *Project) Name() string
// In package "sqldb"
// Bad:
type DBConnection struct{}
// Good:
type Connection struct{}
// In package "ads/targeting"
// Bad:
func Process(in *pb.FooProto) *Report {
adsTargetingID := in.GetAdsTargetingID()
}
// Good:
func Process(in *pb.FooProto) *Report {
id := in.GetAdsTargetingID()
}
重复通常应该在符号使用者的上下文中进行评估,而不是孤立地进行评估。例如,下面的代码有很多名称,在某些情况下可能没问题,但在上下文中是多余的:
// Bad:
func (db *DB) UserCount() (userCount int, err error) {
var userCountInt64 int64
if dbLoadError := db.LoadFromDatabase("count(distinct users)", &userCountInt64); dbLoadError != nil {
return 0, fmt.Errorf("failed to load user count: %s", dbLoadError)
}
userCount = int(userCountInt64)
return userCount, nil
}
相反,在上下文和使用上信息是清楚的情况下,常常可以忽略:
// Good:
func (db *DB) UserCount() (int, error) {
var count int64
if err := db.Load("count(distinct users)", &count); err != nil {
return 0, fmt.Errorf("failed to load user count: %s", err)
}
return int(count), nil
}