先记录初步的想法,后续完善。 对于真正意外的情况,表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题,栈溢出,我们才使用panic。 对于其他的错误情况,我们应该期望使用error来进行判定。
1. panic
panic
的时机:
- 在程序启动时,如果强依赖服务出现故障,应该
panic
退出。 - 在程序启动时,如果发现加载的配置明显不符合要求,应该
panic
退出(防御编程)。 - 其他时候,如果不是不可恢复的错误,不应该使用
panic
,应该返回error
。 - 在程序入口处应该对
panic
进行recover
,防止系统退出;如gin
的recovery
中间件。 - 在程序中我们应该避免使用野生
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() }() }
<a name="a0Wip"></a>
## 2. error Type
`sentinel error`<br />哨兵错误,简单说就是预定义的错误,比如标准库的`io.EOF`、`sql.ErrNoRows`
`error Type`<br />自定义错误类型,实现`error interface`
`opaque error`<br />不透明错误,内部定义一个私有接口,公开一个判断错误的行为函数,让外部断言行为,避免引入`error`包
```go
type temporary interface {
Temporaray() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}
3. handling error
error
的处理时机:
- 我们应该在应用程序中使用
github.com/pkg/errors
,避免在公共库中使用。 error
是函数的最后一个返回值,当err != nil
,函数的其他返回值是不可用的,不应该对其他返回值抱有期待。- 函数出现
error
应该只做一次处理(直接返回或降级处理),如果需要携带信息,使用pkg/errors.WithMessage
如果是应用程序外的其他库(标准库,第三方库)出现
error
,应该使用pkg/errors.Wrap
添加堆栈信息,而且只在该错误第一次出现的地方使用Wrap
func (u *User)Get(db gorm*DB) (*User, error) {
var user User
err := db.First(&user).Error
if err != nil {
return nil, errors.Wrap(err, "get user failed")
}
return &user, nil
}
使用标准库的
errors.Is
判断错误func f() error {
err := A()
if errors.Is(err, io.EOF) {
// ...
}
return nil
}
使用标准库的
errors.As
给错误类型赋值func f() error {
err := A()
var errA errorA
if errors.As(err, &errA) {
// ...
}
return nil
}