特立独行的Go有不同的错误处理方式: 可能发生错误的地方返回一个错误。
还是已打开文件为例,使用标准库io
的函数Open
打开一个文件:
输出
这里的err是error
类型,error
就是一个接口,和其他语言不同,Go中错误就是一个值。
只要实现了这个Error() string
方法就是一个错误。
定义一个MyError
结构体:
新建一个New这个结构体的函数:
实现Error() string
方法
创建一个除法函数,在除数为0时它返回错误:
一个函数返回错误时,把错误放着最后面是一个好的习惯。
好的,来调用这个函数
完整代码:
输出
这就是error的用法,一个函数可能发生错误,(就像打开文件,但文件不存在),就返回error
。 error
是一个接口,它的零值是nil
, 如果err == nil
就说明错误为空,就是没有错误;如果err != nil
就说明错误不为空,就是有错误。设计得很巧妙对不对?
实际过程中,我们使用标准库errors
来新建一个错误:
也可以使用fmt
包来包裹错误
标准库errors
包含了很多有用的处理错误的函数,我们已经接触了New
函数用来新建一个错误
设想一下这样一个错误的链路:
fooA
函数返回一个错误
fooB
函数调用fooA
返回一个错误
fooC
函数调用fooB
返回一个错误
我们在main
函数中调用fooC
:
运行,输出
我们得到了底层的错误fooA
出错,可是并不知道错误的链路。这时可以使用fmt.Errorf
将错误包裹起来
运行,输出
好的,错误的链路清晰了。
有时,我们需要判断一个错误是不是特定的错误:
新建一个特定错误, fooA
返回这个错误
如果还采用上述的包裹方式,就无法判断原来的错误是不是特定错误,因为fmt.Errorf()
返回的是一个新的错误,只是把原来的错误信息格式化成字符串加进去了。
例如:
输出
输出为false
, 因为这两个是不同的错误。
在fmt.Errorf
函数中使用%w
动词进行包裹,这样原来的错误会保留在包裹的错误结构体中。
例如:
使用errors.Is(err, target)
判断err
中是否包裹有特定错误target
完整代码:
运行,输出
结果是true
。
errors.Is(err, target), 接收一个要判断的err, 和一个目标的错误,返回目标的错误是不是包裹在err中,如果是则返回true
。
这样我们就能打印错误的链路和判断是否包含特定的错误了。这些知识已经基本足够,更多的错误处理在另一本书《Go标准库详解》