golang错误处理
golang错误处理方式一直是很多人诟病的地方,很多人都吐槽说一半的代码是 if err != nil { / 错误处理 / },严重影响正常的处理逻辑,我最开启也反感这种错误处理机制,每调用完一个函数都需要check一下,自定义函数也都要增加一个error类型的返回值,但是查了很多资料慢慢理解这种错误机制的好处。因为调用每一个函数都可能发生错误,及时在错误发生的地方做处理更容易构建复杂的大型系统。
异常与错误
错误与异常是很容易混淆的地方,错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中;而异常指的是不应该出现问题的地方出现了问题,这种情况在人们的意料之外。golang使用 panic,defer,recover 来处理异常情况,类似java中的try catch finially。 使用panic抛出异常,抛出异常后将立即停止当前函数的执行并运行所有被defer的函数,然后将panic抛向上一层,直至程序carsh。但是也可以使用被defer的recover函数来捕获异常阻止程序的崩溃,recover只有被defer后才是有意义的。 ``` //当一个http链接到来时,golang会调用serve函数,serve函数会解析http协议,然后交给上层的handler处理,如果上层的handler内抛出异常的话,会被defer里面的recover函数捕获,打印出当前的堆栈信息,这样就可以防止一个http请求发生异常导致整个程序崩溃了。
func (c *conn)serve() { defer func() { if err := recover(); err != nil { const size = 64<<10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] fmt.Printlf("http: panic serving %v: %v\\%s", c.remoteAddr, err, buf) } }() w := c.readRequest() ServeHTTP(w, w.req) }```
错误处理
我们也可以使用一些方法来简化错误处理的逻辑,总体思想是将重复的代码复用。
-
在defer中集中处理错误``` func demo() { var err error defer func() { if err != nil { // 处理发生的错误,打印日志等信息 return } }
err = func1() if err != nil { return }
err = func2() if err != nil { return } ..... } //如果golang支持宏,将能if err != nil { return }作为一个宏定义就完美了,可惜golang不支持宏。 ```
-
all-or-nothing check这个是Rob Pike在 Errors rea values 这篇文章中提到的方法,如果一个完整的任务内有多个小的任务,我们没有必要执行完每个小的任务都检查下是否有错,我们可以在所有的小任务执行完成后再去判断整个任务的执行过程中有没有错误。 type errWriter struct { w io.Writer err error } func (ew *errWriter) write(buf []byte) { if ew.err != nil { return } _, ew.err = ew.w.Write(buf) } ew := &errWriter{w: fd} ew.write(p0[a:b]) ew.write(p1[c:d]) ew.write(p2[e:f]) // 只需要在最后检查一次 if ew.err != nil { return ew.err } //这种处理方式有一个明显的问题是不知道整个任务是在哪一个小的任务执行的时候发生错误,如果我们需要关注每个小任务的进度则这种方式就不适合了。 ```
错误上下文
error是一个接口,任何实现了Error()函数的类型都满足该接口,errors.New()实际上返回的是一个errorString类型,该类型内仅包含一个string字符串,没有错误发生的上下文信息,当我们把error不断向上抛,在上层做统一处理时,只能输出一个字符串信息而不知道错误是在什么地方什么情况下发生的。 ``` type error interface { Error() string }
type errorString struct { s string }
func (e *errorString) Error() string { return e.s }
func New(text string) error { return &errorString{text} } ` package stackerr
import ( "fmt" "runtime" "strings" )
type StackErr struct { Filename string CallingMethod string Line int ErrorMessage string StackTrace string }
func New(err interface{}) StackErr { var errMessage string switch t := err.(type) { case StackErr: return t case string: errMessage = t case error: errMessage = t.Error() default: errMessage = fmt.Sprintf("%v", t) } stackErr := &StackErr{}
}
func (this *StackErr) Error() string { return this.ErrorMessage }
func (this *StackErr) Stack() string { return fmt.Sprintf("{%s:%d} %s\nStack Info:\n %s", this.Filename, this.Line, this.ErrorMessage, this.StackTrace) }
func (this *StackErr) Detail() string { return fmt.Sprintf("{%s:%d} %s", this.Filename, this.Line, this.ErrorMessage) }
```
参考文献 Error handling and Go 《Go web编程》--11.1 错误处理 Go语言错误处理 (Tony Bai的博客) Errors are values StackErr项目源码