news 2026/4/18 5:41:07

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/17 15:58:41

还要多久?NASA卫星从太空俯瞰,那条通往“正义”的道路

当NASA的卫星从数百公里的高空俯瞰地球&#xff0c;它们通常在记录冰川的消融或城市的扩张。但这一次&#xff0c;Landsat 8 卫星的镜头聚焦在了阿拉巴马州的一条街道上。这里&#xff0c;曾见证了一场改变人类文明进程的漫长行军。 来自太空的“历史快照”2025年9月&#xff0…

作者头像 李华
网站建设 2026/4/5 21:14:01

设计模式 -详解

1.单例模式 单例模式是指在整个应用中一个类的对象只允许出现一个(类的对象最多 只允许创建一次)&#xff1b; 我们在创建一个类的对象时&#xff0c;调用的是类的构造器&#xff0c;所以在单例中类的构 造器只允许调用一次 核心&#xff1a;构造方法私有化&#xff0c;不允许…

作者头像 李华
网站建设 2026/4/8 17:50:04

您的APP还在“隐身”吗?2026年ASO优化高级实战指南

应用商店优化 (ASO)是一个持续的过程&#xff0c;旨在通过优化元数据&#xff08;标题、关键词&#xff09;、创意素材&#xff08;应用截图、视频&#xff09;和性能指标&#xff08;应用评分、应用评论&#xff09;来提升应用在Apple和Google Play等应用商店中的曝光度和转化…

作者头像 李华
网站建设 2026/4/18 1:25:39

【山海鲸实战案例】通过二维组件控制三维场景昼夜变化

在项目制作过程中&#xff0c;我们可能会需要手动控制三维场景的昼夜切换&#xff0c;此时通过按钮组件的交互设置就可以非常简单地达到目的&#xff0c;下面我们就来看一下具体该如何进行设置。 首先&#xff0c;创建一个三维场景。 添加两个“按钮”组件&#xff0c;分别命名…

作者头像 李华
网站建设 2026/4/16 17:45:17

原子层加工技术推动碳化硅量子光子电路发展

原子层加工技术助力碳化硅量子光子电路蓬勃发展 来自马克斯普朗克光科学研究所&#xff08;Max Planck Institute for the Science of Light&#xff09;与弗劳恩霍夫集成系统与元器件技术研究所&#xff08;Fraunhofer Institute for Integrated Systems and Device Technolo…

作者头像 李华
网站建设 2026/4/16 10:40:45

深圳跨境电商中的“亚马逊精品模式“详解

深圳跨境电商中的"亚马逊精品模式"详解 一、核心定义 亚马逊精品模式是跨境电商中一种"少而精"的运营策略&#xff0c;指卖家专注于少数高潜力产品&#xff08;通常成熟期仅需10-20款&#xff09;&#xff0c;通过深度选品、精细化运营和供应链优化&#x…

作者头像 李华