[tldr] GO语言异常处理

在开发中, 处理异常是很重要的, 考虑各种错误情况并且提出对应的解决办法是保证不出大BUG的重要之处.

error in go

GO语言的异常是很简单的, 只需要实现Error函数接口即可

go 复制代码
func (e ErrA) Error() string {
	return "ErrA"
}

过于简单的实现, 和C一样

errors

这是GO的一个库, 专门用来处理异常

Go语言的标准库一般会在后面加一个s代表是处理这种类型的标准库, 例如strings

异常处理

error类别判断

类型断言实现类别判断

Go语言提供了类型断言的方式, 针对接口, 可以实现不同类型之间的相互转换

这就是为什么errA需要是error类型, 因为是接口类型才能转换

go 复制代码
	var errA error = ErrA{}
	var errB error = ErrB{}

	fmt.Println(errA.Error())
	fmt.Println(errB.Error())

	if e, ok := errA.(ErrB); ok {
		fmt.Println(e)
		fmt.Println("found ErrB")
	} else {
		fmt.Println("not found ErrB")
	}
  • 这个简单好用, 如果需要嵌套的异常处理, 那么应该选择使用这个.
  • 当然可以使用switch语句针对多种错误类型进行处理, 针对多个类型的时候使用这个方式可以更好的处理
go 复制代码
func main() {
	var errA error = ErrA{}
	var errB error = ErrB{wrappedErr: errA}

	for err := errB; err != nil; err = errors.Unwrap(err) {
		fmt.Println(err)
		switch err.(type) {
		case ErrA:
			fmt.Println("ErrA")
		case ErrB:
			fmt.Println("ErrB")
		default:
			fmt.Println("default")
		}
		fmt.Println()
	}
}

上述代码是使用switch配合类型断言来处理异常

errors函数类别判断

通过errors.Is或者errors.As函数实现类别的判断

Go语言官方提供的异常处理, 也只是简单的封装.

  • 这个函数的本质是检查这个error是否包含某个error(一个error可以包含多个error, 嵌套error)
go 复制代码
	var errA error = ErrA{}
	var errB error = ErrB{}

	fmt.Println(errA.Error())
	fmt.Println(errB.Error())

	if errors.Is(errA, errB) {
		fmt.Println("errA is errB")
	} else {
		fmt.Println("errA is not errB")
	}

使用这个函数让代码看起来十分的简洁

  • 使用errors提供的函数在处理嵌套的error的时候可能存在问题, 我们在下面讨论

嵌套error

有些时候我们需要把error一层一层wrap起来然后向上抛出异常进行处理. 像是多层的Throw错误.

errors库提供了Unwrap函数, 如果error实现了这个接口, 那么就可以实现error嵌套

go 复制代码
package main

import (
	"errors"
	"fmt"
)

type ErrA struct {
	wrappedErr error
}

func (e ErrA) Error() string {
	return fmt.Sprintf("ErrA -> %v", e.wrappedErr)
}

func (e ErrA) Unwrap() error {
	return e.wrappedErr
}

type ErrB struct {
	wrappedErr error
}

func (e ErrB) Error() string {
	return fmt.Sprintf("ErrB -> %v", e.wrappedErr)
}

func (e ErrB) Unwrap() error {
	return e.wrappedErr
}

func main() {
	errA := ErrA{}
	errB := ErrB{}

	fmt.Println(errA.Error())
	fmt.Println(errB.Error())

	if errors.Is(errA, ErrB{}) {
		fmt.Println("errA is ErrB")
	} else {
		fmt.Println("errA is not ErrB")
	}

	fmt.Println()

	var wrapErr error = ErrA{wrappedErr: ErrB{}}
	for err := wrapErr; err != nil; err = errors.Unwrap(err) {
		fmt.Println(err)
	}
}

可以使用for循环语句来解开error

但是这个for循环拿出来的error使用errors.Is函数是无法准确识别当前的error属于哪个类别的, 因为所有被wrap的error类别都可以被识别到.

当然也可以通过errors库提供的Join函数实现合并多个error的功能, var wrappedErr error = errors.Join(errA, errB)语句提供了合并多个error的方式.

合并之后的error实现的Unwrap接口是返回[]error的, 但是在errors库使用的时候, 会被视为同一个error

可以通过errors.As或者errors.Is判断当前的错误是否包含某一个特定的错误

errors处理嵌套error

当然可以使用errors库来处理嵌套的error, 因为只要符合error接口规范的都可以使用errors进行处理

但是存在一些不方便的地方, 比如, 一个嵌套ErrA的ErrB错误对象, 同时可以被认为是ErrA和ErrB类型. 下面是一个例子

go 复制代码
package main

import (
	"errors"
	"fmt"
)

type ErrA struct {
	wrappedErr error
}

func (e ErrA) Error() string {
	return fmt.Sprintf("ErrA -> %v", e.wrappedErr)
}

func (e ErrA) Unwrap() error {
	return e.wrappedErr
}

type ErrB struct {
	wrappedErr error
}

func (e ErrB) Error() string {
	return fmt.Sprintf("ErrB -> %v", e.wrappedErr)
}

func (e ErrB) Unwrap() error {
	return e.wrappedErr
}

func main() {
	var errA error = ErrA{}
	var errB error = ErrB{wrappedErr: errA}

	for err := errB; err != nil; err = errors.Unwrap(err) {
		fmt.Println(err)
		if errors.Is(err, errA) {
			fmt.Println("errA is the cause of the error")
		}
		fmt.Println()
	}
}

结果是

无法识别当前这一层的error是ErrB

造成这个问题的原因是, errors.Is的源码实现如下:

go 复制代码
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
		}
	}
}

如果遇上可以Unwrap的接口类型直接再次Unwrap去找是否存在一个类型是符合条件的

对此, 需要使用类型断言的方式来判断错误类型

go 复制代码
func main() {
	var errA error = ErrA{}
	var errB error = ErrB{wrappedErr: errA}

	for err := errB; err != nil; err = errors.Unwrap(err) {
		fmt.Println(err)
		switch err.(type) {
		case ErrA:
			fmt.Println("ErrA")
		case ErrB:
			fmt.Println("ErrB")
		default:
			fmt.Println("default")
		}
		fmt.Println()
	}
}

效果如下:

成功识别出了错误的类型

  • 如果需要准确识别错误的具体类型, 而不是检查这个报错包含哪些错误的话, 应该使用这个

参考

GO语言的errors库
Go语言(golang)新发布的1.13中的Error Wrapping深度分析

相关推荐
码界奇点18 小时前
基于Wails框架的Ollama模型桌面管理系统设计与实现
go·毕业设计·llama·源代码管理
曲幽1 天前
FastAPI日志实战:从踩坑到优雅配置,让你的应用会“说话”
python·logging·fastapi·web·error·log·info
SunkingYang2 天前
QT编译报错:“error: macro name missing“原因分析与解决方案详解
qt·error·macro·编译报错·name·missing
csdn_aspnet2 天前
Go语言常用算法深度解析:并发与性能的优雅实践
后端·golang·go
吴老弟i3 天前
Go 多版本管理实战指南
golang·go
曲幽3 天前
FastAPI异常处理全解析:别让你的API在用户面前“裸奔”
python·websocket·api·fastapi·web·exception·error·httexception
Grassto3 天前
HTTP请求超时?大数据量下的网关超时问题处理方案,流式处理,附go语言实现
后端·http·golang·go
提笔了无痕4 天前
Web中Token验证如何实现(go语言)
前端·go·json·restful
Grassto5 天前
10 Go 是如何下载第三方包的?GOPROXY 与源码解析
后端·golang·go·go module
无心水5 天前
17、Go协程通关秘籍:主协程等待+多协程顺序执行实战解析
开发语言·前端·后端·算法·golang·go·2025博客之星评选投票