将每一大块知识点,整理出一个理论部分和一个实践部分,实践部分就是工作中遇到的相关的话题。
1.error 与 exception
go 的 error 就是一个接口,一个值,通常用error.news 来定义一个error
var byZero = errors.New("divide no zero")
var byZero1 = errors.New("divide no zero")
if byZero == byZero1 {
println("yes")
} else {
println("no")
}
//errors.new 返回内部的errorString 但是是指针,这里防止了两个string错误描述一模一样的情况。
如果一个函数返回了 value, error,你不能对这个 value 做任何假设,必须先判定 error。唯一可以忽略 error 的是,如果你连 value 也不关心,对于真正的异常,go预留了panic,fatal error 这个不能假设调用者做任何处理
对于真正意外的情况,那些表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题、栈溢出,我们才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定。
- 简单
- 考虑失败,而不是成功(Plan for failure, not success)
- 没有隐藏的控制流
- 完全交给你来控制 error
- Error are values
2. error 类型
- 哨兵形式,先预留可能出现的error 然后作对比 if err ==…
这种方式使得error无法携带更多的上下文,我们不应该依赖error的输出,这个输出更多是给日志和排查使用的
2.自定义结构体的形式
这个方式可以携带更多的上下文了,并且可以通过断言的形式判断error的类型,例如:
// 实现Error方法
type MyError struct {
line int
des string
}
func (e *MyError) Error() string {
return fmt.Sprintf("line is %d, content is %s", e.line, e.des)
}
func test() error {
return &MyError{4, "sth is error"}
}
// main
err := test()
switch err := err.(type) {
case nil:
print("ok")
case *MyError:
print(err.line)
default:
print("unkonwn")
}
这个方法调用者断言的时候需要知道你自定义的error类型,产生了耦合,万一哪天你一改
3.不透明的处理
发现error直接返回error 不对他做任何假设
但是如果不是这种简单的二分 做或者不做,那怎么办
在这种情况下,我们可以断言错误实现了特定的行为,而不是断言错误是特定的类型或值
(其实不太理解,hhh)
3.处理error
无错误的正常流程代码,将成为一条直线,而不是缩进的代码
也就是一般判断 != nil
错误只需要处理一次,不处理的地方返回即可
- 自己代码中使用new 或者errorf
- 调用其他函数,直接rerurn err
- 如果和其他库进行协作,考虑使用 errors.Wrap 或者 errors.Wrapf 保存堆栈信息。同样适用于和标准库协作的时候
- errors.Cause 获取 root error,再进行和 sentinel error 判定