高质量编程概念
高质量编程指的是编写出正确可靠、简洁清晰的代码。其需要达成以下条件:各种边界条件考虑晚辈;能够处理异常情况,保证稳定性;易读易维护。由Go语言开发者 Dave Cheney 提出的编程原则分为以下几点:
- 简单性:消除多余的复杂性,要以简单清晰的逻辑编写代码,不理解的代码无法修复改进。
- 可读性:代码是写给人看的,而不是机器,要编写可维护代码,首先要确保代码可读。
- 生产力:团队整体工作效率十分重要
编码规范
为了编写高质量的Go代码,需要遵从以下编码规范:
1.代码格式
编写Go代码推荐使用gofmt自动格式化代码。gofmt是Go语言官方提供的工具,能够自动格式化 Go 代码为官方统一风格。常见的 IDE 都能很方便的完成 gofmt 工具的配置,例如 GoLand IDE 默认启用了在编辑结束后自动对 Go 文件格式化的功能;Go 语言还为我们提供了 goimports 工具,实际等同gofmt加上依赖包管理,可以实现自动增删依赖的包引用。
2.注释
注释的作用是帮助开发者和使用者更好地理解代码。注释应该做的有:
- 解释代码作用。
- 解释代码如何做的: 适合注释实现过程。
- 解释代码实现的原因: 适合解释代码的外部因素,提供额外上下文。
- 解释代码什么情况下会出错: 适合解释代码的限制条件。 5.公共符号始终要注释。
3.命名规范
1.简洁胜于冗长。 2.缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写 例如使用 ServeHTTP 而不是 ServeHttp,使用 XMLHTTPRequest 或者 xmlHTTPRequest 3.变量在定义时距离其被使用的地方越远,则需要携带越多的上下文信息 4.全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义
4.控制流程
1.避免嵌套,保持正常流程清晰 在下面的示例中,如果两个分支中都包含 return 语句,则可以去除冗余的 else:
// Bad
if foo {
return x
} else {
return nil
}
// Good
if foo {
return x
}
return nil
2.尽量保持正常代码路径为最小缩进,优先处理错误情况和特殊情况,尽早返回或继续循环来减少嵌套,示例代码如下:
// Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil // normal case
}
return err
}
return err
}
// Good
func OneFunc() error {
err := doSomething()
if err != nil {
return err
}
err = doAnotherThing()
if err != nil {
return err
}
return nil // Normal case
}
5.错误和异常处理
1.简单错误:简单错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误;优先使用 errors.New 来创建匿名变量直接表示该错误;如果有格式化需求,使用 fmt.Errorf,示例代码如下:
func defaultCheckRedirect(req *Request, via []*Request) error {
if len(via) >= 10 {
return errors.New("Stopped after 10 redirects")
}
return nil
}
2.错误的 Wrap 和 Unwrap:错误的 Wrap 实际上提供了一个错误嵌套另一个错误的能力,从而生成了一个错误的跟踪链。在 fmt.Errorf 中使用 %w 关键字来将一个错误关联到错误链中,示例代码如下:
result, err := doSomething()
if err != nil {
return fmt.Errorf("error when do something: %w", err)
}
3.错误判定:判定一个错误是否为特定错误,使用 errors.Is,不同于使用 ==, 使用该方法可以判断错误链上的所有错误中是否含有特定错误,示例代码如下:
go复制代码data, err := loadDataFromFile("PATH")
if errors.Is(err, fs.ErrNotExist) {
// File not found at filesystem
data.NewFile()
}
4.在错误链上获取特定种类的错误,使用 errors.As,具体代码如下:
data, err := loadDataFromFile("PATH")
if err != nil {
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed to load file at path:", pathError.Path)
} else {
return nil
}
}
5.panic 的使用:不建议在业务代码中使用 panic;调用函数若不包含 recover 会造成程序崩溃;若问题可以被屏蔽或解决,建议使用 error;当程序在启动节点发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic。
6.recover 的使用:recover 只能在被 defer 的函数中使用;嵌套无法生效;只在当前 goroutine 生效 defer 的语句是先进后出;recover 的一个重要作用是在 panic 后记录重要信息。