前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何在 Go 中优雅的处理和返回错误(1)——函数内部的错误处理

如何在 Go 中优雅的处理和返回错误(1)——函数内部的错误处理

原创
作者头像
amc
修改于 2021-09-18 10:15:29
修改于 2021-09-18 10:15:29
9.7K0
举报
文章被收录于专栏:后台全栈之路后台全栈之路

在使用 Go 开发的后台服务中,对于错误处理,一直以来都有多种不同的方案,本文探讨并提出一种从服务内到服务外的错误传递、返回和回溯的完整方案,还请读者们一起讨论。


问题提出

在后台开发中,针对错误处理,有三个维度的问题需要解决:

  • 函数内部的错误处理: 这指的是一个函数在执行过程中遇到各种错误时的错误处理。这是一个语言级的问题
  • 函数/模块的错误信息返回: 一个函数在操作错误之后,要怎么将这个错误信息优雅地返回,方便调用方(也要优雅地)处理。这也是一个语言级的问题
  • 服务/系统的错误信息返回: 微服务/系统在处理失败时,如何返回一个友好的错误信息,依然是需要让调用方优雅地理解和处理。这是一个服务级的问题,适用于任何语言

针对这三个维度的问题,笔者准备写三篇文章一一说明。首先本文就是第一篇:函数内部的错误处理


高级语言的错误处理机制

  一个面向过程的函数,在不同的处理过程中需要 handle 不同的错误信息;一个面向对象的函数,针对一个操作所返回的不同类型的错误,有可能需要进行不同的处理。此外,在遇到错误时,也可以使用断言的方式,快速中止函数流程,大大提高代码的可读性。

  在许多高级语言中都提供了 try ... catch 的语法,函数内部可以通过这种方案,实现一个统一的错误处理逻辑。而即便是 C 这种 “中级语言”,虽然没有 try catch,但是程序员也可以使用宏定义配合 goto LABEL 的方式,来实现某种程度上的错误断言和处理。


Go 的错误断言

  在 Go 的情况就比较尴尬了。我们先来看断言,我们的目的是,仅使用一行代码就能够检查错误并终止当前函数。

由于没有 throw、没有宏,如果要实现一行断言,有两种方法。

方法一:单行 if + return

第一种是把 if 的错误判断写在一行内,比如:

代码语言:go
AI代码解释
复制
	if err != nil { return err }

这种方法有值得商榷的点:

  • 虽然符合 Go 的代码规范,但是在实操中,if 语句中的花括号不换行这一点还是非常有争议的,并且笔者在实际代码中也很少见到过
  • 代码不够直观,大致浏览代码的时候,断言代码不显眼,而且在花括号中除了 return 之外也没法别的了,原因是 Go 的规范中强烈不建议使用 ; 来分隔多条语句(if 条件判断除外)

因此,笔者强烈不建议这么做。

方法二:panic + recover

第二种方法是借用 panic 函数,结合 recover 来实现,如以下代码所示:

代码语言:go
AI代码解释
复制
func SomeProcess() (err error)
	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	assert := func(cond bool, e error) {
		if !cond {
			panic(e)
		}
	}

	// ...

	err = DoSomething()
	assert(err == nil, fmt.Errorf("DoSomething() error: %w", err))

	// ...
}

  这种方法好不好呢?我们要分情况看:

  首先,panic 的设计原意,是在当程序或协程遇到严重错误,完全无法继续运行下去的时候,才会调用(比如段错误、共享资源竞争错误)。这相当于 Linux 中 FATAL 级别的错误日志,用这种机制,仅仅用来进行普通的错误处理(ERROR 级别),杀鸡用牛刀了。

  其次,panic 调用本身,相比于普通的业务逻辑的系统开销是比较大的。而错误处理这种事情,可能是常态化逻辑,频繁的 panic - recover 操作,也会大大降低系统的吞吐。

  但是话虽这么说,使用 panic 来断言的方案,虽然在业务逻辑中基本上不用,但在测试场景下则是非常常见的。测试嘛,用牛刀有何不可?稍微大一点的系统开销也没啥问题。对于 Go 来说,非常热门的单元测试框架 goconvey 就是使用 panic 机制来实现单元测试中的断言,用的人都说好。

结论建议

  综上,在 Go 中,对于业务代码,笔者不建议采用断言,遇到错误的时候建议还是老老实实采用这种格式:

代码语言:go
AI代码解释
复制
if err := DoSomething(); err != nil {
	// ...
}

  而在单测代码中,则完全可以大大方方地采用类似于 goconvey 之类基于 panic 机制的断言。

Go 的 try ... catch

  众所周知,Go(当前版本 1.17)是没有 try ... catch 的,而且从官方的态度而言,短时间内也没有明确的计划。但是程序员有这个需求呀。这里也催生出了集中解决方案

defer 函数

  笔者采用的方法,是将需要返回的 err 变量在函数内部全局化,然后结合 defer 统一处理:

代码语言:go
AI代码解释
复制
func SomeProcess() (err error) { // <-- 注意,err 变量必须在这里有定义
	defer func() {
		if err == nil {
			return
		}

		// 这下面的逻辑,就当作 catch 作用了
		if errors.Is(err, somepkg.ErrRecordNotExist) {
			err = nil		// 这里是举一个例子,有可能捕获到某些错误,对于该函数而言不算错误,因此 err = nil
		} else if errors.Like(err, somepkg.ErrConnectionClosed) {
			// ...			// 或者是说遇到连接断开的操作时,可能需要做一些重连操作之类的;甚至乎还可以在这里重连成功之后,重新拉起一次请求
		} else {
			// ...
		}
	}()

	// ...

	if err = DoSomething(); err != nil {
		return
	}

	// ...
}

  这种方案要特别注意变量作用域问题:

  比如前面的 if err = DoSomething(); err != nil { 行,如果我们将 err = ... 改为 err := ...,那么这一行中的 err 变量和函数最前面定义的 (err error) 不是同一个变量,因此即便在此处发生了错误,但是在 defer 函数中无法捕获到 err 变量了。

  在 try ... catch 方面,笔者其实没有特别好的方法来模拟,即便是上面的方法也有一个很让人头疼的问题:defer 写法导致错误处理前置,而正常逻辑后置了。

命名的错误处理函数

  要解决前文提及的 defer 写法导致错误处理前置的问题,有第一种解决方法是比较常规的,那就是将 defer 后面的匿名函数改成一个命名函数,抽象出一个专门的错误处理函数。这个时候我们可以将上一段函数进行这样的改造:

代码语言:go
AI代码解释
复制
func SomeProcess() error {
	// ...

	if err = DoSomething(); err != nil {
		return unifiedError(err)
	}

	// ...
}

func unifiedError(err error) error {
	if errors.Is(err, somepkg.ErrRecordNotExist) {
		return nil		// 有可能捕获到某些错误,对于该函数而言不算错误,因此 err = nil

	} else if errors.Like(err, somepkg.ErrConnectionClosed) {
		return fmt.Errorf("handle XXX error: %w", err)

	// ...

	} else {
		return err
	}
}

  这样就舒服一些了,至少逻辑前置,错误处理后置。不过读者肯定会发现——这不是什么语言都可以这么搞嘛?诚然,这怎么看都不像是对 try ... catch 的模拟,但这种方法依然很推荐,特别是错误处理代码很长的时候。

goto LABEL

  理论上,我们可以通过 goto 语句,将错误处理后置,比如:

代码语言:go
AI代码解释
复制
func SomeProcess() error {
	// ...

	if err = DoSomething(); err != nil {
		goto ERR
	}

	// ...

	return nil

ERR:
	// ...
}

  对 C 语言比较熟悉的同学可能会觉得很亲切,因为在 Linux 内核中就有大量这种写法。这种写法呢,笔者其实说不出具体不好的地方,但是这个看起来很像 C 的写法,其实限制很多,反而比起 C 而言,需要注意的地方也更多:

  • 仅限于 ANSI-C 的话,要求所有的局部变量都需要前置声明,这就避免了因为变量作用域而带来的同名变量覆盖;但 Go 需要注意这个问题。
  • C 支持宏定义,配合前文可以实现断言,使得错误处理语句可以做得比较优雅;而 Go 不支持
  • Go 经常有很多匿名函数,匿名函数无法 goto 到外层函数的标签,这也限制了 goto 的使用

  不过笔者倒也不是不支持使用 goto,只是觉得在现有机制下,还是使用前两种模式比较符合 Go 的习惯。


  下一篇文章是《如何在 Go 中优雅的处理和返回错误(2)——函数/模块的错误信息返回》,笔者详细整理了 Go 1.13 之后的 error wrapping 功能,敬请期待~~


本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

原作者: amc,欢迎转载,但请注明出处。

原文标题:《如何在 Go 中优雅的处理和返回错误(1)——函数内部的错误处理

发布日期:2021-09-18

原文链接:https://cloud.tencent.com/developer/article/1879728

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
拒绝千篇一律,这套Go错误处理的完整解决方案值得一看!
导语 | 在使用Go开发的后台服务中,对于错误处理,一直以来都有多种不同的方案,本文探讨并提出一种从服务内到服务外的一个统一的传递、返回和回溯的完整方案,抛砖引玉,希望与大家一起讨论分享。 一、问题提出 在后台开发中,针对错误处理,有三个维度的问题需要解决: 函数内部的错误处理: 这是一个函数在执行过程中遇到各种错误时的错误处理。这是一个语言级的问题。 函数/模块的错误信息返回: 一个函数在操作错误之后,要怎么将这个错误信息优雅地返回,方便调用方(也要优雅地)处理。这也是一个语言级的问题。 服
腾讯云开发者
2021/09/23
8720
Go 进阶训练营 – 错误处理一:最佳实践
和java相比,go的异常处理两极化,panic比exception更严重,java exception是线程级别的,而go的panic是进程级别,任意goroutine出现panic都会导致整个进程挂掉,更能提醒异常情况。error比exception更轻微,在go中,error是当做值来处理的,更加灵活、细致,但需要大量的if err!=nil(考验代码功底的时候到了)。而exception的全局异常捕获用起来更方便、笼统。整体来讲,各有利弊(废话,要是绝对碾压就不会都存在了)。
Yuyy
2022/09/21
1.1K0
Go 进阶训练营 – 错误处理一:最佳实践
go panic与recover分析及错误处理
error 是一种类型,表示错误状态的类型,如果没有错误则是nil。直白点将:error 类型就是描述错误的一种类型。
地球流浪猫
2018/08/02
1.4K0
go panic与recover分析及错误处理
人非圣贤孰能无过,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang错误处理机制EP11
    人非圣贤,孰能无过,有则改之,无则加勉。在编程语言层面,错误处理方式大体上有两大流派,分别是以Python为代表的异常捕获机制(try....catch);以及以Go lang为代表的错误返回机制(return error),前者是自动化流程,模式化的语法隔离正常逻辑和错误逻辑,而后者,需要将错误处理判断编排在正常逻辑中。虽然模式化语法更容易让人理解,但从系统资源开销角度看,错误返回机制明显更具优势。
用户9127725
2022/09/22
1740
18.Go语言-错误与异常
在 Go 中, 错误 使用内建的 error 类型表示。error 类型是一个接口类型,它的定义如下:
面向加薪学习
2022/09/04
3770
学习go语言编程之错误处理
将error作为多种返回值中的一个,但是这并非强制要求。 调用代码时建议按如下方式处理错误情况:
编程随笔
2023/10/15
1820
Go两周入门系列-错误处理
go语言中主要有两类错误,一类是可预见的错误,不会导致程序退出,一类是不可预见的错误,会导致程序退出。
用户10002156
2023/10/08
2060
Go两周入门系列-错误处理
Go语言实战:错误处理和panic_recover之自定义错误类型
Go语言是一种现代的编程语言,它具有简洁的语法和强大的功能。在Go语言中,错误处理是一个重要的主题。Go语言提供了一种简洁的错误处理方式,即通过返回一个错误值来表示一个函数调用是否成功。此外,Go语言还提供了panic和recover机制,用于处理运行时错误。
阿珍
2025/02/22
940
Go语言实战:错误处理和panic_recover之自定义错误类型
Go错误处理机制: panic、recover与error处理
Go语言以其简洁明了的错误处理机制而著称。这一机制包括传统的error返回值、以及用于处理严重错误的panic和recover机制。本篇博客将深入浅出地介绍Go语言中的panic、recover与error处理,并通过实例代码帮助您理解如何正确地处理程序中的错误。
Jimaks
2024/12/07
3720
Go 语言常见错误——异常处理
在 Go 语言中,异常处理与传统的面向对象语言有所不同,主要通过返回错误值的方式来处理程序中的异常情况。虽然这种方式简洁明了,但在实际应用中,开发者常常会忽视错误处理的重要性,导致程序在运行时出现潜在问题或不易察觉的漏洞。
FunTester
2025/03/19
810
Go 语言常见错误——异常处理
Go 语言基础--错误&异常浅析
如果go是你的第一门语言,go的异常和错误体系可能比较容易接受,但如果你有一定的Java或者c++基础,go的异常和错误体系可能会比较不适应。 go的错误及异常体系也同样的追求简洁优雅,它摒弃了Java或者c++ 中的try-catch-finally模式,通过返回值的形式来表示错误,因为go认为try-catch会干扰程序的正常的控制流程,所以通过返回值的性质,认为错误其实是程序运行过程中的重要组成部分。 除此之外go把错误也异常分开了,真正的异常是指程序已经无法向下执行,需要由服务来进行特殊处理,在go中的表现形式是defer、panic、recover。
邹志全
2019/07/31
5900
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
7170
Go进阶笔记关于Error
其实很多时候是使用的姿势不对,或者说,对于error的用法没有完全理解,这里整理一下关于Go中的error 。
后场技术
2020/12/29
5000
Gin框架 - 自定义错误处理
很多读者在后台向我要 Gin 框架实战系列的 Demo 源码,在这里再说明一下,源码我都更新到 GitHub 上,地址:https://github.com/xinliangnote/Go
新亮
2019/07/29
1.8K0
100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南
大家好,我是猫头虎!今天我们继续探索Go语言的奥秘,迎来了我们的第23天学习之旅。在这一天,我们将重点关注Go语言中的错误处理机制。在实际的工程项目中,通过程序错误信息快速定位问题是我们的期望,但我们又不希望错误处理代码显得冗余和啰嗦。Go语言通过函数返回值逐层向上抛出错误,与Java和C#的try...catch异常处理显著不同。这种设计理念鼓励工程师显式地检查错误,以避免忽略应处理的错误,从而确保代码的健壮性。🚀
猫头虎
2024/04/09
1840
100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南
Go函数及与函数相关机制 【Go语言圣经笔记】
函数可以让我们将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用。函数的机制可以让我们将一个大的工作分解为小的任务,这样的小任务可以让不同程序员在不同时间、不同地方独立完成。一个函数同时对用户隐藏了其实现细节(黑盒特性)。由于这些因素,对于任何编程语言来说,函数都是一个至关重要的部分。
Steve Wang
2021/12/06
1.2K0
golang错误处理笔记
Go语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在 BUG 或发生了其它不可控的问题。
lelezc
2022/12/01
5670
Go 语言错误及异常处理篇(三):panic 和 recover
前面学院君介绍了 Go 语言通过 error 接口统一进行错误处理,但这些错误都是我们在编写代码时就已经预见并返回的,对于某些运行时错误,比如数组越界、除数为0、空指针引用,这些 Go 语言是怎么处理的呢?
学院君
2019/08/19
1.5K0
Go 语言错误及异常处理篇(三):panic 和 recover
Go版本大于1.13,程序里这样做错误处理才地道
之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了错误包装(Error Wrapping),以前想要在调用链路的函数里包装错误都是用"github.com/pkg/errors"这个库。
KevinYan
2023/01/03
4170
Go语言错误处理
错误指的是可能出现问题的地方出现了问题,比如打开一个文件时可能失败,这种情况在人们的意料之中。
Steve Wang
2020/12/25
5150
相关推荐
拒绝千篇一律,这套Go错误处理的完整解决方案值得一看!
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档