1.error接口:
erorr是一种内建的接口类型.内建意味着不需要"import".任何包都可以直接使用,
使用起来就像int string一样自然.
源码位置:src/builtin/builtin.go
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
从源码可知.error接口只声明了一个Error()方法.任何实现了该方法的结构体都可以
作为error来使用.error的实例代表一种异常状态.Error()方法用于描述该异常状态.
值为nil的error代表没有异常.
标准库erroor包中的errorString就是实现error接口的一个例子.
源码位置:src/errors/errors.go
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
errorString是errors包的私有类型.对外不可见.只能通过相应的公开接口才可以创
建errorString实例.
2.创建error:
标准库创建方法:
1).errors.New():
源码位置:src/errors/errors.go
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
errors.New()实现比较简单.只是单纯的构建了一个errorString实例便返回了.
2).fmt.Errorf():
源码位置:src/fmt/errors.go
func Errorf(format string, a ...any) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
switch len(p.wrappedErrs) {
case 0:
err = errors.New(s)
case 1:
w := &wrapError{msg: s}
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default:
if p.reordered {
slices.Sort(p.wrappedErrs)
}
var errs []error
for i, argNum := range p.wrappedErrs {
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
err = &wrapErrors{s, errs}
}
p.free()
return err
}
fmt.Errorf会接受两个参数然后对string进行格式化.
3.性能对比:
fmt.Errorf()适用于格式化输出错误字符串的场景.如果不需要格式化字符串.则建议
直接使用errors.New().
示例如下:
package Concurrent
import (
"errors"
"fmt"
"testing"
)
// 场景1:无格式化参数的简单错误(errors.New 原生场景 vs fmt.Errorf 无参数)
func BenchmarkErrorsNew_Simple(b *testing.B) {
// 重置计时器,排除初始化耗时
b.ResetTimer()
// 循环执行 b.N 次,b.N 由基准测试框架自动调整
for i := 0; i < b.N; i++ {
_ = errors.New("simple error")
}
}
func BenchmarkFmtErrorf_Simple(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
// fmt.Errorf 无格式化参数,等价于 errors.New
_ = fmt.Errorf("simple error")
}
}
// 场景2:带格式化参数的错误(fmt.Errorf 核心场景,errors.New 无法直接实现)
func BenchmarkFmtErrorf_Format(b *testing.B) {
// 测试参数:字符串+数字,模拟真实业务格式化场景
msg := "user"
uid := 1001
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Errorf("user %s not found, uid: %d", msg, uid)
}
}
// 场景3:带错误包装的场景(%w 动词,fmt.Errorf 特有)
func BenchmarkFmtErrorf_Wrap(b *testing.B) {
baseErr := errors.New("base error")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Errorf("wrap error: %w", baseErr)
}
}
4.自定义error:
任何实现error接口的类型都可以称为error.比如标准库os中的PathError就是一个
典型的例子.
源码位置:src/io/fs.go
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
5.异常处理:
针对error而言.异常处理包括如何检查错误 如何传递错误.
1).检查error:
最常见的检查error的方式是与nil值进行比较:
func main() {
var err error = T(5)
if err != nil {
}
}
有时也会与一些预定义的error进行比较:
package oserror
import "errors"
var (
ErrInvalid = errors.New("invalid argument")
ErrPermission = errors.New("permission denied")
ErrExist = errors.New("file already exists")
ErrNotExist = errors.New("file does not exist")
ErrClosed = errors.New("file already closed")
)
实现了error接口的类型均可以作为error来处理.也可以使用类型断言来检查error.
func AssertError(err error) {
if e,ok := err.(*os.PathError); ok {
fmt.Printf("path error: %s", e.Path)
}
}
2).传递error:
在一个函数中收到一个error.往往需要附加一些上下文信息再把error继续往上抛.
最常见的添加附加上下文信息的方法是用fmt.Errorf().
func ReturnErrorTest() {
err := errors.New("test error")
if err != nil {
fmt.Errorf("我定义的异常进行了包装%s", err)
}
}
这种方式抛出的error有一个糟糕的问题.就是原error信息和附加的信息被糅合到了
一起.示例如下:
```
import (
"fmt"
"os"
)
func WriteFile(fileName string) error {
if fileName == "测试.txt" {
return fmt.Errorf("write file error:%v", os.ErrPermission)
}
return nil
}
func ExampleWriteFile() {
err := WriteFile("测试.txt")
if err == os.ErrPermission {
fmt.Printf("write file error:%v", os.ErrPermission)
}
}
```
在这个例子中.无法明确到底是不是这个异常.为了解决这个问题.可以自定义error类
型.就像os.PathError.上下文与信息分开放.
type PathError struct {
Op string //上下文
Path string //上下文
Err error //原error
}
6.wrapError:
Go1.13针对error的优化.最核心的内容就是引入了wrapError这一新的error类型.
其他特性都是围绕此类型展开的.
源码位置:src/fmt/errors.go:
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
wrapError中的msg保存上下文信息和err.Error(). err用来存储原error.与之前的
errorString相比.还额外实现了Unwrap接口.用于返回原始的error.
7.fmt.Errorf():
fmt.Errorf()新增了格式动词%w(wrap)用于生成wrapError实例.并且兼容原有动
词格式.源码如下:
func Errorf(format string, a ...any) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
switch len(p.wrappedErrs) {
case 0:
err = errors.New(s)
case 1:
w := &wrapError{msg: s}
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default:
if p.reordered {
slices.Sort(p.wrappedErrs)
}
var errs []error
for i, argNum := range p.wrappedErrs {
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
err = &wrapErrors{s, errs}
}
p.free()
return err
}
fmt.Errorf()将根据动词格式来动态决定生成wrapError还是errorString.
func main() {
err := errors.New("this is an error")
//使用%v
baseError := fmt.Errorf("this is a %v", err)
if _, ok := baseError.(interface{ Unwrap() error }); !ok {
fmt.Println("baseError is errorString")
}
}
使用%w格式动词生成的error类型自动变成wrapError(实现了Unwrap接口).
func main() {
err := errors.New("this is an error")
//使用%w
baseError := fmt.Errorf("this is a %w", err)
if _, ok := baseError.(interface{ Unwrap() error }); ok {
fmt.Println("baseError is wrapError")
}
}
当error在函数间传递时.error之间好像被组织成一个链式结构.
8.errors.Unwrap():
源码位置:src/errors/wrap.go
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
如果参数err没有实现Unwrap()函数.则说明是基础error.直接返回nil.否则调用原
error实现的Unwrap函数并返回. 可以通过循环调用errors.Unwrap()方法来逐层
检查.示例如下:
func ExampleUnwrapLoop() {
err1 := fmt.Errorf("write file error:%w", os.ErrPermission)
err2 := fmt.Errorf("write file error:%w", err1)
err := err2
for {
if err == os.ErrPermission {
fmt.Printf("Permission denied\n")
break
}
if err = errors.Unwrap(err); err == nil {
break
}
}
}
9.errors.Is():
errors.Is()用于检查特定的error链中是否包含指定的error值.
源码位置:src/errors/wrap.go
func Is(err, target error) bool {
if err == nil || target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
return is(err, target, isComparable)
}
func is(err, target error, targetComparable bool) bool {
for {
if targetComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if is(err, target, targetComparable) {
return true
}
}
return false
default:
return false
}
}
}
10.errors.As()方法:
在Go1.13中.errors.As()用于从一个error链中查找是否有指定类型出现.如有.则把
error转换成该类型.
源码位置:src/errors/wrap.go
func As(err error, target any) bool {
if err == nil {
return false
}
if target == nil {
panic("errors: target cannot be nil")
}
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem()
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
return as(err, target, val, targetType)
}
func as(err error, target any, targetVal reflectlite.Value, targetType reflectlite.Type) bool {
for {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
targetVal.Elem().Set(reflectlite.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if err == nil {
continue
}
if as(err, target, targetVal, targetType) {
return true
}
}
return false
default:
return false
}
}
}
山高路远.
如果大家喜欢我的分享的话.可以关注我的微信公众号
念何架构之路