书写风格

  • 与标准库风格保持一致,首字母小写且不加结束标点符号。
// 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)
powered by Gitbook该文章修订时间: 2024-08-04 07:27:05

results matching ""

    No results matching ""