
基础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又会有不得不使用断言的烦恼,可以使用一个错误链传递和检查机制解决