命令记不住

登陆

基础 error

基础error

  • error的类型
  • 如何创建error
  • 如何自定义error
  • 如何检查error
  • 如何处理error
  • 使用error的痛点

1.error接口

error是一种内建的接口类型,内建意味着不需要“import”任何包就可以直接使用,使用起来就想int、string一样自然

type  error interface {
    Error()  string
}

error接口只声明了一个Error()方法,任何实现了该方法的结构体都可以作为error来使用。error的实例代表一种异常状态,Error()方法用于描述该异常状态,值为nill的error代表没有异常

标准库error包中的errorString就是实现error接口的一个例子

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

errorString是errors包的私有类型,对外不可见,只能通过相应的公开接口才可以创建errorString实例

2.创建error

标准库提供了两种创建error的方法:

  • errors.New()
  • fmt.Errorf()

(1)error.New()

errors.New()的实现及其简单,只是简单的构造一个errorString实例便返回

package errors
func New(text string) error {
    return &errorString(text)
}

(2)fmt.Errorf()

errors.New()单调的接收一个字符串参数来构造error,而实际场景中往往需要使用fmt.Sprintf()生成字符串,这时可以直接使用fmt.Errorf()

package fmt
func Errorf(format string, a ...interface()) error {
    returnerrors.New(Sprintf(format, a...))
}

可以看到,fmt.Errorf()只是对errors.New()的简单封装,使用fmt.Errorf()可以使代码更简洁,比如:

errStr := fmt.Sprintf("file not found,file name: %s", fileName)
err := errors.New(errStr)
或
err := errors.New(fmt.Sprintf("file not found, file name: %s", filename))

可以替换成:

err := fmt.Errorf("file not found, file name: %s", fileName)

(3)性能对比

fmt.Errorf()适用于需要格式化输出错误字符串的场景,如果不需要格式化字符串,则建议直接使用errors.New

考虑下面两种创建error的方法:

func MakeByErrorsNew() error {
    return errorrs.New("new error")
}
func MakeByFmtErrorf() error {
    return fmt.Errorf("new error")
}

由于fmt.Errorf()在生成格式化字符串时需要遍历所有字符,所以性能上会有损失

3.自定义error

任何实现error接口的类型都可以称为error,比如标准库os中的PathError就是一个典型的例子

type PathError struct {
    Op string
    Path  string
    Err error
}
func (e *PathError)Error() string{
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

4.异常处理

针对error而言,异常处理包括如何检查错误、如何传递错误

(1)检查error

最常见的检查error的方式是与nil值进行比较

if err != nil{
    //xxxxxxxxx
}

有时也会与一些预定义的error进行比较

//标准库os包中定义了一些常见的错误
//ErrPermission = errors.New("permission deied")
if err == os.ErrPermission {
    //permission denied
}

由于任何实现了error接口的类型均可以作为error来处理,所以往往也会使用类型断言来检查error:

func AssertError(err error){
    if e,ok :=err.(*os.PathError);ok{
        fmt.Printf("it's an os.PathError, operation: %s, path: %s, msg: %v",e.Op,e.Path,e.Err)
    }
}

上面代码中的断言,如果err是os.PathError类型,则可以使用e来访问os.PathError中的成员,在下面的Example测试中,err1是os.PathError类型,断言为真,而err2的断言为假

(2)传递error

在一个函数中收到一个error,往往需要附加一些上下文信息再把error继续向上层抛。

最常见的添加附加上下文信息的方法是使用fmt.Errorf():

if err != nil{
    return fmt.Errorf("decompress %v: %v",name, err)
}

这种方式抛出error有一个糟糕的问题,那就是原error信息和附加的信息被柔合到了一起,比如下面的函数,就会把os.ErrPermission和附加信息柔合到一起

func WriteFile(fileName string ) error {
    if fileName == "a.txt"{
        return fmt.Errorf("write file error; %v",os.ErrPermission)
    }
    return nil
}

这样在下面的Example测试中,就无法判断err是否是os.ErrPermission值:

func ExampleWriteFile(){
    err := WriteFile("a.txt")
    if err == os.ErrPermission{
        fmt.Printf("permission denied")
    }
}

为了解决这个问题,我们可以自定义error类型,就像os.PathError那样,上下文信息与原error信息分开存放

type PathError struct {
    Op  string
    Path string
    Err error
}

这样,对于一个os.PathError类型的error,我们可以检查它到底是不是一个权限不足的错误:

    if e,ok :=err.(*os.PathError);ok && e.Err == os.ErrPermission{
        fmt.Printf("permission denied")
    }

5.小结

使用fmt.Errorf()传递一个error最大的问题是丢弃了原error值,而自定义error又会有不得不使用断言的烦恼,可以使用一个错误链传递和检查机制解决

推荐阅读