func foobar(a, b int) error {
handle err { return err } // This is our handler
x := check foo(a, b) // check invokes the handle func
y := check bar(a, b) // this on as well
}
看起来还蛮好的。
来源: https://levelup.gitconnected.com/golang-2-0-draft-feature-error-handling-c0a2332b9162
但我觉得还能改进,比如把 handle err { return err }
当作默认行为,不需要写这句就是这个行为,如果写了就覆盖默认行为。
1
yxwzaxns 2022-01-22 11:08:19 +08:00
再把 check 去掉就更好了
|
2
SuperMild OP |
3
liuxu 2022-01-22 11:32:00 +08:00 via Android
这个方式很 golang ,不错
|
4
aababc 2022-01-22 11:39:41 +08:00
rust: 快来抄作业呀
|
5
joesonw 2022-01-22 11:40:59 +08:00 via iPhone
这和老早的 try 的 proposal 不是一样的吗
|
7
kidonng 2022-01-22 11:43:55 +08:00 via Android
底下评论:
This version with check/handle is actually a rejected old draft which was reworked as try/defer, rejected again, and eventually gave the 1.13 error handling we now have. 珍惜 if (err != nil) 吧,这可是 Go 为数不多的 boilerplate 🐶 |
8
Buges 2022-01-22 11:48:38 +08:00 via Android
糟糕的设计。加了泛型以后 interface 已经支持 union type 了,弄个 Result 把 rust 的?抄过来就是。
|
9
okayan 2022-01-22 11:54:29 +08:00
这不是很早之前的 proposal 么
|
10
SuperMild OP @Buges
这个设计是兼容 Result 的,比如 ``` func foobar(a, b int) Result { handle err { return NewResult(err) } } ``` 如果彻底抛弃 error 改用 Result ,标准库以及第三方库就要全部重构了,显然是一个非常激进的决策,很难说比现在这个谨慎的决策更好吧。 后续如果社区越来越喜欢用 Result ,倒是再对 Result 提供特殊支持也不晚呀。 |
12
SuperMild OP @liuxu 可以暂时先留给第三方库去做,有了泛型之后自己包裹一个 Result 出来也行(想来应该会有人去做这个事,到时直接用就行)
|
13
Buges 2022-01-22 12:14:04 +08:00 via Android
@SuperMild 不需要全部重构,双返回值到 Result 一个函数就可以解决。
x:=check(foo(a,b))? |
14
iseki 2022-01-22 12:16:02 +08:00 via Android
又多出俩关键字来…能不能尽量用设计良好的语法和充分利用语法特性的标准库来实现,而不是想到什么就加点魔法…
|
15
ncepuzs 2022-01-22 12:22:03 +08:00
当初没有设计完善就草草发布 1.0 版本的后遗症,只能走一步看一步,缝缝补补
|
16
sagaxu 2022-01-22 12:36:08 +08:00
这么快就背上历史包袱了
|
17
kidlj 2022-01-22 12:36:22 +08:00 via iPhone 3
这是 2018 年的设计,早就被否了。
|
18
fengjianxinghun 2022-01-22 12:46:27 +08:00
@iseki 残废的类型系统导致很多东西没法用标准库扩展,只能开特权加魔法
|
19
iseki 2022-01-22 12:52:55 +08:00 via Android
@fengjianxinghun 诶…可 Go 好歹也算是个新语言啊,现在就这样了,以后可咋办啊…
|
20
SuperMild OP @iseki
@fengjianxinghun Go 1 稳定了十年,现在 Go 2 大版本升级才加少量关键词,应该很正常吧? Java 或 C# 之类的,大版本升级从未加过关键词吗?(而且,大版本升级加少量关键词在语言设计圈会被鄙视吗?感觉应该不至于是这样的风气才对。) |
22
SuperMild OP @iseki 原来如此,我刚才误会了。因为你说 “又多出俩关键字来”,后面又有人附和说 “只能开特权加魔法”,所以我误以为批判加关键词了。
至于 make ,确实不优雅,但 Go 的设计理念好像强调实用多过优雅,为了追求简化而放弃了很多东西,是一种选择,有得有失,我认为这个问题可以说喜欢或讨厌,却很难讨论对错。 |
23
dotmeow 2022-01-22 13:43:38 +08:00
这种设计看起来感觉还不如 unwrap 呢
|
24
Zwying 2022-01-22 13:45:58 +08:00
bad design
|
25
KousukeSakurako 2022-01-22 13:49:16 +08:00
if err != nil 写着也挺爽,简单粗暴,少记一个语法
|
27
darknoll 2022-01-22 14:09:31 +08:00
c/c++不也是用错误号吗,有啥不好的,我觉得现在错误处理就够用了
|
28
anonymous256 2022-01-22 16:44:10 +08:00 1
用 handler 比用 if 会更好吗? handler 里面不一样还是要用 if 写一堆判断?一个函数里面的内部需要返回不同的错误。
这个草案是不可能被通过的。 因为这违背了 Golang 的基本设计哲学:错误是值。 https://go.dev/blog/errors-are-values 不直接去返回那个(错误的)值,却放在 handler 里面去返回,不是在搞笑吗?这会让你同一逻辑的代码在空间上分离。比如一个函数 和它的错误处理在代码的空间上分离,这绝对更加不利于代码的可读性。if err != nil { ... } 只是丑陋了一点,但是可读性仍然是良好的。 golang ,永远不太可能有除了 if err != nil { ... } 之外的错误处理方案,因为它的设计哲学决定了这个。除非它改变设计哲学。像 Python 的做法是把错误当成异常,而不是值。 |
29
ZSeptember 2022-01-22 17:22:30 +08:00
和泛型一起提出来的方案。。
像 Rust 那样 ? 不好吗 为了不承认自己设计错误,搞的大家的代码看起来那么恶心,写起来也恶心。 真是无语了 |
30
Joker123456789 2022-01-22 17:35:22 +08:00
实在是想不通 try catch 到底 麻烦在哪了。
|
31
SuperMild OP @anonymous256
1. 返回值这一点没有变,新语法还是返回值。 2. 错误处理在代码的空间上分离,专门用一个 handleErr 函数来处理错误,非常常见,Go 的 web 框架 Echo 就是这样做的。 @ZSeptember 你的这个意见,上面已经有人提出了,而且我也回应过了,改成 ?,返回类型就变成了 Result ,几乎一切库都需要重构才能使用。目前这个方案已经可以非常方便地返回 Result ,后续可以看社区对 Result 的接受度再增加问号之类的语法糖支持。这是激进决策与谨慎决策的不同,只能说性格不一样,很难讨论对错。 |
32
ZSeptember 2022-01-22 17:51:54 +08:00
@SuperMild 可以兼容已有的库,只要最后一个返回值是 error ,就可以使用 ? 操作符。我知道社区早就讨论过这个方案了,也被拒绝了,在这里只是吐槽下,不能接受 Go Team 拒绝的理由而已。
|
33
SuperMild OP @Joker123456789
try catch 据说运行效率比较低。 在使用上,try catch 通常更方便,但 try catch 的思想是 “实在有需要的时候才处理错误”,因此通常会留很多错误让它崩。 而 Go 的方式,由于非常显性,提倡 “认真对待每一个 error”,因此按照 Go 的麻烦操作写出来的程序,通常会把可预见的 error 与不可预见的 panic 区分得非常清晰。 |
34
SuperMild OP @ZSeptember 这个只能少数服从多数,现在有了泛型,Result 的基本用法已经可以用第三方库来实现,也肯定有人会去做这个库,但是看大家爱不爱用吧,如果非常多人爱用 Result ,应该会有语法糖支持。Go 团队一向很保守。
|
35
Joker123456789 2022-01-22 18:02:43 +08:00 1
@SuperMild
嗯,刚才突然想明白了,go 的 error 就不能把他当异常处理,只能当做是一种 代码健壮性的手段,其实任何语言都有这种东西的,只不过没有专门提供一个 error 类型。 比如: js function test(param){ if (param == null){ return "参数不可以为空"; } } java public String test(String param){ if (param == null){ return "参数不可以为空"; } } 真正用来做异常处理的 应该是 panic + recover + 析构函数,他才是对标其他语言里 try catch 的东西。 |
36
ZSeptember 2022-01-22 18:19:33 +08:00 1
@Joker123456789 写分布式应用的情况下,每个步骤的错误都需要仔细处理的时候就会觉得 try catch 很麻烦。go 的这种错误处理在这种场景下用起来还不错,但是写业务的时候,大部分中间步骤不需要处理,直接抛出,当然 try catch 很爽。
Result + ? 操作符,这两种情况场景处理起来的感觉都不错。 Go 的 error 对应的是 Java 的 checked exception ,和 Rust 的 Result<T, Err> 本质是一样的,只是用起来方不方便而已; Go 的 panic 对应的是 Java 的 runtime exception |
37
sagaxu 2022-01-22 18:22:32 +08:00 1
一般有三种方式处理错误
1. 返回值中携带错误码,先检查错误码再决定是否使用返回值 2. 传入一个 code 指针,函数体中修改这个 code 3. try-catch-finally 一把梭 方案 1 ,每次调用都要创建一个包裹 code 的对象实例,多个对象多个开销,内存布局优化的好的语言不惧包裹对象的开销。Go 选择了多返回值,没有包裹对象,因此开销也不大。 方案 2 ,不支持指针的语言不合适,传一个对象进去,修改其 field ,对性能优化有更高的挑战。 方案 3 ,在 exception 没有实际发生的时候,try 中的代码性能损失很小,可能会影响一些优化,但整体可控。而一旦 exception 发生,性能下降几个数量级,所以只能作为异常使用,不能作为代码流程控制的手段。 方案 3 的优势是 exception 可以冒泡,一层层往上传递,比如 web 框架,可以在 handler 中统一 catch 和记录,特立独行的 go 把这个叫做 pannic-recover ,可能是个比较方的 try-catch-finally 的轮子。 |
38
SuperMild OP @ZSeptember 我写了个 check 函数,日常一些不需要特殊处理的 error 就直接抛出。
func check(err error) { if err { panic(err)} } 貌似不少人都会自己写个类似的函数来粗暴处理,尤其是个人小项目。(当然,被说丑的时候还是无法反驳) |
39
iseki 2022-01-22 18:44:28 +08:00 via Android
|
40
ZSeptember 2022-01-22 18:51:31 +08:00
|
41
SuperMild OP @ZSeptember 啊,对哦!等泛型来了我就用这招。
|
42
sagaxu 2022-01-22 21:19:37 +08:00
@iseki
举个例子,根据用户名查找用户帐号, 查无此人是正常逻辑,符合预期,一点也不 exception 。 某个订单里的用户 ID ,根据 ID 查找用户帐号,查无此人是 exception ,正常逻辑不应该走到这一步。 前面那个接口抛 UserNotFoundException 是滥用,后面那个合适。 |
43
iseki 2022-01-23 03:18:44 +08:00
@sagaxu 哈哈哈,我到觉得既然是查找,那没有就是正常的,返回空就 OK 了。不过如果那个函数叫“修改用户信息”,这时候报用户找不到异常就是很合理的了~~~
|
44
Hellert 2022-01-23 03:38:58 +08:00 via Android
x:=getx()?
还是问号更自然。 |