Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >我不允许你只会 if err == nil ,请收下这份优雅处理错误的指南

我不允许你只会 if err == nil ,请收下这份优雅处理错误的指南

作者头像
小锟哥哥
发布于 2022-12-05 06:05:55
发布于 2022-12-05 06:05:55
1.1K00
代码可运行
举报
文章被收录于专栏:GoLang全栈GoLang全栈
运行总次数:0
代码可运行

Go 的错误异常处理,一直都是一个非常好玩的话题。

如果你习惯了 try catch 这样的语法后,会觉得处理错误真简单,然后你再来接触 Go 的错误异常,你会发现他好复杂啊,怎么到处都是 error,到处都需要处理 error。

所以如果你去一些论坛,或许喷得最多的就是这个点了。

一、Go 的约定

首先咱们需要知道 Go 语言里面有个约定,就是一个方法的返回参数,我们通常习惯的把错误当最后一个参数返回。

这虽然官方在这点上没有做硬性规定,但是大家也都习惯这么做,所以大家在写代码时就尽量不要去违反了哈,咱就是放第一个,咱就是玩,估计会被骂死的。

至于为啥 Go 要这样去设计处理异常,咱们这种干饭人事就不去分析了,官方怎么设计咱们就怎么遵守就好了。

二、简单错误创建

Go 的标准库里面为我们提供了两种使用字符串快速创建错误的方式。

1、 errors

我们可以使用 errors 包的 New 方法,传入一个字符串快速地创建。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var e error
e = errors.New("我是错误")

2、fmt

可能大多数同学都习惯用 fmt 去输出一些内容,同样他还能为我们创建错误。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var e error
e = fmt.Errorf("%s", "我还是错误")

相比 errors 包,fmt 还支持格式化字符串输出。

所以从这个角度可以看到,其实错误对 Go 语言来说,其实就是一段字符串。

三、哨兵错误

接下来我们分享 Go 中最常用的设计 error 的方式,那就是哨兵模式。

怎么去理解呢?

就像童话故事里一座城堡,在城堡的一些关卡,总会安排各种各样的哨兵,他们不同哨兵负责的事不同。

所以我们通常会在一个包里面设置一些标志性的错误,方便调用者对错误做更好的处理。

拿我们常用的 GORM 这个库吧,我们在查询某条数据的时候,如果没找到这条数据,不知道你是怎么判断的。

其实官方为我们提供了错误哨兵,在源码 github.com/jinzhu/gorm/errors.go中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var (
 // ErrRecordNotFound returns a "record not found error". Occurs only when attempting to query the database with a struct; querying with a slice won't return this error
 ErrRecordNotFound = errors.New("record not found")
 // ErrInvalidSQL occurs when you attempt a query with invalid SQL
 ErrInvalidSQL = errors.New("invalid SQL")
 // ErrInvalidTransaction occurs when you are trying to `Commit` or `Rollback`
 ErrInvalidTransaction = errors.New("no valid transaction")
 // ErrCantStartTransaction can't start transaction when you are trying to start one with `Begin`
 ErrCantStartTransaction = errors.New("can't start transaction")
 // ErrUnaddressable unaddressable value
 ErrUnaddressable = errors.New("using unaddressable value")
)

所以我们就可以直接通过返回的 error 来判断是不是没找到数据,下面我写一份假代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
g,_ := gorm.Open()
e = g.Find().Error
if e == gorm.ErrRecordNotFound {
 fmt.Println("没找到")
}

其实这样用 == 比较是有坑的,后面我们会讲到。

所以如果我们在写我们的模块的时候,也可以这样去设计我们的错误。

虽然这种设计模式网上也有很多人说不好,因为他建立起了两个包之间的依赖,说人话就是,如果我们要比较错误,就必须导入错误所在的包。

反正任何设计都会有人说好有人说坏,大家理智看到就好了。

四、对错误进行编程

我们需要时刻记住,Go 语言中错误其实就是一串字符串。

所以我们尽量避免去比较 error.Error() 输出的值,因为他正常情况下不是给我们人看的,而是给程序看的,同时方便我们调试。

所以,Go 里面的错误其实我们可以进行一系列的编程。

Go 语言中的错误定义是一个借口,只要是声明了 Error() string 这个方法,就意味着他就可以判定他是一个错误。

这是 Go 中的错误定义源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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
}

如果官方的错误,并不能满足你的需求,咱们也可以自定义。

1、创建错误

我们先来使用常量去创建自定义错误吧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type MyError string

func (this MyError) Error() string {
 return string(this)
}

这样我们就创建好我们的自定义错误了,使用下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
 var e error
 e = MyError("hello")
 fmt.Println(e)
}

当然我们可以把 string 换成 struct ,同时加入很多我们自定义的属性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type MyError struct {
 Code int
 Msg string
}

func NewMyError(code int, msg string) *MyError {
 return &MyError{Code: code, Msg: msg}
}

func (this MyError) Error() string {
 return fmt.Sprintf("%d-%s",this.Code, this.Msg)
}

// FindUser 模拟下我们的业务方法
func FindUser() error {
 return NewMyError(404, "找不到内容")
}

func main() {
 var e error
 e = FindUser()
 fmt.Println(e)
}

2、错误的API

最后我们来说说 Go 语言中错误的 API,到目前为止,我们面对错误除了输出外,就是使用 == 对错误进行哨兵比较,但是这样未必准确。

所以官方在错误的基础上,又扩展了几个 API。

1、Is

我们面对错误,尽量不要使用这样的方式去比较:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 尽量少用
if e.Error() == "404-找不到内容" {
}

尽量少用,最好不用。

也少用这样的方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var ErrorNotFind = NewMyError(404, "找不到内容")

// FindUser 模拟下我们的业务方法
func FindUser() error {
 return ErrorNotFind
}

func main() {
 var e error
 e = FindUser()
 log.Println(e)

 // 尽量少用
 if e == ErrorNotFind {

 }
}

目前我们的错误结构体还是非常简单的,如果我们的结构体里面的属性再多几个,很可能就会出现牛头对马嘴情况。

所以官方为我们提供了 Is 方法的 API,他默认使用 == 将特定的错误与错误链中的错误进行比较,如果不一样,就会去调用错误实现的 Is 方法进行比较。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (this *MyError) Is(target error) bool {
 log.Println("到这里来了....")
 if inputE, ok := target.(*MyError); ok {
  if inputE.Code == this.Code && inputE.Msg == this.Msg {
   return true
  }
 }
 return false
}

func main() {
 var e error
 e = FindUser()
 log.Println(e)

 if errors.Is(e, NewMyError(404, "ddd")) {
  log.Println("是 ErrorNotFind")
 }else {
  log.Println("不是 ErrorNotFind")
 }
}

首先我们先去实现下 Is 这个方法,随后我们使用 errors.Is 进行比较,你会看到控制台输出了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ go run main.go 
2022/08/13 17:20:48 404-找不到内容
2022/08/13 17:20:48 到这里来了....
2022/08/13 17:20:48 不是 ErrorNotFind

2、Unwrap

这是一个不大常用的 API ,标准库里面 fmt.Errorf 就是一个非常典型的使用案例。

场景是什么呢?

我们通常在错误异常的时候,会有给错误加上一些上下文的需求,那在哪里加呢?

就是错误的 Unwrap 方法里面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (this *MyError) Unwrap() error {
 this.Msg = "hello" + this.Msg
 return this
}

func main() {
 var e error
 e = FindUser()
 log.Println("最原始的错误:", e)
 wE := errors.Unwrap(e)
 log.Println("加了上下文的错误:", wE)
}

然后看下我们的输出结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ go run main.go 
2022/08/13 17:30:06 最原始的错误: 404-找不到内容
2022/08/13 17:30:06 加了上下文的错误: 404-hello找不到内容

你会发现,errors.Unwrap 后的错误调用了我们自定义错误的 Unwrap 方法,在我们的 msg 前面加了 hello。

对错误进行编程最常用的两个 API 就是这两个了,还有一些不大常用的比如 As,大家感兴趣的可以自行去翻阅下资料。

总结

Go 的错误处理和其他语言不太一样,如果遵守错误处理的规范,不对错误进行隐藏,写出来的代码一般都是比较健壮的。

于是就难免会出现一个包里面,特别多的错误处理代码,这就是时间和空间的博弈,就看 Go 语言的领路人如何取舍了。

其次每个人对错误的理解和处理思路方式都不太一样。

欢迎留下你对错误处理的思路和看法,就比如:

我们到底是该多使用哨兵错误,还是该少用呢?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GoLang全栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go版本大于1.13,程序里这样做错误处理才地道
之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了错误包装(Error Wrapping),以前想要在调用链路的函数里包装错误都是用"github.com/pkg/errors"这个库。
KevinYan
2023/01/03
4440
golang错误处理笔记
Go语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在 BUG 或发生了其它不可控的问题。
lelezc
2022/12/01
6000
Golang 语言怎么处理错误?
golang 程序大多数是通过 if err != nil 处理错误,在 golang 社区中,有一部分 golang 程序员对此举是持反对观点,他们认为在 golang 代码中存在大量的错误处理代码 if err != nil,使整体代码变得非常不优雅,应该在 golang 中引入其他处理错误的机制,例如 try-catche 或其它此类处理错误的机制。其实,他们忽略了 golang 中一个特别重要的概念,即 errors are values,并且 golang 作者 Rob Pike 也对此问题做出过回应,在 golang 代码中出现重复的错误处理代码 if err != nil,可能是 golang 用户的使用方式有问题。
frank.
2021/03/09
1.4K0
Go errors
Go 语言自身的 errors:https://golang.google.cn/pkg/errors/ 包实现非常简单,使用起来非常灵活,但是又有很多不足。我们在分析 Go 语言 errors 包的同时,也介绍下一个开源的 errors 包:https://pkg.go.dev/github.com/pkg/errors。
一行舟
2022/08/25
5380
Go errors
Go语言(golang)新发布的1.13中的Error Wrapping深度分析
Go 1.13发布的功能还有一个值得深入研究的,就是对Error的增强,也是今天我们要分析的 Error Wrapping.
sunsky
2020/08/20
2.2K0
Go 函数多返回值错误处理与error 类型介绍
error 接口只有一个方法,即 Error() 方法,该方法返回一个描述错误的字符串。这意味着任何实现了 Error() 方法的类型都可以被用作错误类型。通常,Go程序中的函数在遇到错误时会返回一个 error 类型的值,以便调用方可以处理或记录错误信息。
贾维斯Echo
2023/10/23
7440
Go进阶笔记关于Error
其实很多时候是使用的姿势不对,或者说,对于error的用法没有完全理解,这里整理一下关于Go中的error 。
后场技术
2020/12/29
5200
Go 进阶训练营 – 错误处理二:错误定义与处理
哨兵错误,就是定义一些包级别的错误变量,然后在调用的时候外部包可以直接对比变量进行判定,在标准库当中大量的使用了这种方式。例如下方 io 库中定义的错误。
Yuyy
2022/09/13
7470
Go 进阶训练营 – 错误处理二:错误定义与处理
go中如何处理error
go 中的异常处理和其他语言大不相同,像 Java、C++、python 等语言都是通过抛出 Exception 来处理异常,而 go 是通过返回 error 来判定异常,并进行处理。
编程黑洞
2023/03/06
7390
Go语言中的错误处理机制
在Go语言中,错误处理主要通过内置的error接口实现。error接口是一个内置接口,定义如下:
二一年冬末
2024/06/20
1700
Go Error 嵌套到底是怎么实现的?
这句话应该怎么理解呢?翻译起来挺难的。不过从源码的角度来看,好像更容易理解其背后的含义。
AlwaysBeta
2022/01/14
3410
Go中这么多创建error的方式,你真的了解它们各自的应用场景吗
由此可知,该接口只有一个返回字符串的Error函数,所有的类型只要实现了该函数,就创建了一个错误类型。
Go学堂
2023/01/31
7770
Go错误集锦 | 处理error时有哪些常见的陷阱
大家好,我是渔夫子。今天跟大家聊聊在Go中处理error时有哪些常见的陷阱以及如何避免。
Go学堂
2023/01/31
5230
从零开始写一个web服务到底有多难?(三)——异常处理
Go error是一个普通的接口,通过该接口得到一个普通的值。(当然也不太普通一点是error的首字母是小写的,但是我们仍然可以在外部使用它。)
4cos90
2023/12/30
3000
Golang error 的突围
姗姗来迟的 Go 1.13 修改了 errors 包,增加了几个函数,用于增强 error 的功能,这篇文章介绍 error 相关的用法。
梦醒人间
2019/09/18
1K0
Golang error 的突围
Go错误处理正确姿势
func Go(f func()){go func(){ // defer recover 捕获panic defer func(){ if err := recover(); err != nil { log.Printf("panic: %+v", err) } }() f()}()}
冬夜先生
2021/09/03
7520
Go编程模式 - 4.错误处理
捎带提一句:个人不太喜欢上面scanner的错误处理方式,这个要求使用方对这个包很熟悉,否则很容易忘掉后面的错误处理逻辑。但后面处理错误的逻辑,就很直接地将错误返回,可读性很强。
junedayday
2021/08/05
4310
掌握Go语言:Go语言精细错误,清晰、高效的错误处理实践(32)
错误处理是任何编程语言中都至关重要的一部分,Go 语言提供了一套简单而强大的错误处理机制,使得处理错误变得高效而清晰。
用户6256742
2024/08/18
2280
Go Errors 错误处理
error 是一个带有 Error 方法的接口类型,这意味着你可以自己去实现这个接口:
凌虚
2020/07/19
1.3K0
Go:温故错误处理
早期Go将错误视为值的处理方式为我们服务良好。尽管标准库对错误的支持很少——只有errors.New和fmt.Errorf函数,这些函数产生的错误只包含一个消息——内置的错误接口允许Go程序员添加他们想要的任何信息。它只需要一个实现了Error方法的类型:
运维开发王义杰
2024/04/25
1810
Go:温故错误处理
相关推荐
Go版本大于1.13,程序里这样做错误处理才地道
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档