书写风格
- 与标准库风格保持一致,首字母小写且不加结束标点符号。
// Bad
var ErrRecordNotFound = errors.New("Record not found")
var ErrRecordNotFound = errors.New("record not found.")
var ErrRecordNotFound = errors.New("Record not found.")
// Good
var ErrRecordNotFound = errors.New("record not found")
error 处理
- 显示处理 error。
如果 error 作为函数的值返回,必须对 error 进行处理,或使用空白标识符忽略。对于defer xx.Close()
可以不用显式处理。
- error 作为函数返回值且有多个返回值时,error 必须是最后一个参数。
// Bad
func do() (error, int) {
}
// Good
func do() (int, error) {
}
- 采用独立的错误流进行处理。
// Bad
if err != nil {
// handle error
} else {
// normal code
}
// Good
if err != nil {
// handle error
return // or continue, etc.
}
// normal code
- Fail Fast 原则。
如果出现失败应该立即返回 error,如果继续处理,则属于特殊情况需要添加注释。
- 如果函数返回值需用于初始化其他变量,则采用下面的方式。
x, err := f()
if err != nil {
// error handling
return // or continue, etc.
}
// use x
- 错误判断独立处理,不与其他变量组合判断。
一个可能引发的问题就是 err 如果为 nil,但是满足其他逻辑进入到 if 块内,读取 err 值将引发 panic。
// Bad
x, y, err := f()
if err != nil || y == nil {
return err // 当y与err都为空时,函数的调用者会出现错误的调用逻辑
}
// Good
x, y, err := f()
if err != nil {
return err
}
if y == nil {
return fmt.Errorf("some error")
}
- 生成带参数的 error 使用
fmt.Errorf
。
// Bad
errors.New(fmt.Sprintf("module xxx: %v",err))
// Good
fmt.Errorf("module xxx: %v", err)
- 不要包装系统调用错误,并给出一些没意义的附加信息。
// Bad
err := exe.Run()
if err != nil {
return fmt.Errorf("run error %s", err.Error())
}
// Good
return exe.Run()
panic 处理
- 不要随便 panic。
在业务逻辑处理中禁止使用 panic。因为 panic 是级联失败(cascading failures)的主要根源。如果发生错误,该函数应该返回错误,让调用方决定如何处理它。
// Bad
func run(args []string) {
if len(args) == 0 {
panic("an argument is required")
}
// ...
}
func main() {
run(os.Args[1:])
}
// Good
func run(args []string) error {
if len(args) == 0 {
return errors.New("an argument is required")
}
// ...
return nil
}
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
panic/recover 不是错误处理的合适策略,仅当发生不可恢复的异常(如 nil 引用)时,才可以 panic。
在 main 包中程序初始化是一个例外,如程序启动时,文件无法打开或数据库无法连接导致程序无法正常运行可使用 panic。
对于其它的包,可导出的接口也不能有 panic。
- 在 main 包中使用 log.Fatal 或 log.Fatalf 结束程序而不是 panic。
如果 main 中需要使用 panic,建议使用 log.Fatal 或 log.Fatalf 来取代 panic,因为这样可以记录错误的同时结束程序,方便排查问题。
- panic 只能在当前 Goroutine 被捕获。
panic 捕获最晚要在当前 Goroutine 最顶层将其捕获,在其他 Goroutine 中无法捕获当前 Goroutine 的 panic。每个自行启动的 Goroutine,必须在入口处捕获 panic,并打印详细堆栈信息或进行其它处理。
下面是一个反面示例,其他 Goroutine 中无法捕获当前 Goroutine 的 panic。
package main
import (
"fmt"
"time"
)
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
go func() {
fmt.Println("======begin work======")
panic("nil pointer exception")
}()
time.Sleep(1 * time.Second)
fmt.Println("======after work======")
}
程序将意外终止并输出:
======begin work======
panic: nil pointer exception
goroutine 6 [running]:
main.main.func2()
/Users/dablelv/work/code/test/main.go:16 +0x65
created by main.main
/Users/dablelv/work/code/test/main.go:14 +0x48
recover 处理
- recover 用于捕获 runtime 的异常,禁止滥用 recover。
- recover 只有在 defer 中调用才会生效。
必须在 defer 中使用,一般用来捕获程序运行期间发生异常抛出的 panic 或程序主动抛出的 panic。
package main
import (
"log"
)
func main() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
getOne()
panic(44) //手动抛出 panic
}
// getOne 模拟 slice 越界运行时抛出的 panic。
func getOne() {
defer func() {
if err := recover(); err != nil {
// do something or record log
log.Println("exec panic error: ", err)
// log.Println(debug.Stack())
}
}()
var arr = []string{"a", "b", "c"}
log.Println("hello,", arr[4])
}
运行结果:
2022/03/27 10:48:42 exec panic error: runtime error: index out of range [4] with length 3
2022/03/27 10:48:42 exec panic error: 44
类型断言
- 类型断言使用 comma ok 式。
类型断言的单个返回值形式如果断言失败将产生 panic。因此,请始终使用 comma ok 式。如果不关心是否成功,ok 可显示使用空标识符(下划线)忽略。
// Bad
t := i.(string)
// Good
t, ok := i.(string)
if !ok {
// 优雅地处理错误。
}
// 如果不关心是否成功,可显示忽略 ok。
t, _ := i.(string)