Effective Go
如何编写清晰、地道的 Go 代码
Formatting 格式化
在 Golang 中,gofmt
以包未处理对象而非源文件,它将 Go 程序按照标准风格缩进、对齐,保留注释并在需要时重新格式化。
- Indentation 缩进:使用
制表符 Tab
缩进,gofmt
默认使用 - Line length 行长度:Go 对行的长度没有限制
- Parentheses 括号:控制结构(
if
,for
,switch
)在语法上并不需要圆括号
Commentary 注释
Go 支持 C 风格的块注释 /* */
和 C++ 风格的单行注释 //
,其中,//
注释更常用,而 /* */
则主要用于包的注释
godoc
即使一个程序,又是一个 Web 服务器,它对 Go 的源码进行处理,并提取包中的文档内容:
出现在顶级声明之前,且与该声明之间没有空行的注释,将与该声明一起被提出来,作为该条目的说明文档。
每个包都应包含一段包注释,即放置在包子句前的一个块注释。
对于包含多个文件的包,包注释只需出现在其中的任一文件中即可。
包注释应在整体上对该包进行介绍,并提供包的相关信息。
它将出现在 godoc
页面中的最上面,并为紧随其后的内容建立详细的文档。
|
|
Names 命名
Package names 包名
当一个包被导入后,包名就会成为内容的访问器 import "bytes"
,按照惯例,包应当以某个小写的单个单词命名,且不应使用下划线或驼峰记法。
例如,err
的命名就是出于简短考虑。
包名是导入时所需的唯一默认名称,它并不需要在所有源码中保持唯一,即便在少数发生冲突的情况下,也可为导入的包选择一个别名来局部使用。
另一个约定:包名应为其源码目录的基本名称。例如,src/pkg/encoding/base64
中的包应作为 encoding/basee64
导入,其包名应为 base64
而不是 encoding_base64
/ encodingBase64
.
长命名并不会使包更具有可读性,反而一份有用的说明文档通常比额外的长名更具价值。
Getter / Setter
Go 并不对 getter 和 setter 提供自动支持。
如将 Get
放入 getter 的名字中,既不符合习惯,也没有必要,但大写字母作为字段导出的 getter 是一个不错的选择,另外 Set
放入 setter 是个不错的选择。
|
|
Interface names 接口名
按照规定,只包含一个方法的接口应当以该方法的名称加上 er
后缀来命名,如 Reader
/ Writer
/ Formater
等。
字符串转换方法命名应为 String
而非 ToString
MixedCaps 驼峰记法
Go 中约定使用驼峰记法
分号
和 C 一样,Go 的正式语法使用分号 ;
来结束语句,但 Go 的分号不一定出现在源码中,而是词法分析器会使用一条简单的规则来自动插入分号
规则:如在新行前的最后一个标记为标识符(int
/float64
等)、数值或字符串常量之类的基本字面或break
、continue
、fallthrough
、return
、++
、--
、)
、}
之一,则词法分析器将始终在该标记后面插入分号,即如果新行前的标记为语句的末尾,则插入分号;
。
通常,Go 程序只在诸如 for
循环子句这样的地方使用分号,来以此将初始化器、条件及增量元素分开;
Control structures 控制结构
Go 不再使用 do
/ while
循环,只有一个更为通用的 for
,
|
|
Go 没有逗号操作符,且
++
/--
是语句而非表达式
|
|
switch
更加灵活,其表达式无需为常量或整数,case
语句会自上而下逐一进行求值直至匹配为止,它不会自动下溯,但 case
可通过逗号分隔来列举相同的处理条件
break
语句会使 switch
提前终止
|
|
if
强制使用大括号,并且接受初始化语句
|
|
Function 函数
Go 与众不同的特性之一,就是函数和方法可以返回多个值,返回值或结果“形参”可被命名,并作常规变量使用。
Go 的 defer
语句用于预设一个函数调用(即延迟执行函数),该函数会在执行 defer
的函数返回之前立即执行。
被推迟的多个函数,会按照后进先出(LIFO)的顺序执行。
|
|
Data 数据
new(T)
会为类型为 T
的新项分配已置零的内存空间, 并返回它的地址,也就是一个类型为 *T
的值(返回一个指针, 该指针指向新分配的,类型为 T
的零值)。
内建函数 make(T, args)
的目的不同于 new(T)
。它只用于创建切片、映射和信道,并返回类型为 T
(而非 *T
)的一个已初始化 (而非置零)的值。 出现这种用差异的原因在于,这三种类型本质上为引用数据类型,它们在使用前必须初始化。
|
|
Array 数组
数组主要用作切片的构件,主要特点:
- 数组是值,讲一个数组赋值给另一个数组会复制其所有元素
- 如将数组作为参数传入某个函数,则会收到该数组的一份副本而非指针
- 数组的大小是其类型的一部分
|
|
Slice 切片
切片通过对数组进行封装,为数据序列提供了更通用、强大而方便的接口。
slice 保存了对底层数组的引用,如将某个 slice 赋值给另一个 slice,则他们会引用同一个数组。
若某个函数将一个切片作为参数传入,则它对该切片元素的修改对调用者而言同样可见, 这可以理解为传递了底层数组的指针
只要切片不超出底层数组的限制,它的长度就是可变的,只需将它赋予其自身的切片即可。切片的容量可通过内建函数 cap
获得,它将给出该切片可取得的最大长度。
若数据超出其容量,则会重新分配该切片,返回值即为所得的切片。
尽管 Append 可修改 slice 的元素,但切片自身(其运行时数据结构包含指针、长度和容量)是通过值传递的.
二维数组
一种是独立地分配每一个切片;而另一种就是只分配一个数组, 将各个切片都指向它
|
|
Map
可以关联不同类型的值。其键可以是任何相等性操作符支持的类型, 如整数、浮点数、复数、字符串、指针、接口(只要其动态类型支持相等性判断)、结构以及数组。 切片不能用作映射键,因为它们的相等性还未定义。与切片一样,
映射也是引用类型。 若将映射传入函数中,并更改了该映射的内容,则此修改对调用者同样可见。
若试图通过映射中不存在的键来取值,就会返回与该映射中项的类型对应的零值
要删除 map 中的某项,可使用内建函数 delete
,它以映射及要被删除的键为实参。
即便对应的键不在该 map 中,此操作也是安全的。