V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
wkong
V2EX  ›  程序员

个人觉得 Go 的 error 设计的非常好,为什么还那么多人吐槽?

  •  1
     
  •   wkong ·
    tangtaoit · 2023-12-22 10:40:03 +08:00 · 18976 次点击
    这是一个创建于 371 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一个程序是否健壮,主要判断是是否对异常有精准处理。

    像 Java 异常的处理虽然少写了代码,但是增加了未知性。

    Go 虽然多了一些代码,但是很容易写出健壮性的程序。孰轻孰重这不是很明显吗?

    156 条回复    2024-05-17 14:04:26 +08:00
    1  2  
    CrazyMonkeyV
        1
    CrazyMonkeyV  
       2023-12-22 10:45:03 +08:00   ❤️ 7
    你对 JAVA 的异常处理有误解。
    dongtingyue
        2
    dongtingyue  
       2023-12-22 10:45:59 +08:00   ❤️ 6
    javar 比较多他们习惯自己那一套,用其他语言就爱用原来的套到新的上面。
    wkong
        3
    wkong  
    OP
       2023-12-22 10:48:02 +08:00
    @CrazyMonkeyV 什么误解?我也做过几年的 Java 开发
    mars2023
        4
    mars2023  
       2023-12-22 10:48:21 +08:00
    Java 的异常,应该对比 go 的 Panic 吧。
    wkong
        5
    wkong  
    OP
       2023-12-22 10:49:12 +08:00
    @mars2023 不是,Java 的崩溃异常才能对应 Go 的 Panic
    Goooooos
        6
    Goooooos  
       2023-12-22 10:49:54 +08:00   ❤️ 1
    你说的都对
    Maboroshii
        7
    Maboroshii  
       2023-12-22 10:51:34 +08:00   ❤️ 2
    我比较喜欢 python 的异常,没有个 10 层缩进那叫写代码吗? doge
    Ayanokouji
        8
    Ayanokouji  
       2023-12-22 10:51:53 +08:00   ❤️ 21
    1. go 的 error 类型太弱,只能靠字符串判断
    2. 多层 error ,如果直接返回,溯源太难(原生 error 无调用栈),如果追加信息,就会有 1 的问题,是什么错误类型难判断
    3. 如果不介意多代码,java 完全可以全部是 checked exception ,自带类型和调用栈
    nagisaushio
        9
    nagisaushio  
       2023-12-22 10:52:53 +08:00   ❤️ 7
    因为本可以更好,rust 和 go 的异常原理是一样的,但 rust 的语法糖就很香。但凡 go 多个语法糖也不会这么多人吐槽
    pursuer
        10
    pursuer  
       2023-12-22 10:54:21 +08:00   ❤️ 3
    虽然不知道第几次看到类似讨论了...throw catch 是一种函数多级退出的在控制流上语法糖,go 里对应的是 panic ,Java 你写个 return Multivalue<Result,Error?>也不是不行
    cyp0633
        11
    cyp0633  
       2023-12-22 10:54:43 +08:00
    离 rust 就缺那么一点儿
    zihuyishi
        12
    zihuyishi  
       2023-12-22 10:56:08 +08:00
    其实这个错误处理很早的时候 windows 的 com 组件就是这么设计的,所有的 com 相关 c++接口都返回一个 HRESULT 需要处理,然后这个 HRESULT 在 c#和 vb 这种语言表现就是抛异常。包括后来 javascript 的 promise 也是差不多,最开始是.catch()处理,在 async 函数里就是 try catch
    golang 比较特别的其实在于你不好忽略一个 error ,不处理编译不通过,直接用'_'忽略 static check 也会抱怨,导致用户不得不处理这个错误,虽然有些烦,但是我认为是很好的设计。反倒是之前太多人错误处理都太草率了
    wkong
        13
    wkong  
    OP
       2023-12-22 10:56:45 +08:00
    @Ayanokouji

    1. try catch 判断比字符串判断高级些吗?越简单明了的设计反而是好的设计。

    2. 多层 error ,可以在最底层的 error 打个 log ,就知道来源了

    3. 不仅仅是多写代码问题
    musi
        14
    musi  
       2023-12-22 10:56:46 +08:00   ❤️ 4
    一个函数里面加了十几个 if 就是为了判断程序有没有错。。。
    xiangyuecn
        15
    xiangyuecn  
       2023-12-22 11:01:31 +08:00   ❤️ 1
    On Error Resume Next
    On Error GoTo 0

    @Maboroshii 那就不得不提上古时期的 vb 语法了,搁现在也是很优雅的,比 python 的强制缩进好看 100 倍 至少人家还有 end 明确收尾😅
    rrfeng
        16
    rrfeng  
       2023-12-22 11:02:48 +08:00 via Android
    感觉就缺个枚举类型
    如果有,可以把业务 error 都用枚举写出来,就不存在字符串判断的问题。
    MoYi123
        17
    MoYi123  
       2023-12-22 11:03:11 +08:00
    其实是认为 resp, err 这样的写法有 4 种情况
    1. nil, err
    2. nil, nil
    3. resp, err
    4. resp, nil

    而实际上只有 1 和 4 是比较标准的格式, 没有在语言层面上禁止 2,3 这 2 种写法, 虽然我觉得根本无所谓.
    Ayanokouji
        18
    Ayanokouji  
       2023-12-22 11:03:47 +08:00
    @wkong 你去看看多少代码 if err!=nil 直接 return 的,一个业务那么多 error ,怎么确定哪个是底层已经打印日志了。
    调用其他同事的方法,难道还要去看看他写的源码,error 返回的是啥,看看 fmt.errorf("")里边写的啥?
    cmdOptionKana
        19
    cmdOptionKana  
       2023-12-22 11:04:31 +08:00
    @musi 如果认为没必要判断处理,完全可以让它 panic ,并非全部错误都要特殊处理。语言是死的,人是活的。
    ho121
        20
    ho121  
       2023-12-22 11:05:13 +08:00 via Android
    有人写过 C 语言中的错误处理吗?
    emSaVya
        21
    emSaVya  
       2023-12-22 11:05:49 +08:00
    @wkong EAFP LBYL 两种风格罢了。有什么高下之分吗?

    而且 try exception 明显是更优雅的方案, 只不过会带来一些开销。
    littlewing
        22
    littlewing  
       2023-12-22 11:06:02 +08:00
    写 C/C++ 的都没说什么呢?
    大部分 C++ 工程代码是不会用 exception 那一套的
    mightybruce
        23
    mightybruce  
       2023-12-22 11:09:02 +08:00
    因为大多数这种人都是写 java 的,喜欢这种 try catch 异常处理, 对于非业务开发,懒散和滥用 try catch 处理可以导致致命的 bug ,
    linux 内核 和 c 那么多库都没有 try catch, 方式比 go 还原始也没啥人说什么,都是业务开发太闲了。
    go 的哲学就是 错误就是要显示检查处理的,异常处理的不可见错误检查所带来的问题其实是很大的。不少业务开发才不管那么多,还不是能跑就行,最多加个单元测试。
    nomagick
        24
    nomagick  
       2023-12-22 11:09:15 +08:00   ❤️ 4
    你对“设计”这个词是不是有什么误解...

    Golang 的异常处理只能说是没有设计,而不能说是设计得好或设计得不好
    Goooooos
        25
    Goooooos  
       2023-12-22 11:10:41 +08:00   ❤️ 2
    @mightybruce try catch 不是 java 特有,python ,c#,javascript 都有
    hedwi
        26
    hedwi  
       2023-12-22 11:10:47 +08:00
    你说得对 很多人其实并没有处理所有的可能出现的错误
    xiuxian
        27
    xiuxian  
       2023-12-22 11:12:25 +08:00
    @littlewing 我写 C++也基本不用,exception 使用起来是为了取代返回多个错误码,一个个的去判断。 实际上不能像 java 和 go 一样,全局捕获异常。该 carsh 还是得 carsh.
    xiuxian
        28
    xiuxian  
       2023-12-22 11:13:46 +08:00
    crash
    mightybruce
        29
    mightybruce  
       2023-12-22 11:14:47 +08:00
    另外 try catch exception 还涉及到 RAII 机制,go 连对象都没有,谈 try catch 在 go 里面是没什么意义的,最多也只是语法糖。
    CloveAndCurrant
        30
    CloveAndCurrant  
       2023-12-22 11:15:07 +08:00
    其实 go 的 error 相比 rust 的最大缺陷就是不能自动传导,rust 使用?就可以将 error 传导到函数最终结果,go 需要手动才能做到。
    joycelin
        31
    joycelin  
       2023-12-22 11:15:14 +08:00   ❤️ 4
    是这样的,以前也说不需要泛型,我看以后新的错误处理方式出来了你再怎么说
    gitrebase
        32
    gitrebase  
       2023-12-22 11:15:23 +08:00
    @Ayanokouji #18 写 Go 一般在最外层打日志吧,比如在 handler ;“if err!=nil 直接 return”在我的工作经历中,几乎不会有这样的代码,多少都会带点上下文
    CloveAndCurrant
        33
    CloveAndCurrant  
       2023-12-22 11:16:56 +08:00
    @CloveAndCurrant 除了 rust ,java ,Python 这些都可以做到将异常自动传导的
    chendy
        34
    chendy  
       2023-12-22 11:24:30 +08:00
    看具体工作内容吧
    要是写什么底层逻辑这套东西可能是好用的(因为我没写过)
    但是写业务弄这么复杂的错误处理逻辑,系统和开发必死一个
    mcfog
        35
    mcfog  
       2023-12-22 11:27:25 +08:00
    你看,即使在这楼里偏支持的声音,也有挺多并不太理解比较现代的那一版 errors.Is/As 新设计,还有如何结合 golang 偏鸭子类型的 interface 设计做复杂 error 交互的同学的。所以我是觉得 Golang 的 error 设计唯一的问题就是可能低估了大家学习一个和传统非常不一样(但是确实精妙)的设计这件事情的成本。

    也包括相关的 interface 设计,至少我感觉多数人都要教,而且教了以后也不像其他概念基本一次性掌握,得反复 review 。
    june4
        36
    june4  
       2023-12-22 11:27:34 +08:00
    一般程序中很少处理异常,都是默认往上抛,直到顶层处理,比如直接返回 5xx 错误,所以 java 这种就不干扰正常代码。

    少量函数天生大概率需要就近处理异常,这些函数你再用错误码也不迟,比如我最近看的 js 验证处理库每个函数都有二个版本,一个是 xxx()抛异常,一个是 safeXXX()无异常会 return 错误码,这不二全了吗
    mcfog
        37
    mcfog  
       2023-12-22 11:31:57 +08:00   ❤️ 1
    @june4 现在有了 generic 可以搞一个非常棒的 helper

    func Must[T any](v T, err error) T {
    if err != nil {
    panic(err)
    }
    return v
    }

    就是泛化的 https://pkg.go.dev/html/template#Must
    boboaiya3
        38
    boboaiya3  
       2023-12-22 11:33:39 +08:00
    @wkong #13
    boboaiya3
        39
    boboaiya3  
       2023-12-22 11:34:04 +08:00
    @wkong #13
    try catch 判断比字符串判断高级些吗?越简单明了的设计反而是好的设计
    zhanshen1614
        40
    zhanshen1614  
       2023-12-22 11:35:34 +08:00
    大多数人习惯 try catch 捕获异常肯定会觉得 go 的异常处理不方便。go 的异常处理比较麻烦但很详细可以知道是哪个步骤错误,阅读更清晰。try catch 捕获异常要考虑 exception 的从属关系,而且抛出异常也不一定知道是哪里执行错了还得看报错的文件、所在行,需要借助错误日志定位问题。
    boboaiya3
        41
    boboaiya3  
       2023-12-22 11:36:24 +08:00
    @wkong #13
    try catch 判断比字符串判断高级些吗?越简单明了的设计反而是好的设计?
    这个是错误,Java try catch 实现 是基于 c 里面的( non local jump ),这种一种特殊的函数跳转方式,肯定和普通函数执行不一样,性能层面,各方面都存在优势
    ShadowPower
        42
    ShadowPower  
       2023-12-22 11:36:56 +08:00
    可以看看 Rust 的 Result 设计,你会对错误处理有一个全新的认知……
    thinkershare
        43
    thinkershare  
       2023-12-22 11:38:13 +08:00   ❤️ 1
    go 这种异常设计,就是没有设计,最原始的 C 就是这种模样。go 的 整个设计是简陋而不是简洁,随着需求的增大,go 一定会变成和 C++一样难看。
    43n5Z6GyW39943pj
        44
    43n5Z6GyW39943pj  
       2023-12-22 11:39:56 +08:00
    Java 看日志的时候很狼狈,不说了 等下要被喷
    songray
        45
    songray  
       2023-12-22 11:42:20 +08:00   ❤️ 4
    你搞错了几件事.
    Java 、JS 、Python 完全可以做和 Go 一样的 err. 以 js 为例, 虽然没有多返回值, 但你每个方法返回 { err: boolean, value: any } 就行.
    err 的问题在于代码噪音太大, 进行多次相同操作, 唯一也是最好的办法反而是模拟一个蹩脚的 err monad, 参考 Rob 的文章 errors are values.
    这个问题 Go 是可以语言层面进行简化的, 但 go 一直没有这么做.
    更直白来说, 如果开发者一直在模拟 monad 或者 try catch , 那 go 官方为什么不做进 language level 呢?
    happyxhw101
        46
    happyxhw101  
       2023-12-22 11:42:35 +08:00
    楼主你 out 了, 不管你怎么想, 你要跟上时代潮流, 现在的潮流是 rust, 所以 ....
    bwangel
        47
    bwangel  
       2023-12-22 11:43:41 +08:00   ❤️ 3
    error 没有栈信息,配合 pkg.errors 的 WithStack 使用,增加了开发者的心智负担。

    例如以下调用链

    func api()

    func service()

    func rpc()

    api -> service -> rpc

    rpc 返回了一个错误,一层一层最终返回给了 api, api 拿到错误后,需要记录日志,它想知道错误是哪里来的,此时就需要栈信息。

    目前的解决办法,是 rpc 返回错误时,error 用 pkg.errors ( https://github.com/pkg/errors/blob/master/errors.go) 的 Wrap 包装一下,service 包装时,就不能用 wrap 了,需要用 WithMessage ,要不然会出现两份栈信息。

    这要求开发者对代码的层次结构非常清楚,哪些函数是最底层,哪些是上层。

    你想想,你刚接手了一个 5w 行的项目,读了三天代码之后开始写一些小 feature 。这时候你能了解清楚哪些函数是最底层吗?这样很容易就写错了,然后错误信息里面可能就有 N 份栈信息。
    mainjzb
        48
    mainjzb  
       2023-12-22 11:44:04 +08:00
    因为他们没写过 C/C++,没写过 windows api
    gam2046
        49
    gam2046  
       2023-12-22 11:44:23 +08:00
    go error 的设计,好是好,但是 error 本质上是个字符串,这玩意不太好。如果可以包含一个类型信息就会好很多。
    yazinnnn0
        50
    yazinnnn0  
       2023-12-22 11:46:05 +08:00
    因为现代语言大多可以用 monad 去处理这种问题 (比如 error, nil/null, future/promise)

    时髦一点的语言甚至内建了 monad comprehension 去处理 monad 地狱的问题

    但是 go 给的方案是 c 派的返回 errno 的方式, 也许 go 的哲学类似于 c, 但是 go boy 跳出来吹 err != nil 是令人困惑的
    Ayanokouji
        51
    Ayanokouji  
       2023-12-22 11:46:10 +08:00
    @gitrebase 最外层打印日志也有点问题,如果调用链太深或者封装的 error 太多,还是不方便直接看出错误源,error 不自带调用栈,我觉得这是最难接受的
    yazinnnn0
        52
    yazinnnn0  
       2023-12-22 11:55:06 +08:00
    rust 和 go 一样个毛....
    rust kotlin fsharp swift 都从 ocaml 里抄了不少设计, dart 未来也会向 ocaml 方向靠

    go 有一点 ocaml 的影子吗
    otakustay
        53
    otakustay  
       2023-12-22 12:03:57 +08:00
    go 这套显然和 shell 是一样的,有人喜欢 shell 的异常处理吗
    qianzanqi
        54
    qianzanqi  
       2023-12-22 12:07:02 +08:00 via Android
    @bwangel 如果 service 有两处调用 rpc 就还得依靠 message 内容来区分调用处了。

    我一直觉得 go 处理异常要么无脑 WithStack 这个就相当于 java 的一句一 try ;要么无脑 WithMessage 传个随机字符串,查调用栈只要在项目里搜索就行,还没有运行时的性能问题
    flyqie
        55
    flyqie  
       2023-12-22 12:13:03 +08:00 via Android
    go 写业务是折磨,错误处理这边啥玩法都有。。
    MegrezZhu
        56
    MegrezZhu  
       2023-12-22 12:14:00 +08:00
    要求函数迪调用方每次调用都显式地验证错误是否存在,又不提供语法糖才是问题的根源。
    事实上我还挺喜欢这种 error pattern 的,在我司的 C++代码里面大量存在着 StatusOr<RetType>的用法,但它和 Go 不一样的是 C++有宏啊,绝大多数场景下我都不需要写`if (result.ok()) {}`,直接用一些`RETURN_IF_ERROR(func())`, `ASSIGN_OR_RETURN(auto ret, func())`就行了,没有什么重复性的代码要写……
    me1onsoda
        57
    me1onsoda  
       2023-12-22 12:15:55 +08:00
    增加了未知性,这是编程规范层面能解决的。禁止 catch exception runtimeexception 这种模糊的异常。但 go 就很影响编程体验。。
    dacapoday
        58
    dacapoday  
       2023-12-22 12:44:51 +08:00   ❤️ 1
    @Ayanokouji 学艺不精,go 的 error 是接口,只是多数人 偷懒 或 习惯使用标准库的 字符串 实现。
    想要调用栈完全可以自己加,相关的 error 库也非常多。
    关键是 go 给了你选择,运行时收集调用栈是有开销的。
    开发一些偏底层的库时,调用栈信息又完全不够,error 接口 意味着可以自定义 数据结构 只收集关键信息。
    对于调用者,对 error 接口进行类型推断,又能很方便的滤出 是底层系统 error, 还是自定义 error struct ,还是中间各层业务封装的 字符串 error
    nothingistrue
        59
    nothingistrue  
       2023-12-22 12:52:47 +08:00
    学艺不精。参见
    @Ayanokouji #7
    @pursuer #9
    @songray #44
    buffzty
        60
    buffzty  
       2023-12-22 12:57:11 +08:00
    go 的错误处理跟 c 语言一模一样,没有一个人喷 c 但是喷 go 的挺多的
    再者说了 c 和 go 都有 try catch 模式,c 里面用 goto ,go 一般用 errgroup 也有可以用 goto
    Huelse
        61
    Huelse  
       2023-12-22 13:06:06 +08:00
    其实判断这个设计好不好很简单,看其他语言有没有类似的讨论即可。
    像 rust 就不会有这类讨论,当然 rust 有其他的问题,但至少在错误处理上是没什么可争议的,反观 go 争议就特别多。
    lujiaxing
        62
    lujiaxing  
       2023-12-22 13:09:53 +08:00   ❤️ 1
    我就这么说:
    在 Java 或者 ASP.NET Core 程序里, 你是可以做全局异常捕获的. 甚至如果底层框架做得好的话, 还可以把 throw Exception 作为中断请求的手段. 当请求数据不正常的情况下, 不需要你一层一层的从逻辑层 return 到展示层, 从子系统 A return 到子系统 B 然后再 return 到 api 层. 直接:


    ```
    def getById(id):
    if id is None:
    raise BusinessException("ID 都不给我你问你马呢???");

    ......
    ......
    ......
    ```


    就可以了, 这句话不需要程序员任何的处理就可以很优雅的返回给 GUI 层.
    如果是 go, 那就要一层一层的定义接收异常信息的参数. 然后像洋葱一样一层一层一层一层的去返回.

    你跟我说这种设计非常好??????????
    mightybruce
        63
    mightybruce  
       2023-12-22 13:12:57 +08:00   ❤️ 1
    @buffzty try catch 不是 goto 的封装,在 C 中实现 try catch 也不是一种方式,简单一点是 setjmp longjmp,
    很多代码是从汇编的方式实现的。
    try catch 这些和对象资源也是绑定的, 在 C 中实现的也是个模拟的语法糖,对象资源回收是不好实现的

    Rust ,它里面也是满屏幕的.unwrap(),这实际上跟 Go 的 if err != nil 一样
    写业务的还是太闲了, 天天讨论这些。
    TiaoYeTaiLang
        64
    TiaoYeTaiLang  
       2023-12-22 13:16:23 +08:00
    因为 golang 一开始就不是为了开发业务系统用的,而是用于开发中间件系统这种偏底层应用的,对于底层应用来说 error 无非就那么几种,要不计算异常、要不 IO 异常。
    cosiner
        65
    cosiner  
       2023-12-22 13:22:52 +08:00
    错误是 error, 对应 java 的 checked exception, 异常是 panic, 对应 unchecked exception

    go 的问题是 if err != nil 太频繁,因为大多数 go 程序都是 io 类型的,io 的 error 情况非常多,所以才会这样
    换成 compute 类型的,就没那么多 error 了
    go 就相当于 java 里面每一个 checked exception 都要手动 throw ,缺少一个语法糖

    至于错误处理都是一样的,go 里面不要用 _ 忽略错误,java 不要 try catch {}不处理
    jhdxr
        66
    jhdxr  
       2023-12-22 13:25:11 +08:00
    @wkong #13
    别人都说出来了你却还能理解偏,只能说明你太菜。
    重点不是 **try catch** 比 **字符串比较** 高级,而是 **异常类型** 比 **字符串** 高级。
    chenqh
        67
    chenqh  
       2023-12-22 13:32:43 +08:00   ❤️ 4
    @MorJS 不是啊,你不打印堆栈的话,java 的错误看起来不就是和 golang 一样了吗?但是你愿意看这种错误吗?没有堆栈的 err
    根本排查不了,golang 不就是这样,就凭一个 err 鬼知道到底是因为什么出错的,没有堆栈拿头排查
    serialt
        68
    serialt  
       2023-12-22 13:39:03 +08:00
    写一下 shell 你就会习惯 go 的错误处理
    bv
        69
    bv  
       2023-12-22 13:41:17 +08:00
    掌握流量密码,撕比确实能招来一大堆回复。
    pkoukk
        70
    pkoukk  
       2023-12-22 13:42:06 +08:00
    @Ayanokouji #8 try catch 和 error 不是一回事,try catch 对标的是 panic 。
    neoblackcap
        71
    neoblackcap  
       2023-12-22 13:44:08 +08:00
    @chenqh 也许他们都喜欢用 core dump 分析问题
    chenqh
        72
    chenqh  
       2023-12-22 13:45:35 +08:00   ❤️ 1
    @neoblackcap 可能用 golang 的都是大佬呗,不用堆栈,仅仅 print err 就可以知道到底是什么原因导致的错误,反正我不行
    lesismal
        73
    lesismal  
       2023-12-22 13:47:50 +08:00   ❤️ 2
    看名字就知道了,一个是错误处理,一个是异常处理。

    go 用 panic 照样能像 java 那样处理异常,但本来就是为了处理错误所以用 if error 怎么了?而且哪里有错就现场处理、代码的可读性更好。只是那些其他语言习惯了用异常的人一直不这样搞才会觉得别扭。你看写 c 的人有几个说 go 错误处理垃圾的?即使 cpp ,也少有项目把错误处理都搞成 java 那种用异常处理的方式

    反倒是 java 和一些其他语言,错误处理的场景和异常处理的场景,都用处理异常的方式来处理。习惯了自己的这种用异常处理去处理错误的方式,然后来鄙视 go 的该用啥用啥的鲁棒性,这帮人可真逗
    lesismal
        74
    lesismal  
       2023-12-22 13:49:11 +08:00   ❤️ 2
    go 这块的本质就是:
    用错误处理的方式处理错误
    用异常处理的方式处理异常

    java 那些:
    用异常处理的方式处理错误
    用异常处理的方式处理异常

    好奇你们哪里来的自信喷 go
    lxdlam
        75
    lxdlam  
       2023-12-22 13:53:30 +08:00   ❤️ 3
    单从两种范式上来说,值类型跟异常类型错误直接比较很难比较清楚:

    - 值类型还是将错误当作值,本身没有控制流操作,所以当作值处理可以轻松当作比如 data 的一部分到处传递处理等等,缺点就是缺乏强制力,简单把值 ignore 掉就可以丢掉错误,而大家都倾向于懒一点;
    - 异常类型可以打断并接管控制流,更加强大,guarantee 也更好,如果 runtime 实现得不错(比如 Java )那用起来其实还是很舒服的,缺点就是这个特性很容易被误用(相信大家都听说过或者见过用 exception 传值的业务代码)。

    两种范式各有优劣,其实没有直接关系,语言风格而已。

    而单纯讨论值类型错误,Go 的实现有自己的优势。错误这个东西本身就是很容易产生很多 concrete type 的,基于 duck typing 的 interface 化,其实大大简化很多操作,比如直接把已有 error wrap 起来,然后实现一下 `Error` 和一些 helper function ,就能快速搞一个新的错误出来,开发效率是很高的(没有 thiserror 和 anyhow 之前,Rust 搞个自定义错误有多痛苦,相信很多朋友深有体会)。
    Go 最大的问题其实在于,他采用了值类型错误,但是类型系统支持非常 basic ,导致大家处理错误非常痛苦。比如在类型系统比较现代的语言中,这种双类型语言或多或少能实现一定程度的 monad ,这样我们就可以写出类似于:
    ```
    let val = some_function().map(fun).map(foo).map(bar);

    match val {
    Ok(v) => println!("Value is: {}", v),
    Error(e) => println!("Failed! error: {}", e),
    }
    ```
    在这里面,我们我们确实不关心中间错误,直接 chain 起来,处理最后错误就行,而如果我们关心比如到某一步的错误,我们单独接一下这一步的结果就可以拿到中间结果再做处理。无论是看起来还是读起来,其实都非常清晰明了。而可惜的是,Go 因为一开始设计压根没想这个问题,导致现在的打补丁的 generics ,很长线看起来都很难支持这种比较完备的 monad 特性。所以,我们只能一步步 if-else ,或者将这三个函数塞到一个 slice 里面,用 `reflect` 循环每个函数 apply 去每一步拿一下 type 信息做一些非常丑的补丁。
    稍微补一句:为啥这里需要比较好的类型系统才能实现?在上面的流程中,实际上 `map` 接到的函数签名类型是各不一样的,有比较好类型系统的编译器在这里可以做非常好的静态推断;或者我们完全交给动态类型,损失一定 runtime 性能也能做到。而可惜的是,Go 既要选择尽量静态类型,又没把静态类型做很好,就卡在这了。
    lxdlam
        76
    lxdlam  
       2023-12-22 13:54:40 +08:00
    @lxdlam typo:这种双类型语言 => 这种支持双类型(甚至更多类型)组成异构容器的语言
    bthulu
        77
    bthulu  
       2023-12-22 14:04:26 +08:00   ❤️ 1
    @lesismal java 也可以用处理错误的方式处理错误的. java 没限制你的返回类型, 你可以返回一个 result<err, data>, 然后再处理就是.
    bronya0
        78
    bronya0  
       2023-12-22 14:11:03 +08:00
    如果一个编程语言的设计真的很好的话,为什么会有这么多人吐槽?为什么 java 、python 基本不吐槽这个?
    bronya0
        79
    bronya0  
       2023-12-22 14:12:32 +08:00
    go 刚出的时候,这些都是人人吐槽的垃圾设计,过了很多年出了 k8s 和 docker 后,就开始有人舔了
    buffzty
        80
    buffzty  
       2023-12-22 14:17:21 +08:00
    @mightybruce linux 源码都是用 goto 做的 try catch 。setjump 也可以做 goto 比较简单
    #include <stdio.h>

    typedef struct Ex
    {
    int code;
    char* msg;
    } _Ex;

    void exception()
    {
    _Ex* ex; // try {
    // xxx do something
    if (1) // throw
    {
    _Ex tmp_ex = {1, "xxx error"};
    ex = &tmp_ex;
    goto _handle_ex;
    }
    // xxxx do something
    goto _no_ex; // } end try
    _handle_ex: // catch {
    ex != NULL && printf("Exception: %d, %s\n", ex->code, ex->msg);
    _no_ex: // } end catch
    }

    int main(int argc, char* argv[])
    {
    exception();
    return 0;
    }
    somebody1
        81
    somebody1  
       2023-12-22 14:17:26 +08:00   ❤️ 4
    看完 13 楼的回答,我从眼睛到心灵都被震惊了!!!

    “1. try catch 判断比字符串判断高级些吗?越简单明了的设计反而是好的设计。”

    对,就是高级啊!!!!

    类型判断就是比字符串判断高级啊,这还用说嘛!!!全是字符串判断要累死人啊,但是类型判断很简答啊,而且更符合对业务的代码表达!!!!

    你写个一两百行的代码,当然字符串判断很简单。要是几万行呢,一个一个字符串判断???你的字符串手册都得搞个几百页吧!!!

    购物业务的库存不足异常,请用 A 字符串,库存锁定请用 B 字符串。
    订单的查询失败请用 C 字符串,无此订单请用 D 字符串。
    安全策略失败请用 D 字符串,安全策略未知异常请用 E 字符串。
    然后一直 2000 个字符串。

    你真的写过代码吗?一定要回答我这句话!!!
    yannxia
        82
    yannxia  
       2023-12-22 14:17:46 +08:00
    @lesismal 错误和异常也是主动区分出来的(无对错之分),对于 java 来说,只有 expection ,最多分 checked 和 unchecked 。Error 就是一个普通对象( GO 也是),这不过 Go 支持多返回值,大家就基于这个特性搞了这个用法,也可以自己封装个 Result 出来用。

    说到底看场景,
    - 大多数 Java 开发者面对的是业务系统,系统中一旦出现了 error ,大多数情况下是需要中断整个链路的,而且因为有 GC ,不用考虑 RAII 的问题,直接原地回到顶结束就好了。所以这个成了惯用法。
    - Go 的开发者在早期纯业务的很少,主要写一些 CNCF 那些中间件,考虑点和业务系统不一样,希望精细的掌控错误,现在这套就还也还行。

    立场主要看写啥,我拿 go 写业务系统的时候,我也挺讨厌 error 的,99%的时候除了往上抛,又能做什么呢。但是 java 写中间件的时候,用 checkedexpection 体验也不是挺好。
    xFrye
        83
    xFrye  
       2023-12-22 14:25:39 +08:00
    go 有挺多不错的地方,但为什么就偏偏去吹 error 的设计呢
    xausky
        84
    xausky  
       2023-12-22 14:26:44 +08:00
    @lujiaxing go 语言是让你有得选而 Java 很多时候没得选

    比如 Go 想要实现你发的框架统一处理

    ```
    if err != nil {
    panic(err)
    }
    ```
    结合有人说 Must Helper 更简单
    而有的东西比如就是可以处理的,Java 有时候就非要让你 try 比如
    ```
    Map<String, Object> result = new HashMap<>();
    try {
    result = new ObjectMapper().readValue("{}", new TypeReference<HashMap<String, Object>>(){});
    } catch (JsonProcessingException e) {
    // ignore
    }
    ```
    而 go
    ```
    var result map[string]interface{}
    _ = json.Unmarshal([]byte("{}"), &result)
    ```
    bronya0
        85
    bronya0  
       2023-12-22 14:27:57 +08:00
    go 写业务就是一坨答辩,不用怀疑,可以认为没有错误机制,很原始,当成增强的 c 用就行了
    thinkershare
        86
    thinkershare  
       2023-12-22 14:28:50 +08:00
    @yannxia 你这个算是客观的评价,所以我感觉 go 真的只适合做中间件,用来写业务,怎么都是难受,用 go 写过 2 个 web 项目后,感觉 sprint/asp.net core/Koa 写起来舒服得多。如果未来要有好的业务应用程序开发体验,只能和它最初的哲学原则越来越背离。
    yazinnnn0
        87
    yazinnnn0  
       2023-12-22 14:37:27 +08:00   ❤️ 2

    https://fsharpforfunandprofit.com/rop/

    Scott Wlaschin 做过 error handling 方面的 monad 科普

    https://fsharpforfunandprofit.com/rop/rop427.jpg
    lujiaxing
        88
    lujiaxing  
       2023-12-22 14:49:32 +08:00
    @xausky 问题人家 java 里出现异常是要求中断的. 无论如何后面都不能继续执行, 直接回到调用栈的最顶层. 我知道 go 可以全局 if err != nil. 问题是如果要求抛出异常之后中断后续全部逻辑, 阁下又当如何应对? 抛出的不是程序逻辑错误而是业务逻辑设计上的异常情况, 你不还是要一层一层的 if err != null 么?
    wyx119911
        89
    wyx119911  
       2023-12-22 14:52:00 +08:00
    @thinkershare #43 C/C++起码还有宏啊,并不难用也不难看
    lujiaxing
        90
    lujiaxing  
       2023-12-22 14:52:07 +08:00
    @xausky @yannxia 说的很明白了. go 写业务就是一坨屎. 业务越是复杂, 逻辑分支越是多, go 写起来越是恶心. 但是用来写一些很底层的中间件倒是很方便的.
    ShadowPower
        91
    ShadowPower  
       2023-12-22 14:58:27 +08:00
    @lxdlam #57
    我想到了很久以前看过的一篇“面向铁路编程”,里面的内容好像差不多……
    稍微找了一下
    https://fsharpforfunandprofit.com/rop/
    ZeroDu
        92
    ZeroDu  
       2023-12-22 15:04:19 +08:00
    @MorJS #44 看 golang 日志那才是费劲,js ,java ,python 这种一看就可到哪里有错误了
    lxdlam
        93
    lxdlam  
       2023-12-22 15:14:20 +08:00
    @ShadowPower 上面 #67 老哥也提到了,其实这个范式在函数式社区很常见,我们重点不关注你是不是所谓的“错误”,而关注类型本身,考虑用类型做 matcher 去配合值做 transformation 。这些年一些工业界的看起来很新的方法都来源于此,比如经典的 parser combinator ,如果你把 `Result<T>` 这个二元类型异构容器思考成 `Pair<&str /* remain input */, Result<T /* Token type */>>`,其实这里面的函数组合跟我上面说的 error monad 是一致的。更进一步来说,函数式的底层数学抽象 lambda calculus ,其实就是一种 combinatory logic ,天生以组合为主。在这种背景下,函数式语言采用这种思考是非常直接的。
    bronya0
        94
    bronya0  
       2023-12-22 15:20:05 +08:00
    Go 语言的 `error` 类型设计是一种简单而有效的方式来处理错误。它具有以下优点:

    * **简单易用:** `error` 类型是一个内置类型,不需要任何额外的库或框架。它可以很容易地与其他语言的错误处理机制集成。
    * **类型安全:** `error` 类型是一个接口类型,这意味着它可以表示任何实现了 `Error()` 方法的类型。这使得错误处理更加类型安全,因为编译器可以检查错误类型是否与预期的一致。
    * **可扩展性:** `error` 类型可以很容易地扩展,以支持自定义错误类型。例如,我们可以定义一个 `MyError` 类型,它包含额外的错误信息或上下文。

    然而,Go 语言的 `error` 类型设计也有一些缺点:

    * **缺乏详细的错误信息:** `error` 类型本身只包含一个错误消息。这使得调试错误变得困难,因为我们无法获得有关错误的更多信息。
    * **没有错误代码:** `error` 类型没有提供错误代码。这使得在不同系统之间共享错误信息变得困难,因为每个系统可能使用不同的错误代码。

    为了解决这些问题,我们可以使用一些第三方库或框架来增强 Go 语言的错误处理机制。例如,我们可以使用 `github.com/pkg/errors` 库来包装错误并添加额外的上下文信息。我们也可以使用 `github.com/go-kit/kit/log` 库来记录错误并添加额外的元数据。

    总体而言,Go 语言的 `error` 类型设计是一种简单而有效的方式来处理错误。它具有简单易用、类型安全和可扩展性的优点。然而,它也有一些缺点,例如缺乏详细的错误信息和错误代码。我们可以使用一些第三方库或框架来增强 Go 语言的错误处理机制,以解决这些问题。

    ## 改进建议

    以下是一些改进 Go 语言错误处理机制的建议:

    * **在 `error` 类型中添加错误代码:** 这将使在不同系统之间共享错误信息变得更加容易。
    * **提供一种标准的方式来记录错误:** 这将有助于确保错误信息以一致的方式记录下来,并包括所有相关元数据。
    * **鼓励使用自定义错误类型:** 这将使错误信息更加具体和有意义。
    * **提供一种标准的方式来包装错误:** 这将使我们可以轻松地将错误从一个系统传递到另一个系统,而不会丢失任何信息。

    通过这些改进,我们可以使 Go 语言的错误处理机制更加强大和灵活。
    lujiaxing
        95
    lujiaxing  
       2023-12-22 15:21:41 +08:00   ❤️ 1
    @bronya0 请不要贴 GPT 的回答.
    bronya0
        96
    bronya0  
       2023-12-22 15:22:32 +08:00   ❤️ 1
    Go 和 Java 两种语言的错误处理机制都有各自的优势和适用场景,而哪个更优秀主要取决于具体的需求和开发者的偏好。以下是对两者的简要比较:

    ### Go 的错误处理机制:

    1. **优势:**
    - **简洁和清晰:** Go 使用多返回值和接口类型的错误,代码看起来简洁而清晰,错误处理直接与函数调用关联。
    - **显式处理:** Go 鼓励显式地处理错误,开发者需要明确检查并处理每一个错误。
    - **错误值为接口:** 错误是一个接口类型,允许开发者自定义错误类型,提供更多的上下文信息。

    2. **劣势:**
    - **容易忽略错误:** 由于错误是常规的返回值,存在一定的可能性开发者会忽略错误。
    - **容易忽略错误:** 由于错误是常规的返回值,存在一定的可能性开发者会忽略错误。

    ### Java 的异常处理机制:

    1. **优势:**
    - **异常层次结构:** Java 引入了异常层次结构,允许开发者根据具体的异常类型进行处理,使得代码结构更加灵活。
    - **支持异常链:** 异常可以包含其他异常,形成异常链,提供更多上下文信息。
    - **Checked 和 Unchecked 异常:** Java 区分 Checked 和 Unchecked 异常,使得开发者能够有选择地捕获和处理异常。

    2. **劣势:**
    - **语法冗余:** Java 的异常处理语法相对冗长,有时需要使用大量的 try-catch 块。

    ### 总体评价:

    - **Go 适用于简单和清晰的代码结构,强调显式错误处理。适用于构建轻量级服务和工具。**

    - **Java 适用于大型企业级应用,具备更复杂的异常处理机制,支持更精细的错误处理。**

    在选择错误处理机制时,可以根据具体的项目需求和团队经验做出权衡。部分团队可能更喜欢 Go 的简洁性和明确性,而部分团队可能更倾向于 Java 提供的异常层次结构和更灵活的处理方式。
    allanpk716
        97
    allanpk716  
       2023-12-22 15:34:12 +08:00 via iPhone   ❤️ 1
    看了几个吐槽 go err 问题的,这玩意是简陋,但是你们好歹多用下,或者去看看最佳实践嘛…

    什么 err 是判断字符串的都出来了,那是你不用 errors.is 的问题吧

    喜欢用 go ,就是喜欢死板的处理所有的 err ,培训新人友好,你就把能处理的都处理就好了,从头培养编码要考虑鲁棒性的观念。

    非说 try catch 包裹代码优雅,没错,编码能力好的,确实优雅,看的舒服。问题是在我看到的项目中,绝大多数用法就是嫌弃的一比啊…越是水平一般的,越喜欢滥用 try catch (只代表我看到的项目和人),反正该判断的返回值都不管了,try catch 万能…
    Masoud2023
        98
    Masoud2023  
       2023-12-22 15:48:05 +08:00
    因为你 Java 模板代码写多了,所以你才觉得这种糟透了的模板代码没问题。

    你说“但是增加了未知性。”我只能默认你是不了解 Java 的工作细节。
    harry890829
        99
    harry890829  
       2023-12-22 15:50:00 +08:00
    @allanpk716 我也是看了看,感觉真正用的深的没几个,errors.is 这个我必须吐槽一下,这里有个 is ,但是要写入的时候一定要 fmt.Sprintf("%w",err),这个文档和 errors 包的文档不在一起,导致很多人是不知道,或者找不到的
    adoal
        100
    adoal  
       2023-12-22 15:52:55 +08:00
    golang 里的错误处理,从意图上来看,似乎跟某些语言里用 left/right 、result/error 的带值枚举类型来处理错误有相同的动机。但是人家用的是“和”类型,而它居然用的是“积”类型。
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4726 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 59ms · UTC 05:38 · PVG 13:38 · LAX 21:38 · JFK 00:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.