入参&返回值
- 入参和返回值均以小写字母开头。
- 入参和返回值个数均不能超过 5 个,如果超过,请封装成新的类型。
- 尽量用值传递,非指针传递。
- 类型为 map,slice,chan,interface 不要传递指针。
- 返回值超过 3 个,或有相同类型的返回值,或从上下文中不清楚返回值的含义,使用命名返回,其它情况不建议使用命名返回。
// Parent1 ...
func (n *Node) Parent1() *Node
// Parent2 ...
func (n *Node) Parent2() (*Node, error)
// Location ...
func (f *Foo) Location() (lat, long float64, err error)
- 入参和返回值顺序根据关联性排在一起。
- 尽量用 error 表示执行是否成功,而不是用 bool 或者 int。
- 表示执行状态的返回值应该放在最后。
// Bad
ret, info := ModifyUserInfo(user)
// Good
info, ret := ModifyUserInfo(user)
- 不要返回多个用于控制流程的状态。
// Bad
isContinue, retCode := p.processUnity()
// Good
retCode := p.processUnity()
- 如果传入的参数通常是固定的,考虑通过实现多个函数实现默认参数。
如下面这个函数的第二个参数是没有必要的,大部分时候都是 +1,一个 IncCounter() 和一个 IncCounterN() 即可。可参考标准库包的 Split() 和 SplitN()。
metrics.IncrCounter(cntCacheMissKey, 1)
- 批量查询函数返回值使用 slice 还是 map。
有时后我们需要根据多个 ID 查询对应的值,可能会出现部分失败的情况,如某个 ID 不存在。如果不允许部分失败,使用 slice 返回值,如果允许部分失败使用 map。
// GetUserInfoBatch 批量获取用户信息(需全部成功)。
func GetUserInfoBatch(uids ...uint64) ([]UserInfo, error) {
...
}
// GetUserInfoBatch 批量获取用户信息(允许部分失败)。
func GetUserInfoBatch(uids ...uint64) (map[uint64]UserInfo, error) {
...
}
成员函数
- 如果方法不使用类的成员,应该实现为非成员函数。
- 如果非成员函数要使用类的多个属性时,应该实现为成员函数。
局部变量
- 如果局部变量仅被使用一次,且不能起到解释逻辑的作用时,应该删除局部变量,直接内联。
// Bad
ids := GetIDs()
Foo(ids)
// Good
Foo(GetIDs())
defer
- 当存在资源管理时,应紧跟 defer 函数进行资源的释放。
- 判断是否有错误发生之后,再 defer 释放资源。
resp, err := http.Get(url)
if err != nil {
return err
}
// 如果操作成功,再 defer Close()
defer resp.Body.Close()
- 禁止在循环中使用 defer。
// 不要这样使用
func filterSomething(values []string) {
for _, v := range values {
fields, err := db.Query(v) // 示例,实际不要这么查询,防止 SQL 注入
if err != nil {
// ...
}
defer fields.Close()
// 继续使用fields
}
}
// 应当使用如下的方式:
func filterSomething(values []string) {
for _, v := range values {
func() {
fields, err := db.Query(v) // 示例,实际不要这么查询,防止 SQL 注入
if err != nil {
// ...
}
defer fields.Close()
// 继续使用 fields
}()
}
}
- 正常逻辑不应该在 defer 中执行。
减少嵌套(圈复杂度)
- 嵌套深度不能超过4层。
从函数名开始算第一层,当函数的嵌套深度超过 4层,往往会导致圈复杂度过高,函数变得复杂不可读,我们可以拆分函数来减少嵌套深度。
// AddArea 添加成功或出错。
func (s *BookingService) AddArea(areas ...string) error {
s.Lock()
defer s.Unlock()
for _, area := range areas {
for _, has := range s.areas {
if area == has {
return srverr.ErrAreaConflict
}
}
s.areas = append(s.areas, area)
s.areaOrders[area] = new(order.AreaOrder)
}
return nil
}
// 建议调整为这样:
// AddArea 添加成功或出错。
func (s *BookingService) AddArea(areas ...string) error {
s.Lock()
defer s.Unlock()
for _, area := range areas {
if s.HasArea(area) {
return srverr.ErrAreaConflict
}
s.areas = append(s.areas, area)
s.areaOrders[area] = new(order.AreaOrder)
}
return nil
}
// HasArea ...
func (s *BookingService) HasArea(area string) bool {
for _, has := range s.areas {
if area == has {
return true
}
}
return false
}
- 单函数圈复杂度最大值 <=10。
- 条件不满足或出现错误应尽早返回。
代码也可以优先处理条件不满足或错误的情况,尽早返回或继续循环来减少嵌套。
// Bad
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
// Good
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
魔法字面量
- 除了 0 和 1,不要使用魔法数字。
// Bad
func getArea(r float64) float64 {
return 3.14 * r * r
}
func getLength(r float64) float64 {
return 3.14 * 2 * r
}
// Good
// PI 圆周率
const PI = 3.14
func getArea(r float64) float64 {
return PI * r * r
}
func getLength(r float64) float64 {
return PI * 2 * r
}
- 如果字符串字面量出现 >=2 次,则禁止使用,用一个有名称的常量代替,可读性更好。
// Bad
rsp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(req)))
// Good
const JsonContentType = "application/json"
rsp, err := http.Post(url, JsonContentType, bytes.NewBuffer([]byte(req)))
函数分组与顺序
- 函数应该放在
struct
,const
,var
的后面。 - 构造函数应该放在其他函数之前,如
newXYZ()/NewXYZ()
。 - 导出的函数应该放在非导出函数前面
- 同一文件中的函数应按接收者分组。
- 由于函数是按接收者分组的,因此普通工具函数应在文件末尾出现。
- 函数应按粗略的调用顺序排序。
按照上面的规则,下面给出好坏文件内容布局示例。
// Bad
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
// Good
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}