golang 的 defer 真是个好设计

212 天前
 afxcn

我们开发的时候使用了 sync.Pool ,所以需要考虑资源释放问题。

例如下面这样一段代码:

// bearerAuth is a function that performs bearer token authentication and authorization based on the provided access token and values.
func bearerAuth(c *web.Ctx, vals ...int64) error {

	accessToken := c.BearerToken()

	if accessToken == "" {
		return web.ErrUnauthorized
	}

	cat, err := proxy.GetAuthByAccessToken(accessToken)

	if err != nil {
		return err
	}

	c.Init(cat.UserID, cat.UserRight)

	if !utils.CheckVals(cat.UserRight, vals...) {
		cat.Release()
		return web.ErrForbidden
	}

	cat.Release()

	return nil
}

我们得在所有退出路径上调用 cat.Release(),有了 defer ,我们只需要这样就解决问题了

func bearerAuth(c *web.Ctx, vals ...int64) error {

	accessToken := c.BearerToken()

	if accessToken == "" {
		return web.ErrUnauthorized
	}

	cat, err := proxy.GetAuthByAccessToken(accessToken)

	if err != nil {
		return err
	}

	defer cat.Release()

	c.Init(cat.UserID, cat.UserRight)

	if !utils.CheckVals(cat.UserRight, vals...) {
		return web.ErrForbidden
	}

	return nil
}

如果是自己建对象,就更方便了:

user := model.CreateUser() defer user.Release()

10536 次点击
所在节点    Go 编程语言
81 条回复
hez2010
211 天前
@pkoukk 这不是用筷子喝汤,这是面前给你摆一碗咖喱拌饭但餐具只有吸管。
Jirajine
211 天前
@kuanat 其实只有一个原因,就是 simplicity 。go 是一个 dsl ,它只是为特定场景设计的,不适合其他场景很正常。你非要把 go 在这些场景的缺陷(如缺乏 sum type/null safety/checked initialization )解释称 “the go way”更好,其实是一种皈依者狂热。
cmdOptionKana
211 天前
@Jirajine 为什么要扣帽子?你说别人是皈依者狂热,我说你是键盘侠杠精,这种互扣帽子毫无意义。
kuanat
211 天前
@Jirajine #62

我的观点一直都是,在合适的场景使用恰当的工具。在说 Go 好的帖子里这样说,在说 Go 不好的帖子里也这样说。就连这个帖子里,我的观点也是一致的,defer 不能滥用,而 error 处理要换思路。

关于 sum type 我从来没谈过,因为我想不出这样做的原因,我也没有从 Go 的官方文章找到头绪。Null safety/checked initialization 都是用来佐证,语言特性是取舍这一观点的。既然选择了简洁,就要付出代价,Go 没有特别多的选择。

既然你能准确使用这些术语,说明对这些话题有足够的认知,那完全可以阐述你对于语言设计的认识和观点,没有必要归结于个人偏好。
ensonmj
211 天前
@arloor 但是 rust 特别强调不能依赖 drop 来保证资源释放,很烦
bukekangli
211 天前
屎真香?
mayli
211 天前
@aababc with 多个嵌套就有点费劲
arloor
210 天前
@ensonmj 哪里说的
Jirajine
210 天前
@cmdOptionKana #63 为什么要扣“扣帽子”的帽子?
我们都知道任何语言都有优缺点,但如果它的缺陷在你感觉而言也成了优势,这种现象将之描述为皈依者狂热。
ragnaroks
210 天前
标题改成“defer 对于 golang 真是个好设计”就没这么多争论了
mark2025
209 天前
mark2025
209 天前
zzhaolei
209 天前
该说不说的,评论区太多 Javaer 了,Javaer 就是改不了自己的 Java 思维,套用之前看到的一个 boss 上的评价,Javaer 的大脑已经被污染了,接受不了新的东西。
zzhaolei
209 天前
https://go.dev/talks/2012/splash.article

没事儿读一读这篇文章,看看 Go 是怎么诞生的,是为了解决什么问题。吵来吵去的也只是用自己的固有思维去思考,没有意义。
lolizeppelin
209 天前
@kuanat
defer 你解释那么多都没啥意义....

defer 怎么实现大家并不关心,纯粹语法上难看而已
python 的 with 就是漂亮,defer 就是丑
kuanat
208 天前
@lolizeppelin #75

确实我说 Go 好不好对 Go 和其他编程语言没有什么意义,但不代表我在这里的回帖没有意义。

很多人觉得我是狂热的吹捧者,我想还是有所误解。这里面有我表述方式的原因,也有读者立场的原因。我在之前的某个帖子里表达过,我大多数回复的目的是“创造价值”,因为我从 V2EX 学到了很多,我也想把我学到的反馈给社区。Go 的主题比较多更多是因为它有一定流行度却又不那么流行,在我日常工作里,主流的语言应用比例里 Go 反倒没有那么高。

关于 python 的 with 语法,我有个问题,你怎么看自定义对象需要手写 __enter__ __exit__ 的?

单独拿 with 出来,配合 python 的缩进,起到了看上去很美的效果。但是没有 RAII ,为了实现 scope ,又要回到手写析构的路上。

因此我一直都强调,单独拿一个关键词一个特性出来比较是不合适的。
lolizeppelin
208 天前
@kuanat

你不怎么写 python 才会有这个问题, python 有 contextlib.contextmanager

golang 的很多问题就是 TM 的太丑了....还在那里犟,一开始还吹逼不要泛型呢,后来呢?
有些丑的好改有些改不了了而已...大家凑活着用罢了。
kuanat
208 天前
@lolizeppelin #77

如果只是单纯辩论美丑,我原本没打算回复,但既然你提到了 python 用装饰器来减少代码,那我觉得还是可以讨论一下的。

生命周期管理这个事情的困难在于:资源的分配和释放可能时机不确定,又或者在代码中的位置不同,还可能是多线程的主体不确定。原地释放几乎没有人会写错,而 scope 变得很大的时候,错写或者漏写的几率就会变大。

考虑 with...as 加入到 python 中已经有二十年了,但是装饰器版本的 contextmanager 也就十年,异步安全的版本可能最近才实现。这里有个很现实的问题,作为使用者是更愿意手写 enter/exit 还是愿意手动管理?当 contextmanager 加入的时候,项目允许升级吗?当有异步、多线程需求的时候,开发者能写出正确的版本吗?

所以抛开美丑的主观看法不谈,defer 这种万金油几乎是个 Go 开发者就会写,而 with...as 使用率就要大打折扣。先让人用起来显然是重要地多的事情。这里不抬杠开发者水平高低的问题,defer 是一种兜底机制,当开发者不确定我应该在什么时候释放资源的时候就可以用,而 with...as 即便是装饰器的版本显得繁琐,写好几行肯定比不上写一个单词,这是人性。



之前的回复里我总是反复提,不要单独拿某个关键词来讨论特性,那就以 with...as 来彻底完整做个说明,证明一下为什么这样做不合适。

C/Python/Java 由于诞生较早,都是先有的实现,然后反过来总结出来的标准。比较新的语言,都是设计先于实现,终归会从之前语言中总结经验教训,所以不仅仅是 Go ,你会发现 C#/Rust/Kotlin 这些“现代”语言往往有些共性,共享的是新的设计理念。



这里 defer/with...as 反映的其实是对异常处理这件事的哲学思考不同。

传统语言中,一方面异常是不区分错误和 Panic 的,另一方面异常又与控制流强耦合,try...catch 实质是隐式的 goto 指令。

现代语言几乎都放弃了这样的思路,Panic 要立即终止,而错误是可以处理的。代码层面也尽量避免隐式跳转。这样的改变是大趋势,原因是大家都从 C 语言中汲取了足够的教训,隐式控制流是有害的( hidden control flow considered harmful ),特别是与 C 的宏机制一结合,人脑 debug 几乎变得不可能。

反过来,显式控制流的优点是显而易见的,一方面极大提高了代码的可读性以及可靠性,所见即所得,不存在那些写在其他地方的代码被意外调用执行的情况;另一方面对于并行编程的支持变得更加容易。

如果拿 Go 来举例可能没有说服力,我简单说下 Rust 和 Kotlin 。

- Rust 没有 exception ,只有 recoverable/unrecoverable 错误,类型分别为 Result<T, E> 和 panic! 只有后者是宏实现的跳转

- Kotlin 保留了 try 关键词,但 try 是表达式而非(控制流)语句,也就是说 try 可以返回(错误)值

在减轻开发者心智负担方面,新的设计哲学可谓是殊途同归。这个进步主要体现在工程化层面,当语言不区分错误和 Panic 的时候,开发者会主动选择最无脑的方式,也就是随意抛出集中处理。现代语言中砍掉了这样的机制,反过来倒逼开发者思考异常处理的本质,从而间接提高代码的可复用性,以及程序的可靠性。



回到 with...as 的问题上,Python 的错误处理还是传统的控制流模式。换句话说,就算语法糖写出花来,它的核心仍旧是基于 enter/exit 的对于 RAII 的模仿。由于 Python 没有 RAII ,这种模仿的结果就是一定要手动处理 scope 的问题。

先说一个比较接近最终方案的提案版本:

with EXPR:
____BLOCK

这个版本看上去相当美好,可是没多久就被枪毙了。原因有两个,一是 with 只能是 statement 而不能是表达式(需要同时支持 with VAR = EXPR: 这样的语法,VAR = EXPR 赋值本身也是合法的 EXPR );二是 EXPR 自身有可能再次抛出异常,或者包含 break/continue 等其他控制流指令,结果会导致 scope 失效。

第二条麻烦一下解释器也不是不可以,多维护一个基于栈的 trackback 机制。但第一条的影响是致命的:

with VAR = EXPR:
____BLOCK

想要对以上形式写装饰器,__exit__ 一定要在 VAR 上有定义,这就失去了装饰器省代码的意义。

为了不让 VAR 获得 EXPR 的赋值,最终实现的版本用 with EXPR [as VAR]: 这样的形式。相关实现细节 PEP 都能搜到,这里就不继续展开了。

即便是用上装饰器,代码大概是这样的:

@contextmanager
def open(file):
____f = open(file)
____try:
________yield f
____finally:
____f.close()

写这么多只是为了实现:可以在 with scope 里,不用单独写 f.close()。

装饰器的实现晚于 with...as 很久才进标准库,核心原因就是前面提到的隐式控制流。装饰器本身

客观地说,Python 本来就是试错型快速迭代偏好的语言,with...as 加上迭代器的语法糖的作用非常有限。它起到的主要作用是,当你知道什么时候该写 f.close() 的时候( scope 边界清晰),不用你再写了(自定义类型还是要写装饰器)。但是解决不了辅助开发者判断,什么时候该写的问题,你无法依赖解释器帮你兜底。



尽管在站队好或者不好的事情上,网友可能会支持你也可能会反对你,但不代表大家都是出于相同的推理逻辑。在语言设计这么关键的问题上,一半是妥协一半是取舍,单拿出来一个点做比较最终只会变成屁股之争。
securityCoding
208 天前
go 在鹅厂的语言份额极速上升中。。。当你维护过 c++
Hantong
208 天前
@securityCoding 毕竟写 c++ 的心智耗费蛮大的, 特别是所谓 modern c++.

贴个偶然间看到的 issuecomment 片段, 阐述了为什么 SRS 坚持使用 c++98, ref: https://github.com/ossrs/srs/issues/1191#issuecomment-917324341

```
No, I still firmly believe that if we can use C++98, we should definitely not use C++11. The more complex language features we use, the smaller the target audience of developers becomes. Even the senior brother finds C++98 too complicated, let alone C++11. I hope all C programmers can easily modify the code of SRS.
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1036972

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX