Golang Go语言错误处理的姿势

发布于 1周前 作者 vueper 来自 Go语言

Golang Go语言错误处理的姿势

各位好。

前段时间看到 v2 上有个帖子,询问如何在 Go 中为错误加上堆栈,我以前也为类似的问题困扰过,后来找到了 pkg/errors ,再后来官方库有了 fmt.Errorf() ,我把这个小小经验写了下来,希望能抛砖引玉,欢迎各位交流拍砖。

在这篇文章中,我们将区分错误( error )和异常( panic ),讨论什么样的错误是“好”的(容易检查和排错),介绍一种让错误变“好”的常用方式(fmt.Errorf())。

谢谢。


更多关于Golang Go语言错误处理的姿势的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

52 回复

func ReadCache(city string) (weather string, err error) 这种 return 方式不是特别的推荐。 比较难读

更多关于Golang Go语言错误处理的姿势的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这不是 go 的常见做法么,还能写出啥花样?

我也不喜欢这样,而且有时候会有些问题

#2 不要给返回值名字,这很容易写出 bug

这种写法本身没有什么问题,就是相当于在顶部声明了变量。
只不过这种写法可以直接 return,在大的函数里可能不清楚到底 return 了什么。

这时候只要把返回值写进去就可以了。

裸返回和一般的返回是等效的,优势是可以少写一点东西。

劣势是有时确实容易写出 bug, 这时一般推荐用上包含 ineffetient assign 的静态分析 linter,可以避免翻车。

这不是一种常见的作法。 自己写的容易忘记,何况让别人读。


感觉这种方式主要问题可能不是难读,而是容易发生变量作用域覆盖或者忘记返回具体值,引入潜在的 BUG 。但是如果一个函数返回结果非常复杂,使用返回值命名可以降低理解成本。可以写成这样 func ReadCache(city string) (_weather string, _err error),避免直接对 _xxx 赋值,而是强制使用 return 。

standard libs 都用一种方式是有原因的。

不能一棍子打死,短小的代码这样写有时还是能省略无用的声明的
容不容易出 bug 是另一回事,怎么就难读了?

#9 go 源码里这种还是挺常见的

https://tour.golang.org/basics/7 自己看吧。我不懂怎么解释这么明显的东西。

另外,虽然源码里算常见,但我个人也不喜欢这种写法,自己代码里基本不用。

tour 里说的是建议性的并不是说 std 里没有,我第一次是回复你 #9 “standard libs 都用一种方式”

你这文章真是写了个寂寞。。
标题叫错误处理,正文没有任何错误处理的内容。。
可能对错误处理有一些误解,使用 fmt 添加错误上下文的错误,是无法处理的。只能打个日志,然后方便排查问题。
错误处理最重要的是错误识别,也就是需要用到 Is,As,Unwrap 系列方法,识别已知的错误,然后根据情况处理,未知的错误继续往外抛。
要做错误识别,需要支持错误继承,嵌套什么的,pkg/errors 也并不只是添加堆栈上下文,也是稍微支持了错误继承。

同意 这种写法给看代码的人带来挺大的心智负担 变量声明越接近变量使用的地方最好



有心智负担,是因为还没习惯;习惯成自然,习惯之后也没什么事情

大哥你自己不看一下你贴了些什么吗? 里面的源码用了 named return, 但是别人用 naked return 了吗?我是真的不想回这个话题了, 自己了解清楚再出来杠。 我的上个回复也不是针对你的, 我回复的是 js285 什么的

naked return 除了写的时候剩下 2 秒钟,根本没有其他的好处。naked return 除了 c++, 其他语言应该没有。不熟悉 go 的人会误以为什么都没有 return. 成熟的团队也不会让你短的 func 用 naked return, 长的用正常 return.

#19 如果这样讲的话,那你也看一下,你在之前有提到过 naked return 吗?你看看一楼没有提把?你一楼的更让人想到的是返回值那里的变量声明而不是 naked return 。js2854 #10 的回复也是声明相关的而不是 naked return 。如果要细聊,那就聊明白点。而且,你确实没有 at 我进行回复,但是刚好是在我下一楼,你谁都没 at,我以为是回复我呢。即使不是回复我,你贴出来的链接,或者可以用来以上与你相关的所有人?那沟通技巧也可以提高下。想让别人看清楚再来杠,自己也要尽量把事情说清楚,否则别人也很难看清楚。

另外,对于我个人的感觉,函数定义的地方的返回值声明,比 naked return 让人阅读障碍更大。

再另外,你看下我贴的这个:
github.com/golang/go/blob/master/src/bufio/bufio.go#L169
你再往下看几行,std 到底有没有:
github.com/golang/go/blob/master/src/bufio/bufio.go#L174

纸上得来终觉浅,不要只看一些观点性的东西就轻易下一些结论


第一,你一楼不管是指 named 还是 naked,我个人都没想反驳,挺对的。但后面提到的 std 里只有一种,用词太武断了,所以随口回了一句。
第二,这个语法,虽然别扭,但并不是什么高级玩意,你 #12 的语气,是带着不屑的,对于不是什么高级货的东西的不屑,没什么必要,真要是牛逼的东西你懂别人不懂,那你狂忍你狂,我举双手支持你的强。但这点玩意如果也觉得值得炫耀的,那可能是出于瓢的阶段了,刚好又是在我下一楼以为是回复我,所以才会连回了几个。

这个帖子浪费我太多积分了,不再回复了。有兴趣的欢迎来我的项目交流:
github.com/lesismal/nbio (可以拿 evio/gev/gnet,以及 gobwas/ws 等来对比看看)
github.com/lesismal/arpc
相关话题(压测请不要看各个仓库展示的数据,以自己代码实测为准):
colobu.com/2021/08/01/benchmark-of-rpc-frameworks

你说得对,我这篇文章对错误的识别处理说得太少了,标题有问题。感谢提醒。
pkg/errors 里有个 Cause()方法,在 Go 1.13 前用来做错误识别还是很趁手的。

你回复的莫名其妙的。你自己不觉得很迷吗?我不想再解释也不是对你说的, 我对那位上来就反对然后不举证的老铁说的。 你非要对号入座。你觉得例子你觉得是很好的例子吗? 里面一会 named return , 一会又用另一个方式,named return 里一会 return , 一会又完整列出来。 你喜欢就这么写好了,我只是说我知道一些团队特意禁止这种方式。你自己不那么写,还要浪费那么多时间找些不好的例子, 就为打我的脸?您是真的迷

站长说的对,不要浪费时间在没意义上的人和事情上。观点都描述不清楚,到现在都没听明白你到底是说 named 还是 naked,自己也没 at 别人刚好在我楼下而且语气可以被当成是回复上面相关人的,自己都不反思下表达能力么。
怪不得编译器厂招聘帖子里一堆人怼你

block 了先

反思,反思,少回帖,只在 V 站攒积分少 BB 。虽然还是想知道 “std 里只有一种” 到底指的是什么,但是我放弃。

我昔所造诸恶业,皆有无始贪嗔痴。

戒骄戒躁,继续安心写自己喜欢的东西,向站长学习。

哈哈哈,你是最棒的呢。

1.自己都不用方式 return 方式,花时间去翻一些代码出来想打脸。2. 会去翻一大篇过去的贴来攻击,r 不是显得你更蠢。3. 你真是闲得慌,这帖子一开始就跟你无关,非要强行对号入座。4. 最后还来一堆圣母。5. 你是真的 6666

#20
1. 我的确不用这种方式,但是你能看懂 #10 是回复的这句不:“standard libs 都用一种方式是有原因的。”?我希望既然聊技术严谨一点,不要没怎么看过源码就随口说源码怎么样,或者其他类似的观点,因为大家信誓旦旦言之凿凿的观点,可能会对其他人造成误导。
2. 不是翻帖子,而是之前就看过那个帖子,感觉一楼可能还相对经验欠缺、讨论技术的时候自己没太搞懂知识点就随口说了,然后加上我上一条看到他说的这个观点,所以觉得应该回复一下,但是回复了几次,一楼都没有说清楚到底他说的 “standard libs 都用一种方式是有原因的。” 中的这种方式是指什么,或者也可能我理解能力太差、一楼已经在后续回复中讲清楚了但是我没 get 到,如果是这样,那各位可以指正,我虚心接受。
3. 我上面回复了帖子,对方在我下一楼没有 at 任何人,我也没太关注他们之前的讨论,而且我下一楼的回复中的内容也可以用来回复我的内容,并且语气带着轻浮,参考我本次回帖第 2 条中的出发点,所以觉得有必要多回复一些。
4. 你确认圣母这个词符合你用在这里的场景吗?

另外,如果本来帖子与我无关、我自己“强行对号入座”,那我对号入座跟阁下又有什么关系呢?而且我前面几次回复中也解释过了为啥我会回复好几段。你倒是也挺有时间得嘛,或者你可能也没仔细看我前面回的内容没分析我和一楼多次对话中的内容吧~

小伙子心态蛮好的,我喜欢,大过节的,不吵了,没必要,如果造成不适,我抱歉,请见谅。但该严谨还是建议严谨

#28 上一楼是回复 28 楼,写错了写成 20 。
补充一下,我没花多少时间故意翻源码打脸之类的,因为平时就经常源码,也正是因为以前就隔三差五在源码中看到类似的写法,所以看到 #9 说只用一种方式的时候,才会第一时间觉得不严谨。

今年完善了一份 poller 的框架,几年前比较火的一个老外的关于 golang websocket 百万连接的帖子和相关的 gobwas/ws 都是存在缺陷的方案,我这个实现了完整的异步流解析 tls/http1.x/websocket,能够解决 golang 1000k 问题。对比其他 golang 非阻塞 io 框架,他们都还没有支持这些,并且单就网络库部分的性能,基本高于其他库,易用性扩展性都更强。吹个牛逼说,目前全网独一份,不信你可以去看 evio/gev/gnet/easygo,还有字节的 netpoll,或者也去找找有没有其他库,做下对比,看看他们有没有支持这么多功能。
或者不必功能支持完整度,单就性能对比下:
https://github.com/lesismal/go-net-benchmark

这些花费了我很多时间,还在持续打磨,社交确实会消耗很多精力,所以才会有 #26 反思。你如果觉得我很闲,可以粗略看下我的几个库相关的,再看看我是不是很闲

当然,对于这个帖子消耗了这么多时间,确实是犯二了,我继续反思。

我所有的回复都是针对这个人的这个话 ”XTTX 这不是 go 的常见做法么,还能写出啥花样?" 。 所有的 std libs 都有明确的 return 。所有的事都有 corner case. 你自己举例的代码都来回混用,肯定不是值得推荐的写代码的方式,反而是一个 readability 的反例。 一个 method 里使用了 nake return,后面又注明 return value 。你说 block 了我 xttx,我用这个号回你。你这多看不出来么。 你强行打脸不成还去挖坟,你这个病得看。我也特别闲, 你挖坟得时候可以看看我的那个贴,错了就是错了。 <<为什么不要用 naked return>> https://levelup.gitconnected.com/go-naked-returns-4e2094b598e6?gi=5c972b7c406c https://www.ardanlabs.com/blog/2013/10/functions-and-naked-returns-in-go.html

我针对的是你 #9 那句 “standard libs 都用一种方式是有原因的”,BTW 你看懂我引用的那几行代码是哪里的没。。。你仔细看下,那不是我自己代码,那个就是 go 源码,你所说的 std libs 不会是不包括 go 源码吧?

“<<为什么不要用 naked return>>”

“standard libs 都用一种方式”
这两句话不是一回事好吗,我 #11 是回复 #9 楼这句的,麻烦少年看下

如果你不知道我引用的那几行是 go 源码,那当我没说吧

源码都没怎么看,虽然不是什么过错,但是言之凿凿说源码这样子,对其他人会是一种误导。我是针对这个在杠。
我也早说过了,named 还是 naked,我自己都不用,也从没说过 named 或者 naked 是好东西。

#31 又是说 “所有的 std libs 都有明确的 return”。我引用那几行代码就是 go 源码,你看看有没有 named 和 naked 。如果说我之前错以为你没 at 人那一层是在回复我,那是我理解错了。但是我感觉我之前说的啥你也没看懂

func (b *Reader) Discard(n int) (discarded int, err error) {
if n < 0 {
return 0, ErrNegativeCount
}
if n == 0 {
return
}

b.lastByte = -1
b.lastRuneSize = -1

remain := n
for {
skip := b.Buffered()
if skip == 0 {
b.fill()
skip = b.Buffered()
}
if skip > remain {
skip = remain
}
b.r += skip
remain -= skip
if remain == 0 {
return n, nil
}
if b.err != nil {
return n - remain, b.readErr()
}
}
}

// ReadByte reads and returns a single byte.
// If no byte is available, returns an error.
func (b *Reader) ReadByte() (byte, error) {
b.lastRuneSize = -1
for b.r == b.w {
if b.err != nil {
return 0, b.readErr()
}
b.fill() // buffer is empty
}
c := b.buf[b.r]
b.r++
b.lastByte = int©
return c, nil
}


这两段来自同一个你引述的 package, 什么叫双重混用。这种代码读起来就不是非常舒服。你可以举证为什么这种代码是最优雅的。

“naked return 会影响 readability” 对我来说很明显,我不知道怎么解释。

我所有的回复都因为那个人的“我只知道 naked return”的半嘲讽,这个贴早就可以停了。你强行打脸,你打成了吗?其他人也应该少和你对话, 一言不合你就开始翻我的 post 记录,找找能人身攻击的东西,就差人肉了吧?其他人也回来翻翻你这个贴的行为

你说的那个贴,我真的不觉得有什么。我错了别人指出来,我认错,他还解释了我的提问。

“这两段来自同一个你引述的 package, 什么叫双重混用。这种代码读起来就不是非常舒服。你可以举证为什么这种代码是最优雅的”
—— 所以你是真看不懂是吧。我上面说了,没有反驳这个是不是优雅。我针对的是,你说 std 里只有一种方式、std 里没有这种方式,而我引用的代码就是 std 里的、有这种方式。我上面都解释过了我也不觉得这样好,但不好跟没有是两码事,不要自己可能都没读过源码就随便说源码里没有。

“一言不合你就开始翻我的 post 记录,找找能人身攻击的东西,就差人肉了吧”
—— 上面也解释过了,是先看到你那个帖子里,又看到你这个帖子里,你对技术的观点太随意了。你可以再看下我 #29 楼中的第 2 条。

如果说前面是我自己以为你在回复我、算是我瞎,那后面回复了这些跳,解释了好几次,你没一次看懂了。另外,别提了另外个帖子就上纲上线的人身攻击转移话题,同为讨论技术的态度不严谨,要掰扯那正经点先把源码里有没有这种方式的事情扯清楚。我都强调过了那个就是源码、里面有这种方式、你都不正面回复又。反而我都强调过了我没说 named/naked 好,你也看到过了然后又来继续解释这个不好,我纠结它好不好了吗?

  1. “<<为什么不要用 naked return>>” —— 我没反驳过,我觉得这么说是 ok 的。
    2. “standard libs 都用一种方式” —— 我反驳的是这个,因为源码里有这种方式,我引用的代码就是 go 源码

    如果分不清我是在说 1 还是在说 2,那就停了吧。如果没读过 go 源码,甚至不知道 go 上面引用的就是 go 源码,那你随意吧。

回到技术问题上,我也赞成 named/naked 不好,go 源码里虽然也有这样用,但仍然也不建议大家这样用。

go 源码有很适合用来学习,建议有兴趣的同学多读读。

楼歪到姥姥家了

如果没有裸返回, 如何实现 recover 修改函数返回值? 请教?

你 jjyy 那么多只为打脸我一句“standard lib”都不用 naked return. 然后还举个例子, 例子里还是反向证明大部分都不用 naked return. 你也太搞笑了,是你去翻我的“黑史”。 我可没有去翻你的贴子。

“我也赞成 named/naked 不好, 但是我抓到了对方论点里一点不严谨的地方, 我要去打他的脸,打不成我就去翻他以前的贴子,去黑他,然后我再装圣母。” 这是杠精行为, 我不回复了。 你慢慢品。

大佬请教问题, 如何实现 recover 修改函数返回值?

我已经解释过两次了,不是翻你黑史,而是那个帖子先有了印象所以加上这个帖,但凡你前面仔细看下我在说哪个点,我至于掰扯这么多、提那个帖子?因为你不把观点表达清楚、回复之前那个哥们也没 at 别人,回复我也不看我是在说哪个点,所以才会联想到你之前帖子、聊技术太随意了。

另外,也别上纲上线的弄好像我人身攻击你一样,这两个帖子你要是不那么随意,也不会有这么多互喷。我抱怨下就成了我故意攻击你?那你随意在先、并且我都回复了那么多似乎直到上一条你才看懂我在杠什么,那你就尊重别人了?麻烦你能不能讨论的时候认真点先?

再说,技术的事情,就事论事,该严谨的地方,怎么就不能杠精了?十几年前,或者至少八九年前,技术群还没那么多,好多人都是论坛上交流,一个技术问题,一群人争论得没完没了,随便盖出几十几百楼,就比如源码的事情,尤其是底层的一些、源码级别相关的,大家都非常重视,一丁点细微差别都可能是天差地别,经常都能杠得各自在各自电脑前面红耳赤,但是技术的问题,杠完了大家都更清楚了,也不会因为杠技术本身而觉得彼此伤害了。反倒是遇到随便拿来什么就信口开河满嘴跑火车聊技术的会让大家郁闷。技术的事情,从论坛到工作到开源,包括内核社区,即便不是三天两头,也是隔三差五就会有杠的事情发生,认真杠一些事情才争论的清楚,不信你去翻翻 linus 大神历史。

说我杠精行为就杠精行为吧,我承认,对于技术,我确实比较杠。
但是,杠精就不对了?专业领域的杠,别人还夸你认真钻研呢,如果都不杠了,高级的科学家、工程师、设计师各种估计都得绝种。都不杠了,小白错误言论满天飞,更多小白被带歪。技术不是娱乐圈,虽然拿娱乐心态聊技术不违法并且是你的自由,但既然你不认真了,也麻烦你不要怪罪别人认真对待技术的人。

如果你觉得就这么一句"standard lib"无关大雅无关紧要,那好,你继续随意吧。你可以自己回顾看下,这两个帖子你对技术的点是不是不够认真,这个帖子提到源码相关的,我猜测你没有怎么研究过。如果没研究过,就没必要随口就说关于它的事情、对更多小白会造成误导。

并且再强调一次,我是先看到的那个帖子然后才注意到这个帖子,不要以为别人有心情故意去黑你,你没那么重要,我也没那么重要。聊技术,就按技术论技术完事了,
我提那个帖子只是说你不认真,但是反过来给我扣的帽子是我搞人身攻击,想问下,到底谁在人身攻击谁?

这两天这个帖子说太多了,浪费了自己时间,也浪费了大家的时间。抱歉了各位,以后我尽量忍住、少回帖。
如果这种行为不好,站长封我我也认,确实心境还够,需要继续修炼学会忍耐与安静。

#46 更正结尾处:确实心境还不够,少打了个“不”

func FindWeather(city string) (weather string, err error) {
weather, err := ReadCache(city)
if err != nil {
err = fmt.Errorf(“reading cache: %w”, err)
return
}

if weather != “” {
// cache hit
return
}

// cache missed, query for data source and update cache
// …
}

虽是这么说,但你这个 Example 就已经有问题了吧,weather 和 err 作用域变了导致裸返回的都是空值

囧,我第二行写错了,:=应为=,我今天改下,谢谢提醒。
作用域应该是没变,上面这句我这么写编译不过去,因为:=左值没有新变量,伪代码大意了。

我这辈子都没有碰到有人为我写那么长的文章的人,我谢谢您。

不客气。国庆结束了,好好写代码持续输出。

在Go语言中,错误处理是一个核心且常见的编程模式,其设计哲学强调简洁和明确。以下是几种常见的Go语言错误处理“姿势”:

  1. 使用内置的error类型: Go语言通过内置的error接口来表示错误,任何实现了Error()方法的类型都可以作为错误。这种模式允许你自定义错误类型,提供更丰富的错误信息。

  2. 多值返回: Go函数可以返回多个值,这通常用于返回结果和错误。例如,Read方法返回读取的字节数和可能发生的错误。

  3. 错误检查: 在调用可能返回错误的函数后,应立即检查错误。如果错误不为nil,则通常通过log.Fatalpanic或返回错误来处理。

  4. 错误包装: Go 1.13引入了fmt.Errorf%w动词,允许你创建包含原始错误的包装错误。这有助于保留错误的上下文和堆栈跟踪。

  5. 延迟错误处理: 使用deferrecover可以在某些情况下延迟错误处理,尤其是在需要清理资源或捕获恐慌时。

  6. 自定义错误类型: 定义自己的错误类型可以提供更具体的错误信息和行为。这有助于调用者区分不同类型的错误,并采取相应的措施。

总之,Go语言的错误处理机制灵活且强大,通过合理使用上述技巧,可以编写出健壮、易维护的代码。在处理错误时,务必保持清晰和一致的风格,以提高代码的可读性和可维护性。

回到顶部