error與panic
error:可預(yù)見(jiàn)的錯(cuò)誤
panic:不可預(yù)見(jiàn)的異常
panic處理
通過(guò)panic,defer,recover來(lái)處理異常
如下示例代碼,當(dāng)一個(gè)http鏈接到來(lái)時(shí),golang會(huì)調(diào)用serve函數(shù),serve函數(shù)會(huì)解析http協(xié)議,然后交給上層的handler處理,如果上層的handler內(nèi)拋出異常的話,會(huì)被defer里面的recover函數(shù)捕獲,打印出當(dāng)前的堆棧信息,這樣就可以防止一個(gè)http請(qǐng)求發(fā)生異常導(dǎo)致整個(gè)程序崩潰了。
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\n%s", c.remoteAddr, err, buf)
}
}()
w := c.readRequest()
ServeHTTP(w, w.req)
}
error處理
在defer中集中處理錯(cuò)誤
func demo() {
var err error
defer func() {
if err != nil {
// 處理發(fā)生的錯(cuò)誤,打印日志等信息
return
}
}
err = func1()
if err != nil {
return
}
err = func2()
if err != nil {
return
}
.....
}
all-or-nothing check
如果一個(gè)完整的任務(wù)內(nèi)有多個(gè)小的任務(wù),我們沒(méi)有必要執(zhí)行完每個(gè)小的任務(wù)都檢查下是否有錯(cuò),我們可以在所有的小任務(wù)執(zhí)行完成后再去判斷整個(gè)任務(wù)的執(zhí)行過(guò)程中有沒(méi)有錯(cuò)誤。
//冗長(zhǎng)的代碼,滿篇重復(fù)的 if err != nil
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
// and so on
我們可以這樣簡(jiǎn)化代碼
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
}
這種處理方式有一個(gè)明顯的問(wèn)題是不知道整個(gè)任務(wù)是在哪一個(gè)小的任務(wù)執(zhí)行的時(shí)候發(fā)生錯(cuò)誤,如果我們需要關(guān)注每個(gè)小任務(wù)的進(jìn)度則這種方式就不適合了。
錯(cuò)誤上下文
error是一個(gè)接口,任何實(shí)現(xiàn)了Error()函數(shù)的類型都滿足該接口,errors.New()實(shí)際上返回的是一個(gè)errorString類型,該類型內(nèi)僅包含一個(gè)string字符串,沒(méi)有錯(cuò)誤發(fā)生的上下文信息,當(dāng)我們把error不斷向上拋,在上層做統(tǒng)一處理時(shí),只能輸出一個(gè)字符串信息而不知道錯(cuò)誤是在什么地方什么情況下發(fā)生的。
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}
}
既然error是一個(gè)接口,那我們也可以自己實(shí)現(xiàn)一個(gè)滿足該接口的類型,并記錄下發(fā)生錯(cuò)誤的文件,函數(shù),堆棧等信息,更方便錯(cuò)誤的查找。
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{}
stackErr.ErrorMessage = errMessage
_, file, line, ok := runtime.Caller(1)
if ok {
stackErr.Line = line
components := strings.Split(file, "/")
stackErr.Filename = components[(len(components) - 1)]
}
const size = 1 << 12
buf := make([]byte, size)
n := runtime.Stack(buf, false)
stackErr.StackTrace = string(buf[:n])
return 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)
}
第三方錯(cuò)誤庫(kù)推薦
github.com/pkg/errors是一款提供了日志上下文封裝的error庫(kù),可以非常方便的給error加上額外的信息、記錄stack信息等等。詳細(xì)使用說(shuō)明可以參考github或者源碼。