Go 编程规范 #
代码组织 #
目录 #
/cmd
# 程序和库的私有代码。这里的代码都是你不希望被别的应用和库所引用的.
# 把你真正的应用代码放在 /internal/app 目录 (比如: /internal/app/myapp) 把你的应用间共享的代码放在 /internal/pkg 目录 (比如: internal/pkg/myprivlib)
/internal
/pkg
不应该存在的目录 #
/src
参考:
文件内容 #
建议文件按以下顺序进行布局:
- General Documentation: 对整个模块和功能的完整描述注释,写在文件头部。
- package: 当前 package 定义
- imports: 包含的头文件
- Constants: 常量
- Typedefs: 类型定义
- Globals: 全局变量定义
- functions: 函数实现
语言规范 #
申明 slice #
申明 slice 时,建议使用 var 方式申明,不建议使用大括号的方式
// GOOD:
var t []string
// BAD:
t := []string{}
embedding #
- embedding 只用于 “is a” 的语义下,而不用于 “has a” 的语义下
- 语义上 embedding 是一种继承关系,而不是成员关系
- 一个定义内,多于一个的 embedding 尽量少用
- 一个定义内有多个 embedding,则很难判断某个成员变量或函数是从哪里继承得到的
- 一个定义内有多个 embedding,危害和在 python 中使用
from xxx import *
是类似的
type Automobile struct {
// ...
}
type Engine struct {
// ....
}
// GOOD:
type Car struct {
Automobile // Car is a Automobile
engine Engine // Car has a Engine
}
// BAD:
type Car struct {
Automobile // Car is a Automobile
Engine // Car has a Engine, but Car is NOT a Engine
}
函数参数和返回值 #
-
必须
- 函数返回值小于等于 3 个,大于 3 个时必须通过 struct 进行包装
-
建议
- 函数参数不建议超过 3 个,大于 3 个时建议通过 struct 进行包装
- 函数返回值
- 逻辑判断型:返回值的意义代表 “真” 或 “假”,返回值类型定义为 bool
- 操作型:返回值的意义代表 “成功” 或 “失败”,返回值类型定义为 error
- 获取数据型:返回值的意义代表 “有数据” 或 “无数据 / 获取数据失败”,返回值类型定义为(data, error)
缩进 #
- 使用 tab 进行缩进。
- 跨行的缩进使用 gofmt 的缩进方式。
- 设置
tabstop=4
空格 #
- 圆括号、方括号、花括号内侧都不加空格
- 逗号、冒号(slice 中冒号除外)前不加空格,后边加一个空格
- 所有二元运算符前后各加一个空格(作为函数参数时除外)
var (
s = make([]int, 10)
)
// GOOD:
func foo() {
m := map[string]string{"language": "golang"}
r := 1 + 2
func1(1+2)
fmt.Println(m["language"])
}
// BAD:
func foo() {
m := map[string]string{ "language" : "golang" }
r := 1+2
func1(1 + 2)
fmt.Println(m[ "language" ])
}
命名 #
文件名 #
文件名都使用小写字母,如果需要,可以使用下划线分割
文件名的后缀使用小写字母
函数名 / 变量名 #
采用驼峰方式命名,禁止使用下划线命名
首字母是否大写,根据是否需要外部访问来决定
常量 #
尽量不要在程序中直接写数字,特殊字符串,全部用常量替代
包名 #
关于包的命名,Go 团队建议以简单,扁平为原则。
例如,strutils
是 string utility
函数的名字,http 是 HTTP 请求的名字。
包的名字应避免使用下划线,中划线或掺杂大写字母。
编程实践 #
error string #
error string 尽量使用小写字母,并且结尾不带标点符号
Don’t panic #
除非出现不可恢复的程序错误,不要使用 panic,用多返回值和 error。
关于 lock 的保护 #
- 如果临界区内的逻辑较复杂、无法完全避免 panic 的发生,则要求适用 defer 来调用 Unlock,即使在临界区过程中发生了 panic,也会在函数退出时调用 Unlock 释放锁
- go 提供了 recover,可以对 panic 进行捕获,但如果 panic 发生在临界区内,则可能导致对锁的使用没有释放
- 这种情况下,即使 panic 不会导致整个程序的奔溃,也会由于” 锁不释放 “的问题而使临界区无法被后续的调用访问
- 上述操作如果造成临界区扩大后,需要建立单独的一个函数访问临界区。
func doDemo() {
lock.Lock()
// step1: 临界区内的操作
lock.Unlock()
// step2: 临界区外的操作
}
// 如果改造为 defer 的方式,变为如下代码,实际上扩大了临界区的范围(step2 的操作也被放置在临界区了)
func doDemo() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作
// step2: 临界区外的操作
}
// 需要使用单独的匿名函数,专门用于访问临界区
func doDemo() {
func() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作操作
}()
// step2: 临界区外的操作
}
unsafe package #
- 除非特殊原因,不建议使用 unsafe package
- 比如进行指针和数值 uintptr 之间转换就是一个特殊原因
叶王 © 2013-2024 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。