编写地道的Go代码:第一章 命名规范

Grady Booch 是一位在软件工程领域做出了杰出贡献的计算机科学家。他通过开发 UML、提出 Booch 方法以及撰写畅销书等方式,极大地推动了软件工程领域的发展,并对今天的软件开发实践产生了深远的影响。他关于整洁代码(clean code)一段描述非常得到大家的赞同:
清晰的代码,简练而直白,读之如行云流水般的优美散文。它绝不故弄玄虚,而是以简洁的抽象和清晰的逻辑,淋漓尽致地展现设计者的匠心。
这个系列就是介绍Go语言中几百个编写整洁代码的建议。我不把它们称作为编码规范或者最佳实践,而是把它们称之为建议,原因在于有些建议并不一定强调开发者必须遵守,而是在大部分情况下,这些建议的做法比较符合地道的Go编程写法。
我在实际的工作中,也看到很多初入Go语言领域编写应用程序的同学,或许他刚入门或者从其他语言刚转入到Go一两年,得益于 Go语言的简单易学,就非常快的编写了Go可运行的程序,这是非常好的一件事情,但是也看到代码中一些不是那么地道的Go语言写法,比如变量的命名很奇怪、打印error信息首字母大写等等,让人很容易识别出是Go语言初学者的痕迹。
所以我想通过一个简短的课程,给大家介绍一下Go语言中如何编写干净整洁,逻辑清晰的代码,让你的代码看起来就像专家写的。
Go标准库是学习这些规范的最佳代码。
首先第一课就是命名规范。
编程中的命名规范是一套规则,用于为代码中的各种元素(如变量、函数、类、文件等)选择名称。良好的命名规范可以提高代码的可读性、可维护性和团队协作效率。
但是,做到好命名并不是一件容易的事。
There are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton
计算机科学中最难的两件事:缓存失效和命名。
基础规则
1、使用驼峰命名法,公开函数首字母大写,私有函数首字母小写
这是 Go 语言中最基本的命名约定。公开的函数、类型、变量等可以被其他包访问,因此首字母需要大写;私有的则只能在包内部使用,首字母小写。
示例:
公开函数:
io.Copy()
私有类型:
io.nopCloser
2、接口名应以 -er 结尾,表示行为,如Reader、Writer
通过 -er 后缀,可以直观地表明这是一个行为接口;符合英语语言习惯,容易理解和记忆;清晰地表达"能做什么"的概念。
示例:
io.Reader
:定义了Read()
方法,表示可以读取数据的对象io.Writer
:定义了Write()
方法,表示可以写入数据的对象Closer
:定义了Close()
方法,表示可关闭的对象。Stringer
:定义了String()
方法,表示可以格式化成字符串的对象。
3、变量名应该简短且有意义
变量名应该清晰地表达变量的用途,避免使用晦涩的缩写或无意义的名称。循环计数器通常可以使用 i
、j
、k
等单字母。特定的参数和变量也可以使用单字符,如r
、w
。
示例:
临时变量
wt
, 如wt, ok := src.(WriterTo)
简短有意义的变量
size
:size := 32 * 1024
循环中的变量
i
:for i := 0; i < len(data); i++
4、包名使用小写单词,不使用下划线或混合大小写
包名应该简洁明了,反映包的功能。尽量一个单词。
示例:
net
、bytes
、http
、hash
、image
等需要两个单词的,可以缩写,如
bufio
、expvar
、ioutil
、strconv
等不推荐,如
credentialprovider
、serviceaccount
5、常量名使用驼峰法
这种命名方式可以清晰地将常量与变量区分开。
示例:
如
bufio.MaxScanTokenSize
、net.IPv4len
、io.SeekStart
等全大写加下划线分隔如
MAX_CONNECTIONS
并不是Go语言命名常量地道的写法
6、 名称要表明其用途,避免无意义的别名
示例:
// 不好的例子,x,y含义不清晰
func process(x, y int) int {
return x * y
}
// 好的例子
func calculateArea(length, width int) int {
return length * width
}
包名规范
7、包名应该简短、明确,比如net、http、bytes (同#4)
示例:
net
: 这个包提供了网络相关的基本功能,例如TCP/IP、UDP、域名解析等。包名简洁明了,直接表达了其用途。例如,net.Dial()
函数用于建立网络连接。http
: 这个包实现了HTTP客户端和服务端。例如,http.ListenAndServe()
用于启动HTTP服务器,http.Get()
用于发送HTTP GET请求。同样,包名非常清晰地表明了其功能。bytes
: 这个包提供了操作字节切片的函数。例如,bytes.Buffer
类型用于高效地构建字节切片。包名简短且准确地描述了其操作的数据类型。
8、避免使用common、util这样笼统的包名
Go标准库中没有使用 common
或 util
这样的包名。这是因为这些名称过于宽泛,无法清晰地表达包的功能。如果需要一些通用的工具函数,通常会根据其具体用途放到更具针对性的包中,或者如果确实非常通用,会考虑是否可以合并到已有的相关包里。
示例:
ioutil
是和输入输出相关的工具包httputil
是和http相关的工具包
9、 包名和目录名保持一致
Go的包管理机制要求包名和目录名保持一致。例如,net
包的代码位于 $GOROOT/src/net
目录下,http
包的代码位于 $GOROOT/src/net/http
目录下(注意http是net的子包)。这种约定使得代码组织清晰,易于查找和维护。
10、避免包名冲突,必要时使用重命名导入
虽然Go的包路径包含了完整的导入路径(例如 net/http
),但仍然可能存在不同模块的包名相同的情况。这时可以使用重命名导入来解决冲突。
示例:
import (
"fmt"
myhttp "my.org/http" // 将 my.org/http 包重命名为 myhttp
stdhttp "net/http" // 将 net/http 包重命名为 stdhttp
)
func main() {
stdhttp.Get("http://example.com")
myhttp.MyFunction()
}
11、避免包名和内建类型冲突,可以使用复数
示例:
bytes
: 操作字节切片。strings
: 操作字符串。maps
: 扩展的map操作。slices
: 扩展的slice操作。
变量命名
12、 bool类型变量名应带有is
、has
、can
等前缀
使用这些前缀可以清晰地表达变量的布尔含义。例如:
isValid
:表示某个值是否有效。hasPermission
:表示是否拥有某个权限。canAccess
:表示是否可以访问某个资源。isFinished
:表示某个操作是否完成。hasError
:表示是否发生错误。
13、 切片名称用复数形式,如users
、ports
使用复数形式可以清晰地表明变量存储的是一个集合。例如:
users
:存储用户信息的切片。ports
:存储端口号的切片。messages
:存储消息的切片。results
:存储结果的切片。
14、 指针变量不需要带ptr
前缀
Go语言中,类型信息已经足够表明一个变量是否为指针,因此不需要额外的 ptr
前缀。直接使用变量名即可,例如:
var user *User
user = &newUser
name := user.Name // 直接使用 user.Name,无需 ptrUser.Name
15、 接收者名称应简短,通常用类型首字母,再不济用两个字母简写
在方法定义中,接收者名称应该简短,通常使用类型名的首字母。例如:
type User struct {
Name string
}
func (u User) GetName() string { // 接收者 u
return u.Name
}
type File struct {
fd int
}
func (f File) Close() error { // 接收者 f
// ...
}
16、 中间结果用有意义的名称,避免result1
、result2
使用有意义的名称可以提高代码的可读性。例如,不要使用:
result1 := calculateValue(x)
result2 := processResult(result1)
而应该使用:
calculatedValue := calculateValue(x)
processedValue := processResult(calculatedValue)
或者更具上下文的简短名称:
sum := calculateSum(numbers)
average := calculateAverage(sum, len(numbers))
17、 全局变量一般不添加前缀
不像其他编程语言,Go语言一般不为全局变量添加前缀。例如下面的写法在地道的Go程序较少见:
var gLogger *log.Logger
var gConfig *Config
func init() {
gLogger = log.New(os.Stdout, "MYAPP: ", log.LstdFlags)
gConfig = loadConfig()
}
18、 接口实现者命名可以使用接口名做后缀
这是一个常见的约定,可以清晰地表明某个类型实现了哪个接口。例如:
io.Reader
接口的实现类型可以命名为FileReader
、BufReader
、StringReader
等。http.Handler
接口的实现类型可以命名为MyHandler
、UserHandler
等。
函数/方法命名
19、Set
前缀用于属性访问,一般不使用GetXXX()
,而是直接XXX()
命名属性读取方法
在Go中,对于属性的设置使用 Set
前缀,而读取则直接使用属性名作为方法名。例如:
func (c *IPConn) SetDeadline(t time.Time) error
func (c *IPConn) SetReadBuffer(bytes int) error
func (c *IPConn) SetReadDeadline(t time.Time) error
func (c *IPConn) SetWriteBuffer(bytes int) error
func (c *IPConn) SetWriteDeadline(t time.Time) error
func (a *TCPAddr) AddrPort() netip.AddrPort
func (a *TCPAddr) Network() string
func (a *TCPAddr) String() string
20、New
前缀用于构造函数
使用 New
前缀的函数用于创建类型的实例。
包下面如果只有一个构造函数,那么可以只定义New
,如果有多个构造函数,可以使用NewXXXXX
明确特定类型的构造函数:
func New(text string) error
func NewReader(rd io.Reader) *Reader
func NewReaderSize(rd io.Reader, size int) *Reader
21、Parse
前缀用于解析操作
使用 Parse
前缀的函数用于将字符串或其他格式的数据解析成某种类型。例如:
num, err := strconv.ParseInt(str, 10, 64)
22、Handle
前缀用于处理回调
使用 Handle
前缀的函数用于处理事件或回调。例如:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
23、 测试函数用Test
前缀加被测函数名
Go的测试框架要求测试函数以 Test
开头,后面是被测函数的名称。例如:
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
if Add(2, 3) != 5 {
t.Error("Add(2, 3) should be 5")
}
}
24、 基准测试用Benchmark
前缀
基准测试函数以 Benchmark
开头。例如:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 1)
}
}
25、 方法名应体现动作或者功能,如convertToString
而不是conversion
,除非是读取属性方法
方法名应该清晰地表达方法的功能。使用动词或动词短语,例如:
convertToString()
:将某个值转换为字符串。calculateSum()
:计算总和。sendMessage()
:发送消息。
而不是使用名词,例如 conversion
、calculation
、message
。但是读取属性的方法可以直接使用属性名,如前文所述的 Name()
。
26. 避免无意义的命名,比如仅仅使用Handle
、Process
、Do
,标准库这样写除外
除非在非常通用的上下文中,否则应避免使用过于笼统的命名,例如仅仅使用 Handle
、Process
、Do
。应该根据具体的功能使用更具描述性的名称。例如,与其使用 HandleData()
,不如使用 ProcessUserData()
或 ValidateInputData()
。标准库中一些非常底层的包,由于其通用性,可能会使用这些简单的命名,如func (c *Client) Do(req *Request) (*Response, error)
,但一般情况下我们应该避免。
27、和第12条一样, 返回值是bool类型的函数名应带有is
、has
、can
等前缀
个别函数例外,如Equal
。
28、函数一般使用动词原型
比如使用Equal
而不是Equals
,使用Get
而不是Gets
。
29、单词倾向于使用美式写法
美式拼写 | 英式拼写 |
---|---|
Marshal, UnMarshal | Marshall, Unmarshall |
color, flavor, neighbor, rumor | colour, flavour, neighbour, rumour |
center, fiber, meter | centre, fibre, metre |
analyze, organize, realize | analyse/analyze, organise/organize, realise/realize |
traveling, traveling, labeled | travelling, travelling, labelled |
program, draft | programme, draught |
adapter | adaptor |
结构体命名
30、名称应该是名词或名词短语
结构体的名称应该清晰地描述它所代表的事物或概念,因此应该使用名词或名词短语。例如:
User
:表示一个用户。Product
:表示一个产品。Order
:表示一个订单。CustomerAddress
:表示客户地址。FileData
:表示文件数据。
避免使用动词或形容词作为结构体名称,例如 RunningProcess
或 ImportantData
。
31、尽量使用单个单词,适当采用缩写(众所周知的缩写)
能用一个单词能表达的就用一个单词,或者一个单词加实现的接口名,杜绝非常多的单词组成的结构名。
广为人知的缩写可以使用,例如 HTML
、URL
、ID
、API
等。
一些结构体名称:
Buffer
LimitedReader
HTMLDoc
而不是HyperTextMarkupLanguageDocument
Cond
而不是ConditionVariable
32、保持一致性
在一个项目中,应该保持结构体命名的一致性。例如,如果使用 User
表示用户,则不要在其他地方使用 Person
或 Member
表示相同的概念。
接口命名
33、多方法接口用名词描述其功能
如果一个接口包含多个方法,则应该使用名词或形容词来描述接口的功能或特性。例如:
ReadWriteCloser
:包含Read()
、Write()
和Close()
方法。HTTPHandler
:处理 HTTP 请求的接口。Encoding
:定义编码/解码操作的接口。Iterator
:定义迭代器行为的接口。Ordered
:定义了可排序行为的接口。这里的Ordered
更多的是名词的意思(OrderedObject
)
避免在这种情况下使用 动词+er
的形式,因为这可能会导致接口名称过于冗长或难以理解。 只对单一方法接口的命名动词+er
采用形式。
较少使用形容词,比如优先使用Iterator
,而不是Iterable
。
34、避免使用I
或Interface
前缀
在 Go 语言中,不推荐使用 I
或 Interface
前缀来命名接口。例如,不要使用 IReader
或 ReaderInterface
,而应该直接使用 Reader
。Go 语言的类型系统已经足够清晰,不需要额外的 I
或 Interface
前缀来表明一个类型是接口。
类型别名命名
35、如果是包级别的类型,名称要足够特异
对于包级别的类型别名,名称尤其需要具有独特性,以避免与其他包或类型产生混淆。这意味着名称应该清晰地表达别名所代表的类型,并尽可能避免使用过于通用的名称:
package network
import "net/http"
// Bad: MyCustomRequest 是 http.Request 的类型别名,用于表示自定义的请求类型
// 仅仅使用 MyCustomRequest 可能不够清晰,因为其他包也可能定义类似的别名。
type MyCustomRequest = http.Request
// 更好的方式:添加更具特异性的前缀或后缀
type NetworkRequest = http.Request
type InternalRequest = http.Request //如果仅在包内部使用
36、考虑添加适当后缀表明其特性
为了进一步提高类型别名的可读性和可理解性,可以考虑添加适当的后缀来表明其特性或用途。
一些常用的后缀包括:
Type
: 用于强调这是一个类型别名。例如RequestType
。Alias
: 明确表明这是一个别名。例如RequestAlias
。Func
: 用于函数类型别名。例如HandlerFunc
。List
: 用于表示某种类型的列表。例如UserList
(虽然通常用[]User
)
嵌入字段命名
37、匿名嵌入使用类型名称
当一个类型被匿名地嵌入到另一个类型中时,该类型的名称就成为了嵌入字段的名称。这意味着你可以直接通过外部类型的实例访问嵌入类型的字段和方法。
type Address struct {
Street string
City string
ZipCode string
}
type User struct {
Name string
Address // 匿名嵌入 Address 类型
}
38、命名嵌入使用有意义的字段名
当使用命名嵌入时,需要为嵌入的类型指定一个字段名。这个字段名应该清晰地描述嵌入类型的用途或角色。
type Author struct {
Name string
Bio string
}
type Book struct {
Title string
Author Author `json:"author"` // 命名嵌入 Author 类型,字段名为 author
}
39、 避免因嵌入导致的名称冲突
当外部类型和嵌入类型具有相同的字段或方法名时,会发生名称冲突。在这种情况下,外部类型的字段或方法会“屏蔽”嵌入类型的同名成员。为了避免混淆,应该尽量避免这种冲突。
type Inner struct {
Value int
Name string
}
type Outer struct {
Value string
Inner
}
func main() {
outer := Outer{
Value: "outer value",
Inner: Inner{
Value: 10,
Name: "inner name",
},
}
println(outer.Value) // 输出:outer value (Outer 的 Value 屏蔽了 Inner 的 Value)
println(outer.Inner.Value) // 输出:10,需要通过Outer.Inner.Value访问
println(outer.Name) // 输出:inner name
}
40、考虑可见性要求选择大小写
和其他标识符一样,嵌入字段的可见性也由其首字母的大小写决定。
首字母大写的嵌入字段是导出的(public),可以被其他包访问。
首字母小写的嵌入字段是非导出的(private),只能在当前包内部访问。
Error类型命名
41、 错误变量以Err
或Error
为前缀
在包级别定义的错误变量(通常是哨兵错误),应该以 Err
或 Error
为前缀。这有助于清晰地标识这些变量表示的是错误。
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
42、自定义错误类型以Error
为后缀
当需要定义包含更多信息的自定义错误类型时,应该使用结构体或其他类型,并实现 error
接口。自定义错误类型的名称应该以 Error
为后缀。
type InputError struct{ ... }
type onceError struct {
sync.Mutex // guards following
err error
}
43、wrap 错误时保持原始错误信息的语义
使用 fmt.Errorf
的 %w
动词或第三方库(如 errors
包)进行错误包装时,应该尽量保持原始错误信息的语义,避免信息丢失或误导。
package mypackage
import (
"fmt"
"os"
)
func ReadFile(filename string) error {
_, err := os.ReadFile(filename)
if err != nil {
// 正确的 wrap 方式,保留了原始错误信息
return fmt.Errorf("reading file %s: %w", filename, err)
// 不好的 wrap 方式,丢失了原始错误信息
// return fmt.Errorf("failed to read file")
}
return nil
}
正确的 wrap 方式保留了 os.ReadFile
返回的原始错误信息,方便调试和排查问题。
44、 错误字符串首字母不大写,不用标点结尾
错误字符串应该使用小写字母开头,并且不要使用标点符号结尾。这是 Go 语言的惯例。
package mypackage
import "errors"
var ErrInvalidInput = errors.New("invalid input") // 正确:小写开头,没有标点
// 不好的例子:
// var ErrInvalidInput = errors.New("Invalid input.") // 大写开头,有句点
// var ErrInvalidInput = errors.New("INVALID INPUT") // 全部大写
常量命名
Go 语言中,常量使用 const
关键字声明。常量名通常使用驼峰式,而不是全大写加下划线。
45、枚举常量使用分组前缀,如ColorRed
、ColorBlue
当定义一组相关的常量,用于表示枚举值时,应该使用一个共同的前缀来分组这些常量。这有助于清晰地标识这些常量属于同一个枚举类型。
package mypackage
const (
ColorRed = 1
ColorGreen = 2
ColorBlue = 3
)
const (
FileModeRead = 0400
FileModeWrite = 0200
FileModeExec = 0100
)
除非是众所周知的常量,如星期名,月份名、性别等等。
46、状态常量用State
前缀,如StateActive
当定义一组常量来表示某个对象或系统的不同状态时,应该使用 State
前缀。
package mypackage
const (
StateActive = 1
StateInactive = 2
StatePending = 3
)
在这个例子中,StateActive
、StateInactive
和 StatePending
使用了 State
前缀,清晰地表明它们表示不同的状态。
47、配置相关常量用Config
前缀
当定义一些用于配置系统的常量时,应该使用 Config
前缀。
package mypackage
const (
ConfigMaxConnections = 100
ConfigDefaultTimeout = 30 // seconds
)
48、魔法数字转为常量时用Max
、Min
、Default
等前缀
当代码中出现一些难以理解的“魔法数字”时,应该将其定义为常量,并使用 Max
、Min
、Default
等前缀来描述其含义。
package mypackage
const (
MaxUserNameLength = 50
MinPasswordLength = 8
DefaultPageSize = 20
)
// 不好的例子:直接使用魔法数字
// if len(userName) > 50 { ... }
// 好的例子:使用常量
if len(userName) > MaxUserNameLength {
// ...
}
49、 私有常量可以使用小写,但要保持包内一致性
虽然 Go 语言的惯例是使用大写字母命名常量,但对于只在包内部使用的私有常量,可以使用小写字母。但是,在一个包内应该保持一致性,要么全部使用大写,要么全部使用小写。推荐使用大写,除非有特殊原因。
package mypackage
const (
maxInternalValue = 100 // 私有常量,小写
MinInternalValue = 0 // Bad: 同一个包内,也应该用小写,保持一致性
)
枚举类型命名
50、定义一个基础类型
需要定义一个基础类型来表示枚举值。通常使用整数类型(如 int
、int8
、int16
、int32
、int64
)或字符串类型。
使用整数类型: 如果枚举值只是简单的标识符,没有其他关联的数据,则通常使用整数类型。
使用字符串类型: 如果枚举值需要更具描述性的字符串表示,或者需要与其他系统进行交互,则可以使用字符串类型。
51、使用常量组定义枚举值
使用 const
关键字和分组声明来定义枚举值。这有助于将相关的常量组织在一起,提高代码的可读性。
type Color int
const (
ColorRed Color = iota // 0
ColorGreen // 1
ColorBlue // 2
)
// 使用 iota 的偏移量
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
52、使用分组前缀
使用一个共同的前缀来分组枚举常量,清晰地表明它们属于同一个枚举类型。
type Color int
const (
ColorRed Color = iota
ColorGreen
ColorBlue
)
53、为枚举类型定义 String() 方法
为了方便打印和调试,应该为枚举类型定义 String()
方法,使其能够返回枚举值的字符串表示。
package main
import "fmt"
type Color int
const (
ColorRed Color = iota
ColorGreen
ColorBlue
)
func (c Color) String() string {
switch c {
case ColorRed:
return "Red"
case ColorGreen:
return "Green"
case ColorBlue:
return "Blue"
default:
return fmt.Sprintf("Color(%d)", c) // 处理未知值
}
}
func main() {
fmt.Println(ColorRed) // 输出: Red
fmt.Println(ColorGreen) // 输出: Green
fmt.Println(ColorBlue) // 输出: Blue
fmt.Println(Color(4)) // 输出: Color(4)
}
可以使用dmarkham/enumer
、abice/go-enum
等一些工具辅助代码生成。
54、使用类型别名提高可读性
可以使用类型别名来简化枚举类型的声明,尤其是在枚举类型名较长的情况下。
type AudioSampleRate uint32
const (
AudioSampleRate44100 AudioSampleRate = 44100
AudioSampleRate48000 AudioSampleRate = 48000
// ...
)
// 可以使用类型别名简化
type ASR = AudioSampleRate
const (
ASR44100 ASR = 44100
ASR48000 ASR = 48000
)
55、避免使用零值作为有效的枚举值
通常情况下,应该避免使用零值作为有效的枚举值,以便能够区分未初始化的枚举值和有效的枚举值。如果需要表示“未设置”或“默认”状态,可以定义一个特殊的枚举值,例如 ColorUnknown
或 StatusDefault
。
56、使用_
跳过iota
的某些值
可以使用空白标识符 _
来跳过 iota
生成的某些值。
type SpecialErrorCode int
const (
_ SpecialErrorCode = iota // 0 (被跳过)
ErrorCodeOne // 1
ErrorCodeTwo // 2
ErrorCodeThree // 3
)
注释
57、文件注释
位于文件顶部,描述文件的用途、作者、版权信息等。
常常使用/* ... */
且多行的形式。
/*
Copyright 2024 The MyProject Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package mypackage
58、包注释
位于 package
声明之前,简要描述包的功能,以单词Package
开始,后接包名。
// Package mypackage provides functions for ...
package mypackage
59、函数/方法注释(文档注释)
位于函数或方法声明之前,使用 //
或 /* ... */
,但推荐使用 //
以便 godoc
工具生成文档。
以函数名或者方法名开头。
// Add adds two integers and returns their sum.
//
// Example:
// sum := Add(1, 2) // sum is 3
//
// Parameters:
// a: The first integer.
// b: The second integer.
//
// Returns:
// The sum of a and b.
func Add(a, b int) int {
return a + b
}
60、类型注释
位于类型声明之前,以类型名开头,常常使用 //
。
// User represents a user in the system.
type User struct {
Name string
Age int
}
61、代码块注释
解释一段代码的逻辑, 常常使用 //
。
// Calculate the average of the numbers.
sum := 0
for _, num := range numbers {
sum += num
}
average := float64(sum) / float64(len(numbers))
62、行尾注释
位于代码行尾,对该行代码进行简短的解释,如无特殊需求推荐使用 //
。
x := 10 // Initialize x to 10
63、通用注释规则
空格:
单行注释
//
后应有一个空格,例如// This is a comment
。多行注释
/* ... */
的开始和结束符号与注释内容之间也应有空格,例如/* This is a multi-line comment */
。
句点(结尾标点):
函数/方法、类型、包、文件注释等描述性注释,推荐使用完整的句子,以句点结尾。这有助于提高可读性,尤其是在使用
godoc
生成文档时。代码块注释和行尾注释可以根据需要使用句子片段或短语,句点不是强制的,但保持一致性很重要。
首字母大小写:
函数/方法、类型、包、文件注释等描述性注释,首字母应大写。
代码块注释和行尾注释可以根据需要使用大小写,但保持一致性很重要。
缩进: 注释的缩进应与其所注释的代码保持一致,以提高代码的整体可读性。
避免嵌套
/* ... */
注释: C 风格的多行注释不支持嵌套。如果需要注释掉一段包含多行注释的代码,可以使用//
进行单行注释。函数名和方法名一般没有参数和返回值的列表解释,而是针对特殊情况进行简单解释。有些公司要求提供参数和返回值的格式说明,这不是Go地道的写法。