news 2026/4/16 0:12:40

Go进阶之异常处理error

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之异常处理error

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 } } }

山高路远.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 21:14:37

基于tood_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco模型的家禽种类识别系统_1

1. 基于TOOD_x101-64x4d-dconv-c4-c5_fpn_ms-2x_coco模型的家禽种类识别系统 1.1. 引言 随着现代农业的快速发展&#xff0c;家禽养殖业的规模不断扩大&#xff0c;对家禽种类识别的需求也日益增长。传统的家禽识别方法主要依靠人工经验&#xff0c;存在效率低、准确性差等问…

作者头像 李华
网站建设 2026/3/28 18:10:19

从“自发自用”到“智慧调度”:农村光伏如何高效融入微电网?

分布式光伏是点亮农村绿色发展的“第一缕光”。它利用农村丰富的屋顶、庭院、农业设施顶棚等空间&#xff0c;将阳光就地转化为电能&#xff0c;直接降低了用电成本&#xff0c;减少了碳排放。随着技术成本下降&#xff0c;农村分布式光伏安装量快速增长&#xff0c;但简单地“…

作者头像 李华
网站建设 2026/4/10 20:53:31

先正达集团在中国加速布局全球级研发中心和制造工厂 | 美通社头条

、美通社消息&#xff1a;全球领先的农业科技企业先正达集团将全球领先的植保研发中心落地上海&#xff0c;并在江苏南通同步建设高标准制剂与工程化平台。一个旨在贯通研发到应用的植保领域"中国地标"正日益清晰。今年1月&#xff0c;先正达集团全球植保中国创新中心…

作者头像 李华
网站建设 2026/3/26 18:43:55

新手做自媒体,如何在30天内建立正反馈避免放弃

当你第一次踏入自媒体的世界&#xff0c;满心憧憬地按下“发布”按钮&#xff0c;却只等来寥寥几个阅读量时&#xff0c;那种失落感足以浇灭大部分人的热情。这几乎是每位新手创作者的必经之路——在最初的30天黄金期内&#xff0c;如何建立起持续的正反馈循环&#xff0c;避免…

作者头像 李华
网站建设 2026/4/8 9:49:32

开源的自动驾驶框架

目前主流的开源自动驾驶框架&#xff0c;这类框架覆盖了从入门学习、算法研发到工程落地、实车部署的全场景&#xff0c;核心分为全栈式框架&#xff08;覆盖感知/预测/决策/规划/控制全流程&#xff0c;可直接对接实车&#xff09;和模块化框架&#xff08;聚焦单一环节&#…

作者头像 李华
网站建设 2026/4/8 22:38:32

STM32F407通过UART读取JY-901加速度数据方案

一、硬件连接与配置 1. 引脚连接 JY-901与STM32F407的UART接口连接如下&#xff08;以USART2为例&#xff09;&#xff1a;JY-901引脚STM32引脚功能TXPA3JY-901发送端RXPA2JY-901接收端VCC3.3V电源供电GNDGND共地2. 波特率设置 JY-901默认波特率为9600bps&#xff0c;需在STM32…

作者头像 李华