先记录初步的想法,后续完善。 对于真正意外的情况,表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题,栈溢出,我们才使用panic。 对于其他的错误情况,我们应该期望使用error来进行判定。

1. panic

panic的时机:

  • 在程序启动时,如果强依赖服务出现故障,应该panic退出。
  • 在程序启动时,如果发现加载的配置明显不符合要求,应该panic退出(防御编程)。
  • 其他时候,如果不是不可恢复的错误,不应该使用panic,应该返回error
  • 在程序入口处应该对panic进行recover,防止系统退出;如ginrecovery中间件。
  • 在程序中我们应该避免使用野生goroutine,应该使用同一的 Go 函数进行创建,这个函数中会进行 recover ,避免因为野生 goroutine panic 导致主进程退出。 ```go

// 仅限于无参的函数,如果是有参的,做不到同一公用 func NewGo(f func()) { go func(){ defer func() { if err := recover(); err != nil { log.Printf(“panic: %+v”, err) } } f() }() }

  1. <a name="a0Wip"></a>
  2. ## 2. error Type
  3. `sentinel error`<br />哨兵错误,简单说就是预定义的错误,比如标准库的`io.EOF`、`sql.ErrNoRows`
  4. `error Type`<br />自定义错误类型,实现`error interface`
  5. `opaque error`<br />不透明错误,内部定义一个私有接口,公开一个判断错误的行为函数,让外部断言行为,避免引入`error`包
  6. ```go
  7. type temporary interface {
  8. Temporaray() bool
  9. }
  10. func IsTemporary(err error) bool {
  11. te, ok := err.(temporary)
  12. return ok && te.Temporary()
  13. }

3. handling error

error的处理时机:

  • 我们应该在应用程序中使用github.com/pkg/errors,避免在公共库中使用。
  • error是函数的最后一个返回值,当err != nil,函数的其他返回值是不可用的,不应该对其他返回值抱有期待。
  • 函数出现error应该只做一次处理(直接返回或降级处理),如果需要携带信息,使用pkg/errors.WithMessage
  • 如果是应用程序外的其他库(标准库,第三方库)出现error,应该使用pkg/errors.Wrap添加堆栈信息,而且只在该错误第一次出现的地方使用Wrap

    1. func (u *User)Get(db gorm*DB) (*User, error) {
    2. var user User
    3. err := db.First(&user).Error
    4. if err != nil {
    5. return nil, errors.Wrap(err, "get user failed")
    6. }
    7. return &user, nil
    8. }
  • 使用标准库的errors.Is判断错误

    1. func f() error {
    2. err := A()
    3. if errors.Is(err, io.EOF) {
    4. // ...
    5. }
    6. return nil
    7. }
  • 使用标准库的errors.As给错误类型赋值

    1. func f() error {
    2. err := A()
    3. var errA errorA
    4. if errors.As(err, &errA) {
    5. // ...
    6. }
    7. return nil
    8. }