入参&返回值

  • 入参和返回值均以小写字母开头。
  • 入参和返回值个数均不能超过 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 {...}
powered by Gitbook该文章修订时间: 2024-03-22 15:30:00

results matching ""

    No results matching ""