高质量编程集及规范 | 青训营

64 阅读4分钟

编码规范

1.代码格式

使用gofmtgoimports自动格式化代码。

gofmt是Go语言官方提供的工具,能自动化Go语言代码为官方统一风格;goimports实际等于gofmt加上依赖包管理、自动增删依赖的包引用、将依赖包按字母序排序并分类。

2.注释

(1) 解释代码作用:解释公共的常量或对外公开的函数,描述他们的功能和用途。

(2) 解释代码如何做:对代码功能(比如某个函数)的实际实现进行说明。

(3) 解释代码实现原因:说明代码实现的外部原因,提供额外的上下文。

(4) 解释代码什么情况会出错:解释代码有哪些限制条件。

(5) 公共符号要始终注释:包中声明的公共符号(变量、常量、函数、结构)添加注释,库中的任何函数必须进行注释,不需要注释实现接口的方法。

3.命名规范

variable(变量名):

(1) 简洁胜于冗长。

(2) 缩略词大写,但是,当其位于变量开头且不需导出时,全小写。(使用ServeHTTP而不是ServeHttp

(3) 变量距其被使用的的地方越远,需要携带越多的上下文信息。(比如全局变量)

function(函数名):

(1) 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现。

(2) 函数名尽量简短。

(3) 当包名 和 某个函数的返回类型为相同 时,可以省略类型信息而不导致歧义。

(4) 当包名 和 某个函数的返回类型为不同 时,可以在函数名中加入类型信息。

package(包名):

(1) 只由小写字母组成,不包含大写字母和下划线等字符。

(2) 简短并包含一定的上下文信息。

(3) 不要与标准库同名。(比如syncstring

(4) 尽量使用单数而非复数。

(5) 不使用常用变量名做包名。

4.控制流程

(1) 避免嵌套,保持流程清晰。如果两个分支都有return,则需要去除冗余的else。

(2) 优先处理错误和特殊情况,尽早返回或继续循环来减少嵌套。

5.错误和异常处理

1) 简单错误:

简单错误是指只出现一次的错误,而且在其他地方都不需要捕获该错误。

(1) 优先使用 errors.New来创建匿名变量直接表示简单错误。

(2) 如果有格式化要求则使用 fmt.Errorf

func defaultCheckRedirect(req *Request, via []*Request) error {
    if len(via) >= 10 {
        return errors.New("stopped after 10 redirects")
    }
    return nil
}

2)(复杂的错误)错误的 Wrap (包装)和 Unwarp(解包)

(1) 错误的 wrap 实际上是提供一个error(错误)嵌套到另一个error的能力,从而形成一条error的跟踪链。

(2) 在fmt.Errorf中使用 %w 关键字来将一个error错误关联到错误链中。

list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
if err != nil {
    return fmt.Errorf("reading srcfiles list: %w", err)
}

3)error (错误判定)

(1) 使用errors.ls(函数)判断错误链上是否有指定类型的错误。

data, err = lockedfile.Read(targ)
if errors.Is(err, fs.ErrNotExist) {
    return []byte{}, nil
}
return data, err

(2) 使用errors.As获取错误链上的特定种类的错误,可以把特定类型错误的内容取出

if _, err := os.Open("non-existing"); err != nil {
    var pathError *fs.PathError
    if errors.AS(err, &pathError) {
        fmt.Println("Failed at path: ", pathError.Path)
    } else {
        fmt.Println(err)
    }
}

4)panic

panic比error更严重,一般表示程序无法正常工作,用于真正异常的情况。

(1) 不建议带业务代码中使用 panic

(2) 调用函数不包含 recover会造成程序崩溃。

(3) 若问题可以被屏蔽或解决,建议使用 error 代替 panic

(4) 当程序启动阶段发生不可逆转的错误时,可以在initmain函数中使用panic

5)recover

记录下出现 panic 的上下文,将 panic 的调用堆栈信息记录并输出。

(1) 生效范围:recover 只能在被defer的函数中使用。

(2) 嵌套无法生效。

(3) 只在当前goroutine生效。

(4) defer的语句是后进先出。

func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
    defer func() {
        if e := recover(); e!= nil {
            if se, ok := e.(scanError); ok {
                err = se.err
            } else {
                panic(e)
            }
        }
    }
}

(5) 如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈:

func (t *treeFS) Open(name string) (f fs.File, err error) {
    defer func() {
        if e := recover(); e != nil {
            f = nil
            err = fmt.Errorf("gifts panic : %v\n", e, debug.Stack())
        }
    }()
}