一分钟学习golang第十三天

135 阅读3分钟

错误处理

Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。

在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如os.Open函数在打开文件失败时将返回一个不为nilerror变量

func Open(name string) (file *File, err error)

下面这个例子通过调用os.Open打开一个文件,如果出现错误,那么就会调用log.Fatal来输出错误信息:

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}

类似于os.Open函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error

Error类型

error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

error是一个内置的接口类型,可以在/builtin/包下面找到相应的定义。而在很多内部包里面用到的 errorerrors包下面的实现的私有结构errorString

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

可以通过errors.New把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下:

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

下面这个例子演示了如何使用errors.New:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // implementation
}

在下面的例子中,在调用Sqrt的时候传递的一个负数,然后就得到了non-nilerror对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:

f, err := Sqrt(-1)
if err != nil {
    fmt.Println(err)
}

自定义Error

error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,就可以实现自己的错误定义,请看来自Json包的示例:

type SyntaxError struct {
    msg    string // 错误描述
    Offset int64  // 错误发生的位置
}

func (e *SyntaxError) Error() string { return e.msg }

Offset字段在调用Error的时候不会被打印,但可以通过类型断言获取错误类型,然后可以打印相应的错误信息,请看下面的例子:

if err := dec.Decode(&val); err != nil {
    if serr, ok := err.(*json.SyntaxError); ok {
        line, col := findLine(f, serr.Offset)
        return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
    }
    return err
}

需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如:

func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
    var err *SyntaxError     // 预声明错误变量
    if 出错条件 {
        err = &SyntaxError{}
    }
    return err               // 错误,err永远等于非nil,导致上层调用者err!=nil的判断始终为true
}

原因见 http://golang.org/doc/faq#nil_error (需科学上网)

上面例子简单的演示了如何自定义Error类型。但是如果还需要更复杂的错误处理呢?此时,来参考一下net包采用的方法:

package net

type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试:

if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
    time.Sleep(1e9)
    continue
}
if err != nil {
    log.Fatal(err)
}