Go Error
一. 设计理念
- 简单
- 考虑成功而不是只有成功
- 没有隐藏的控制流
- 完全交给调用者控制 error
- Error are values(Rob Pike)
二. 错误与异常
go 中的错误处理主要使用到error
和 panic
,其中 error
使用居多
error
error
主要有以下几个特点
- 不会造成程序运行结束
error
仅仅是一个返回值- 将问题抛给调用者处理,可以不处理,但建议处理
- 明确告诉调用者需要处理错误,一定程度上确保调用者会处理(强烈建议)
panic
panic
主要有以下几个特点
- 调用者未处理时可能会造成整个程序运行结束
- 隐式的,调用者不知道被调用方法是否会触发
panic
,所以无法确保调用者一定会处理 - 通常用作较大错误,程序无法执行(fatal error)时抛出(慎用)
三. 几种错误实践方案
1. Sentinel Error(预定义错误)
(1. 标准库中使用到预定义错误的例子
尽可能提前定义好所有需要的错误类型及错误代码,方便业务中使用及判断
var (
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
ErrBufferFull = errors.New("bufio: buffer full")
ErrNegativeCount = errors.New("bufio: negative count")
)
(2. 使用 error 对象进行 == 判断,而非 error 的内容
error 的内容是为了方便调试或者日志记录,而非方便程序控制
仅使用错误内容判断错误,而进行错误逻辑处理可能造成隐患,因为可能存在相同错误内容,但不同错误对象的情况。errors.New()
时也会返回当前对象的指针,以确保每个新建一个对象都是唯一的。
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbfuhipu-1655983351596)(/Users/zsl/Library/Application Support/typora-user-images/image-20220623190257837.png)]
(3. 预定义 error,需要放置在公共包中,且需要有文档描述
Sentinel Error 通常被用作底层工具开发使用,第三方包如果大量使用可能会导致循环导入问题(import loop)
需要放置在公共包中,且需要有文档描述。(注意:阅读源码发现方法的返回值仍然是 error
)
var ErrShortWrite = errors.New("short write")
var errInvalidWrite = errors.New("invalid write result")
var ErrShortBuffer = errors.New("short buffer")
......
2. Error Types(错误类型)
(1. 使用自己封装的 type 作为 error 使用
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
(2. 判断错误时可使用断言的方式做类型转换
func underlyingError(err error) error {
switch err := err.(type) {
case *PathError:
return err.Err
case *LinkError:
return err.Err
case *SyscallError:
return err.Err
}
return err
}
3. Opaque error(模糊错误)
(1. 仅仅判断 error 是否为 nil,而不关心具体是什么 error
(2. assert errors for behaviour, no type(可暴露行为,而不暴露类型)
4. Warp error(包装错误)
将错误向上抛,在某个地方统一处理
- 仅处理错误一次
- 需要使用预定义错误、类型断言、行为判定时不能使用
fmt.Errorf()
做转换 - 通过日志记录错误是需要记录详细信息(即仅凭日志就可以粗略看出具体问题,位置信息、参数信息、错误信息等)
使用github.com/pkg/errors
做错误包装,
- 程序中出现错误使用
errors.New()
或者 errors.Errorf()
返回错误 - 调用其他包,返回错误时可直接返回
- 调用其他库报错时,使用
error.Warp()
或者error.Warpf()
保存堆栈信息(适用于标准库) - 程序顶部使用
%+v
方式打印错误堆栈信息 errors.Cause
打印原始错误,可以使用预定义错误、类型断言、行为判定等配合使用- 业务库中可以使用,封装库避免使用 Wrap error ,否则可能出现两层堆栈信息
四. 错误处理写法优化策略
1. 错误流缩进
indented flow is for errors
无错误的代码正常执行,而不是缩进的代码(推荐判断 err != nil
不推荐err == nil
)
func test() {
a, err := funca()
if err != nil {
}
if err == nil {
}
}
2. 避免无效判断
eliminate error handling by eliminate errors
避免无效的错误判断
func test() error {
err := funca()
if err != nil {
return err;
}
return nil;
}
func test() error {
return funca()
}
3. 收集错误
可以通过错误记录或者收集的方式减少错误判断
func (s *Scanner) advance(n int) bool {
if n < 0 {
s.setErr(ErrNegativeAdvance)
return false
}
if n > s.end-s.start {
s.setErr(ErrAdvanceTooFar)
return false
}
s.start += n
return true
}
func (s *Scanner) setErr(err error) {
if s.err == nil || s.err == io.EOF {
s.err = err
}
}
未使用错误收集方式
type Header struct {
Key, Value string
}
type Status struct {
Code int
Reason string
}
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
_, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
if err != nil {
return err
}
for _, h := range headers {
_, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
if err != nil {
return err
}
}
if _, err := fmt.Fprint(w, "\r\n"); err != nil {
return err
}
_, err = io.Copy(w, body)
return err
}
使用错误收集方式
type errWriter struct {
io.Writer
err error
}
func (e *errWriter) Write(buf []byte) (int, error) {
if e.err != nil {
return 0, e.err
}
var n int
n, e.err = e.Writer.Write(buf)
return n, nil
}
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
ew := &errWriter{Writer: w}
fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
for _, h := range headers {
fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
}
fmt.Fprint(ew, "\r\n")
io.Copy(ew, body)
return ew.err
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)