GCTT 出品 | Go 语言的错误处理 go 语言 gui
bigegpt 2024-10-29 12:57 5 浏览
Go 语言的错误处理是基于明确的目的而设计的。你应该从函数中返回所有可能的错误,并且检查/处理这些返回值。和其他语言相比,这一点可能看起来有些繁琐和不人性化,其实并不是这样的。让我们来看看一些基本的例子,然后继续做一些较重要的事情。
Non 错误
实际上 Go 有个概念 non-error。这是一个语言特性,不能用在用户自定义函数中。最明显的例子就是从 map 中通过 key 获取值。
if val, ok := data["key"]; ok { // key/value 在 map 中存在 }
当尝试获取指定 key 的值的时候,会返回一个可选的第二个值,是一个 boolean 类型表示获取的值是否存在。
运行这段程序不会有任何错误。你能看到是否接受第二个返回值是完全可选的。
另一个例子是从 channel 中成功读取数据。同样的,你可以在读取操作返回时,使用变量来接收第二个返回值。
第二个参数是一个 boolean 类型,表示语言结构层面的成功或失败,并不是一个严格的返回类型。你可以写一个函数声明 func() (interface{}, bool) 同上面的代码语义相同,但是不能够忽略第二个参数 bool 返回值了,你需要为他指定一个接收变量。
忽略错误
Go 提供了足够的灵活性可以让你忽略指定的返回错误。例如你想转换一个字符串到数字类型,并且你不在意转换失败时返回 0 。你可以使用 _ 字符来忽略指定的返回值,在下面例子中忽略了 error 返回值:
v := "abc" s, _ := strconv.Atoi(v) fmt.Printf("%d\n", s)
很明显转换不会成功,在这处理 “invalid syntax” 错误会很繁琐。当然这取决于你的使用场景,有一些场景处理返回错误没什么价值。
我近期遇到的一个例子是 sony/sonyflake 。 这个项目是一个 ID 生成器,返回 int64 类型的 id 和可能的错误。
想要生成一个新的 id ,你只要调用 NextID 方法即可。 func (sf *Sonyflake) NextID() (uint64, error) NextID 能够连续生成 ID 从开始时间到 174 年左右。当超过这个限制的时候,NextID 会返回一个错误。
我非常确信在看这篇文章的人不会活过174年。在这种情况下,你真的需要处理那个特定的错误么?这里真的需要返回一个错误么?
我认为这是一个设计缺陷,我们可以使用 Go 的另一个灵活性来更好地处理:panic。参见一篇很棒的文章 go by example:
使用 panic 的一个通用的场景就是如果一个函数返回了一个错误值,但是我们不知道或者不希望去处理的时候中断执行。
还有一些其他的关于忽略返回错误的例子。可能最常见的忽略返回错误的处理方式是在 json.Marshal 中。在明确的理解之后,有些错误在第一次发生时可以不去处理。
连续的错误处理
你的目的应该是处理所有的错误,像下面这样结束执行相对比较容易:
如果能像处理 if 语句一样,独立处理每个返回错误不是更好么?让我们看一些你不知道的情况:
if func1() || func2() || func3() {
这个 if 语句会分别测试每个表达式。也就是说如果 func1() 返回了 false,那么 func2 和 func3 函数就不会被调用。if 语句可以中断执行流程,尽管如此也没有方法使用一条语句完成检查返回错误。 至少你可以按照下面的方法来处理:
这个例子中,我们在表达式的前面添加了一个简单的语句,这条语句会在测试表达式之前执行。这是 Go 语言规范的另一个特性。可惜的是,我们不能使用这个特性来控制程序流程。但是,我们可以考虑创建一个可变参函数来接收 func() error 参数,并且在第一个错误发生时立即返回。
这里例子 playground example 演示了如何实现一个顺序调用函数的处理,并且所做的修改不会影响到函数结构。基本上只依赖于如何保存函数返回值 除了返回错误之外。
如果你正在处理 jmoiron/sqlx 你可以这么写:
一个明显的问题就是过于冗余的部分,把一个局部函数包装到函数签名中,如果编程语言的语义允许在 if 语句中(多次)赋值和测试的话,出错检查可以简化成:
可惜的是,Go 不会把 errors 作为 boolean 表达式处理,也不允许在 boolean 表达式中赋值,会提示错误 ”expected boolean expression, found simple statement (missing parentheses around composite literal?)“ 。对于这一点我并不是很强烈的认为不好,因为还有其他的方法可以做到。如果你在其他语言例如 Node 或者 PHP,尝试使用赋值给一个变量来代替测试一个值的话,你会发现 Go 的处理方式更加优美,注意:有时也非常痛苦。
改进错误处理
通常我写的包括输入,输出参数的验证函数,功能函数的函数签名是 func() error。来看一个更复杂些的例子,我创建了一个 response writer 输入参数为 interface{} 把第一个非空值或函数返回值写入 http.ResponseWriter。
这个函数的好处是提供了基于可变参数的条件执行处理。和传入 ...error 和 []error 不同的是不需要先执行所有的函数。
使用这个函数的一个简单的 API 调用样例如下:
在后面的语句 resputil.JSON 中可以看出,程序执行流程是显而易见并且明确的,当有任何错误发生时都会中断执行并返回。 另一个附带的好处是,无论何时发生错误了,你只要返回错误即可,不需要关心在这里应该返回的其他返回值,因为会在外部的闭包中被处理,并且只处理一次。
注意:语言需要支持函数 return err 而不仅仅只是返回错误信息,当你使用这种处理方法时,所有其他的返回值都是未初始化时的默认值,所以不违反期待函数返回值的规则,在这里也有类似的建议 this issue filed for Go2
异步错误处理
几周前在 reddit 上有过一个讨论, @ligustah 推荐看一下 x/sync/errgroup 包,这个包提供了类似 sync.WaitGroup 实现方式的 Group structure ,会返回发生的第一个错误或者在没有错误时返回 nil 。每个函数都可以在 goroutines 中执行。
引用至 godoc 中的例子 JustErrors
开始的时候,我觉得这个包还不错,但是里面有一些需要注意的地方。
- 你可能需要返回所有的错误(你只能拿到第一个)
- 在发生错误的时候想要中断执行(必须等到所有的 goroutines 执行完毕)
- 执行的是并行检查(没有提供顺序执行的 API )
根据你的使用场景,可以参照这个模型,做一些私有的实现。这个包本身有些繁琐,看了前面的介绍,写一个聪明的错误检查封装函数只需要少数的几行代码即可。
最后总结
有些时候看起来 Go 语言检查返回值错误是比较痛苦的事情,特别是当你受到有 try/catch 特性的语言影响的时候,例如: Java,PHP 等。当你第一次看到 Go 的这种处理方式的时候可能并不喜欢,希望检查错误的处理能更好些(更简洁些?),我相信其他的语言有更糟糕的例子,更加繁琐,更多不足的地方。
errors 和 panics 有一些不同的地方,实际上 panics 是带有函数调用栈信息的。如果你想在 errors 里面添加调用栈信息,我推荐你使用 Dave Cheney 的 pkg/errors 包。文章 Don’t just check errors, handle them gracefully 对每个人来说都是在必读列表中的。
在其他语言中对错误的处理有一些显著的痛点,如果 Go 的处理有一点繁琐的话,那么它带来的是更多的好处。
via: https://scene-si.org/2017/11/13/error-handling-in-go/
作者:Tit Petric 译者:tyler2018 校对:polaris1119
本文由 GCTT 原创编译,Go语言中文网 荣誉推出
相关推荐
- 悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)
-
新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...
- 高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源
-
凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...
- 微服务架构实战:商家管理后台与sso设计,SSO客户端设计
-
SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...
- 还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑
-
在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...
- Seata源码—6.Seata AT模式的数据源代理二
-
大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...
- 30分钟了解K8S(30分钟了解微积分)
-
微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...
- SpringBoot条件化配置(@Conditional)全面解析与实战指南
-
一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...
- 一招解决所有依赖冲突(克服依赖)
-
背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...
- 你读过Mybatis的源码?说说它用到了几种设计模式
-
学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...
- golang对接阿里云私有Bucket上传图片、授权访问图片
-
1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...
- spring中的资源的加载(spring加载原理)
-
最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...
- Android资源使用(android资源文件)
-
Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...
- 如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)
-
深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...
- @Autowired与@Resource原理知识点详解
-
springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...
- java的redis连接工具篇(java redis client)
-
在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)