V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
SuperMild
V2EX  ›  Go 编程语言

据说 Go 2.0 的错误处理有可能是这个样子

  •  
  •   SuperMild ·
    ahui2016 · 2022-01-22 11:05:44 +08:00 · 5654 次点击
    这是一个创建于 818 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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 } 当作默认行为,不需要写这句就是这个行为,如果写了就覆盖默认行为。

    44 条回复    2022-01-23 03:38:58 +08:00
    yxwzaxns
        1
    yxwzaxns  
       2022-01-22 11:08:19 +08:00
    再把 check 去掉就更好了
    SuperMild
        2
    SuperMild  
    OP
       2022-01-22 11:12:32 +08:00   ❤️ 1
    @yxwzaxns 那就是风格问题,不是好坏的问题。

    一般应该在保持风格不变的前提下讨论,因为讨论风格很容易变成无意义的争吵。
    liuxu
        3
    liuxu  
       2022-01-22 11:32:00 +08:00 via Android
    这个方式很 golang ,不错
    aababc
        4
    aababc  
       2022-01-22 11:39:41 +08:00
    rust: 快来抄作业呀
    joesonw
        5
    joesonw  
       2022-01-22 11:40:59 +08:00 via iPhone
    这和老早的 try 的 proposal 不是一样的吗
    liuxu
        6
    liuxu  
       2022-01-22 11:42:17 +08:00 via Android
    @aababc 要是能把 rust 的?实现那是极好的,怎么写怎么舒心
    kidonng
        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 🐶
    Buges
        8
    Buges  
       2022-01-22 11:48:38 +08:00 via Android
    糟糕的设计。加了泛型以后 interface 已经支持 union type 了,弄个 Result 把 rust 的?抄过来就是。
    okayan
        9
    okayan  
       2022-01-22 11:54:29 +08:00
    这不是很早之前的 proposal 么
    SuperMild
        10
    SuperMild  
    OP
       2022-01-22 12:09:14 +08:00
    @Buges

    这个设计是兼容 Result 的,比如

    ```
    func foobar(a, b int) Result {
    handle err { return NewResult(err) }
    }
    ```

    如果彻底抛弃 error 改用 Result ,标准库以及第三方库就要全部重构了,显然是一个非常激进的决策,很难说比现在这个谨慎的决策更好吧。

    后续如果社区越来越喜欢用 Result ,倒是再对 Result 提供特殊支持也不晚呀。
    SuperMild
        11
    SuperMild  
    OP
       2022-01-22 12:11:08 +08:00
    @okayan 我之前没有专门去留意,今天看到 Medium Daily Digest 给我推荐才顺手点开看看。
    SuperMild
        12
    SuperMild  
    OP
       2022-01-22 12:13:00 +08:00
    @liuxu 可以暂时先留给第三方库去做,有了泛型之后自己包裹一个 Result 出来也行(想来应该会有人去做这个事,到时直接用就行)
    Buges
        13
    Buges  
       2022-01-22 12:14:04 +08:00 via Android
    @SuperMild 不需要全部重构,双返回值到 Result 一个函数就可以解决。
    x:=check(foo(a,b))?
    iseki
        14
    iseki  
       2022-01-22 12:16:02 +08:00 via Android
    又多出俩关键字来…能不能尽量用设计良好的语法和充分利用语法特性的标准库来实现,而不是想到什么就加点魔法…
    ncepuzs
        15
    ncepuzs  
       2022-01-22 12:22:03 +08:00
    当初没有设计完善就草草发布 1.0 版本的后遗症,只能走一步看一步,缝缝补补
    sagaxu
        16
    sagaxu  
       2022-01-22 12:36:08 +08:00
    这么快就背上历史包袱了
    kidlj
        17
    kidlj  
       2022-01-22 12:36:22 +08:00 via iPhone   ❤️ 3
    这是 2018 年的设计,早就被否了。
    fengjianxinghun
        18
    fengjianxinghun  
       2022-01-22 12:46:27 +08:00
    @iseki 残废的类型系统导致很多东西没法用标准库扩展,只能开特权加魔法
    iseki
        19
    iseki  
       2022-01-22 12:52:55 +08:00 via Android
    @fengjianxinghun 诶…可 Go 好歹也算是个新语言啊,现在就这样了,以后可咋办啊…
    SuperMild
        20
    SuperMild  
    OP
       2022-01-22 12:58:36 +08:00
    @iseki
    @fengjianxinghun

    Go 1 稳定了十年,现在 Go 2 大版本升级才加少量关键词,应该很正常吧?

    Java 或 C# 之类的,大版本升级从未加过关键词吗?(而且,大版本升级加少量关键词在语言设计圈会被鄙视吗?感觉应该不至于是这样的风气才对。)
    iseki
        21
    iseki  
       2022-01-22 13:02:44 +08:00 via Android
    @SuperMild 不是,只是感觉 go 里总是有很多奇怪的魔法,比如那个 make
    SuperMild
        22
    SuperMild  
    OP
       2022-01-22 13:24:28 +08:00
    @iseki 原来如此,我刚才误会了。因为你说 “又多出俩关键字来”,后面又有人附和说 “只能开特权加魔法”,所以我误以为批判加关键词了。

    至于 make ,确实不优雅,但 Go 的设计理念好像强调实用多过优雅,为了追求简化而放弃了很多东西,是一种选择,有得有失,我认为这个问题可以说喜欢或讨厌,却很难讨论对错。
    dotmeow
        23
    dotmeow  
       2022-01-22 13:43:38 +08:00
    这种设计看起来感觉还不如 unwrap 呢
    Zwying
        24
    Zwying  
       2022-01-22 13:45:58 +08:00
    bad design
    KousukeSakurako
        25
    KousukeSakurako  
       2022-01-22 13:49:16 +08:00
    if err != nil 写着也挺爽,简单粗暴,少记一个语法
    SuperMild
        26
    SuperMild  
    OP
       2022-01-22 13:53:36 +08:00
    @dotmeow 是指 error 的 unwrap 方法吗?这个完全不影响 unwrap 的呀。
    darknoll
        27
    darknoll  
       2022-01-22 14:09:31 +08:00
    c/c++不也是用错误号吗,有啥不好的,我觉得现在错误处理就够用了
    anonymous256
        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 的做法是把错误当成异常,而不是值。
    ZSeptember
        29
    ZSeptember  
       2022-01-22 17:22:30 +08:00
    和泛型一起提出来的方案。。
    像 Rust 那样 ? 不好吗
    为了不承认自己设计错误,搞的大家的代码看起来那么恶心,写起来也恶心。
    真是无语了
    Joker123456789
        30
    Joker123456789  
       2022-01-22 17:35:22 +08:00
    实在是想不通 try catch 到底 麻烦在哪了。
    SuperMild
        31
    SuperMild  
    OP
       2022-01-22 17:44:55 +08:00
    @anonymous256

    1. 返回值这一点没有变,新语法还是返回值。
    2. 错误处理在代码的空间上分离,专门用一个 handleErr 函数来处理错误,非常常见,Go 的 web 框架 Echo 就是这样做的。


    @ZSeptember 你的这个意见,上面已经有人提出了,而且我也回应过了,改成 ?,返回类型就变成了 Result ,几乎一切库都需要重构才能使用。目前这个方案已经可以非常方便地返回 Result ,后续可以看社区对 Result 的接受度再增加问号之类的语法糖支持。这是激进决策与谨慎决策的不同,只能说性格不一样,很难讨论对错。
    ZSeptember
        32
    ZSeptember  
       2022-01-22 17:51:54 +08:00
    @SuperMild 可以兼容已有的库,只要最后一个返回值是 error ,就可以使用 ? 操作符。我知道社区早就讨论过这个方案了,也被拒绝了,在这里只是吐槽下,不能接受 Go Team 拒绝的理由而已。
    SuperMild
        33
    SuperMild  
    OP
       2022-01-22 17:53:39 +08:00
    @Joker123456789

    try catch 据说运行效率比较低。

    在使用上,try catch 通常更方便,但 try catch 的思想是 “实在有需要的时候才处理错误”,因此通常会留很多错误让它崩。

    而 Go 的方式,由于非常显性,提倡 “认真对待每一个 error”,因此按照 Go 的麻烦操作写出来的程序,通常会把可预见的 error 与不可预见的 panic 区分得非常清晰。
    SuperMild
        34
    SuperMild  
    OP
       2022-01-22 17:57:53 +08:00
    @ZSeptember 这个只能少数服从多数,现在有了泛型,Result 的基本用法已经可以用第三方库来实现,也肯定有人会去做这个库,但是看大家爱不爱用吧,如果非常多人爱用 Result ,应该会有语法糖支持。Go 团队一向很保守。
    Joker123456789
        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 的东西。
    ZSeptember
        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
    sagaxu
        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 的轮子。
    SuperMild
        38
    SuperMild  
    OP
       2022-01-22 18:26:51 +08:00
    @ZSeptember 我写了个 check 函数,日常一些不需要特殊处理的 error 就直接抛出。

    func check(err error) { if err { panic(err)} }

    貌似不少人都会自己写个类似的函数来粗暴处理,尤其是个人小项目。(当然,被说丑的时候还是无法反驳)
    iseki
        39
    iseki  
       2022-01-22 18:44:28 +08:00 via Android
    @SuperMild 很难说 Go 这种实用主义是错的,就个人喜好上确实不太喜欢。不知道这样的实用主义会不会给后面带来麻烦呢

    @sagaxu 其实一直很好奇如何把异常当作常规控制流使用,比如递归时用一个 throw 结束递归?
    ZSeptember
        40
    ZSeptember  
       2022-01-22 18:51:31 +08:00
    @SuperMild 有了泛型更好玩一点

    SuperMild
        41
    SuperMild  
    OP
       2022-01-22 19:06:49 +08:00
    @ZSeptember 啊,对哦!等泛型来了我就用这招。
    sagaxu
        42
    sagaxu  
       2022-01-22 21:19:37 +08:00
    @iseki

    举个例子,根据用户名查找用户帐号, 查无此人是正常逻辑,符合预期,一点也不 exception 。
    某个订单里的用户 ID ,根据 ID 查找用户帐号,查无此人是 exception ,正常逻辑不应该走到这一步。

    前面那个接口抛 UserNotFoundException 是滥用,后面那个合适。
    iseki
        43
    iseki  
       2022-01-23 03:18:44 +08:00
    @sagaxu 哈哈哈,我到觉得既然是查找,那没有就是正常的,返回空就 OK 了。不过如果那个函数叫“修改用户信息”,这时候报用户找不到异常就是很合理的了~~~
    Hellert
        44
    Hellert  
       2022-01-23 03:38:58 +08:00 via Android
    x:=getx()?

    还是问号更自然。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1146 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 18:24 · PVG 02:24 · LAX 11:24 · JFK 14:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.