前言:
本次课程简要介绍了高质量编程的定义和原则,分享了代码格式、注释、命名规范、控制流程、错误和异常处理五方面的常见编码规范,帮助我们在今后的开发过程中写出更加优秀的代码 …
编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码,一份高质量的代码应该具备以下特点:
Go 语言开发者 Dave Cheney 给出了三条编程原则,在编程中我们应该尽可能遵循这些原则。
如何编写高质量的 Go 代码?
包中声明的每个公共的符号(变量、常量、函数…)都要添加注释;任何既不明显也不简短的公共功能必须予以注释;无论长度或复杂度如何,对库中的任何函数都必须进行注释。
注释应该解释代码的作用、代码是如何做的以及代码的实现原因,还应该解释代码什么情况会出错。
比如 Go 的标准库中对于函数也有注释来说明功能:
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
(PS:有一个例外,不需要注释实现接口的方法。)
推荐使用 gofmt 自动格式化代码。
gofmt 是 Go 语言官方提供的工具,能自动格式化 Go 语言代码为官方统一风格,常见 IDE 都支持方便的配置。此外 goimports 也是 Go 语言官方提供的工具,可以实现自动增删依赖的包引用、将依赖包按字母序排序并分类。
在 GoLand 中开启 gofmt 支持:
GoLand 提供了 File Watchers 功能,将 go fmt 添加进去,修改触发的条件即可。
配置完成后每次保存代码时 go fmt 就会自动格式化代码。
ServeHTTP
),但当其位于变量开头且不需要导出时,使用全小写(比如 xmlHTTPRequest
)。foo
的包某个函数返回类型 Foo
时,可以省略类型信息而不导致歧义。foo
的包某个函数返回类型 T
时(T 并不是 Foo),可以在函数名中加入类型信息。流程控制语句应该优先处理错误情况、特殊情况,尽早返回或继续循环来减少嵌套。
✔原则:尽量保持正常代码路径为最小缩进。
比如下面的代码就是一个错误的示范:
// Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil // normal case
}
return err
}
return err
}
if
条件内,成功退出的条件是 return nil
,必须仔细匹配大括号才能发现;调整后的代码如下:
// Good
func OneFunc() error {
if err := doSomething(); err != nil {
return err
}
if err := doAnotherThing(); err != nil {
return err
}
return nil // normal case
}
编写流程控制代码时要尽可能遵循线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支。
errors.New()
来创建匿名变量来直接表示简单错误。fmt.Errorf()
。Github 仓库中的示例代码:
func defaultCheckRedirect(req *Request, via []*Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
fmt.Errorf()
中使用 %w
关键字来将一个错误关联至错误链中。Github 仓库中的示例代码:
list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
if err != nil {
return fmt.Errorf("reading srcfiles list: %w", err)
}
errors.AS()
。Github 仓库中的示例代码:
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)
}
}
Go语言不支持传统的 try…catch…finally
这种异常,但是 Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。
✔ panic 的注意事项:
painc()
。panic()
,若问题可以被解决或屏蔽,建议使用 error
替代。✔ recover 的注意事项:
recover()
只能在被 defer 的函数中使用。补充 - Go 中 defer 的概念:
Go 语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行。