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

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

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

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

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

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

    156 条回复    2024-05-17 14:04:26 +08:00
    1  2  
    adoal
        101
    adoal  
       2023-12-22 15:58:04 +08:00
    居然用 tuple……虽然实际实现不会有人写出左右都是 nil 或者都是 value 的情况,但……这种严肃的需要,难道不应该用和类型从语言层面就保证吗?
    lsk569937453
        102
    lsk569937453  
       2023-12-22 16:05:21 +08:00
    @mightybruce 库里写 unwrap 就是灾难,还要捕获 panic
    nebkad
        103
    nebkad  
       2023-12-22 16:10:44 +08:00
    调侃:因为你和那些人不是一个档次。(对,你的高)

    你想听到的答案:因为口味问题。

    真正的原因:因为 go 的 err 强迫你写大量重复且相似的代码,并且这些代码完全可以用更好的模式来取代。
    mxT52CRuqR6o5
        104
    mxT52CRuqR6o5  
       2023-12-22 16:16:52 +08:00
    如果简单就是好的话,那我觉得最好的编程语言非 brainfuck 莫属了,其他语言整那么多特性有用吗?简单才是最好的,brainfuck 是最好的编程语言
    CodingIran
        105
    CodingIran  
       2023-12-22 16:18:01 +08:00
    @rrfeng #16 可以参照 Swift 的 Error ,就是一个协议,只要遵循这个协议,类、结构图、枚举都可以作为 Error 被 try catch 到
    kuanat
        106
    kuanat  
       2023-12-22 17:20:51 +08:00   ❤️ 9
    我觉得单独拿出来一个语法细节做比较是没有意义的,异常处理的模式要放到语言设计层面去考虑。没有什么优劣之分,更多是一种取舍。

    在我看来,Go 的整体设计思想是降低开发者的认知负担。前段时间 V2EX 就有个帖子,讨论并发模型里的内存可见性。对于 Go 开发来说,不知道内存可见性这个概念一点不影响写出正确的多线程代码,因为这个模型非常符合直觉。但是对于写 C 的来说,不了解这样的抽象就会踩很多坑。

    但是做类似的抽象是有代价的,Go 的主要思路是“协作”(这个概念是我自创的),就是在无关紧要的地方都做一点小妥协,换取整体的简洁。比如 goroutine 就是这样,没有机制让你主动 kill 一个 goroutine ,但是可以提前沟通一个 context 机制,gouroutine 可以在获知意图的时候主动退出。

    回到错误和异常处理的话题上,Go 选择了简化控制流,从机制上减少因为错误处理又引入新的错误的风险。代价是,异常处理流程的参与者都要主动确认自己的职责。本质上 if err!=nil 不是异常处理,而是判断这个事情要不要我来处理。这样设计的前提是 Go 从思想层面重新定义了“错误”和“异常”,能恢复的都叫错误,不能恢复的都交给 panic/recover ,楼上也有不少人提到了,Rust 的设计思路也是这样,这里就不展开讨论了。

    再说一下语法层面的事情。前两天还有帖子讨论为什么 Go 直到 1.21 才提供 Min/Max 的标准实现。当一门语言的设计目标包含了简洁、高效和向后兼容的时候,增加新特性就变成了需要慎重考虑的事情,泛型这个事情 Go 讨论了接近十年。设计者知道 Min/Max 是个常见需求,泛型也是很必要的特性,在没有标准库实现之前,还是通过 math 包提供了浮点类型的 Min/Max 功能。其他类型需要开发者自己写,毕竟浮点比较很容易出错,但其他类型几乎写不错的。还是那个“协作”的思想,在可以接受的地方做些妥协,是为了整体更高效。

    所以说到底这是个取舍的问题,Go 自始至终就是忠于“总体简洁”这个思路的,不够理想的错误处理就是代价。一些看似简单的语法变动,在编译器层面就是极其复杂的问题。Rust 内存安全,但是开发者要理解复杂的编程范式,编译器又慢又复杂。Java 写起来语法糖够多,虚拟机也自由,代价就是效率不够高。Go 相对平衡,开发者和编译器各自妥协一下,花一点小代价为对方解决一些棘手的问题。在这个事情上,不能既要又要,所以我才说单独比较错误处理这个点没有意义,一定要放到语言设计的整体层面上来讨论。

    关于一些改善错误处理的思路:

    1. 为 error 设计额外的类型

    这个方案其实有过非常多的讨论,包括 Go 没有 enum 类型说到底是同一个问题。核心原因还是增加复杂的类型和 Go 的设计思路不符,而且实现起来和接口冲突,所以放弃了几乎所有非算术类型的支持。对于 error 类型的需求建议用接口和类型断言来实现。代价是类型断言是运行时特性,不如编译时实现安全。

    2. 函数式 monad

    除非换一种语言,几乎没有可能从底层支持函数式编程范式。另一个思路是基于泛型来模拟,这个方案只能说有希望,进标准库估计要讨论的东西很多。

    3. Go 现在推荐的思路

    就因为错误处理这个事,Go 很长时间标准库里都没有结构化日志。和基于泛型的 cmp 一样,slog 也是 1.21 才进的标准库。

    对于错误类型的定义,用接口和类型断言。像 call stack 这类信息,从 runtime 取回来按照需求用接口封装一下。
    对于错落处理的传递,用自定义 Logger 或者 Context 。比如 tracing 这类信息追加一下。

    这里额外提一句,Context 就是非常好的“协作”机制,调用方的举手之劳,整个调用链都受益的。让被调用方绞尽脑汁汇报……只能说一将无能累死三军。

    最后做个总结,不要脱离语言背景谈语法细节。用一门语言就学习它的思想,然后把精髓用在最合适的场景里。
    HyperionX
        107
    HyperionX  
       2023-12-22 17:32:04 +08:00
    吐槽没堆栈信息的是都不用 trace_id 吧。不用 go 的生态只单用 go 语言,有不契合当前熟练语言的生态东西和思想,引发很多吐槽确实很常见
    timnottom
        108
    timnottom  
       2023-12-22 17:56:20 +08:00
    @harry890829 #99 新版有:errors.Join
    harry890829
        109
    harry890829  
       2023-12-22 18:10:41 +08:00
    @timnottom 这个我没主意,我去看看
    cI137
        110
    cI137  
       2023-12-22 20:50:21 +08:00 via iPhone
    @Ayanokouji
    @gam2046 error 是接口,可以自己实现 error ,自己加类型
    netabare
        111
    netabare  
       2023-12-22 21:13:26 +08:00 via Android
    这讨论让我想起了两篇很典的文章:

    https://hugotunius.se/2020/05/16/making-invalid-state-unrepresentable.html

    https://thedailywtf.com/articles/functionally-null

    简单说就是在没有 monad 的语言里重新发明了 monad ,而且还是劣化难用的版本。
    james122333
        112
    james122333  
       2023-12-22 21:28:23 +08:00 via Android
    @otakustay

    不一样 shell 的信号处理不是 panic
    shell 有更方便的就是回传状态 0 成功 1 以上错误 当然这样回传值就没了
    可以 hack 就是
    cI137
        113
    cI137  
       2023-12-22 21:29:04 +08:00 via iPhone
    @somebody1 是用错了,error 是接口类型,不是字符串,你可以加自己的类型。
    james122333
        114
    james122333  
       2023-12-22 21:31:09 +08:00 via Android
    因此流程控制对 shell 来讲是其优点
    james122333
        115
    james122333  
       2023-12-22 21:45:57 +08:00 via Android
    @otakustay

    go 也没办法&&和||判断状态 因为回传 value
    blankmiss
        116
    blankmiss  
       2023-12-22 22:36:51 +08:00   ❤️ 1
    @Livid #96 #94 GPT 回复
    fgwmlhdkkkw
        117
    fgwmlhdkkkw  
       2023-12-22 23:25:21 +08:00
    golang 还有一个更严重的问题是反射依赖 tag ,而 tag 又只是字符串,……奇丑无比
    aristotll
        118
    aristotll  
       2023-12-22 23:30:57 +08:00
    @mcfog #35 其实就是 lo.Must https://github.com/samber/lo#must 。 活捉 mc 😁
    junkun
        119
    junkun  
       2023-12-23 00:03:00 +08:00
    @CloveAndCurrant rust 的 error 是支持 traceback 的
    FightPig
        120
    FightPig  
       2023-12-23 08:41:16 +08:00
    go 要简单化,不需要泛型,然后过了多少年打脸了,go 不需要复杂的 err 处理,if err != nil 就够了,我看后面要出了的话,用不用吧
    cmdOptionKana
        121
    cmdOptionKana  
       2023-12-23 09:19:55 +08:00
    @FightPig @joycelin

    不要为了自己的偏见去造谣或传谣。

    Go 官方从来没说过不需要泛型,一直都是说优先级比较低,并且很早就在官方博客说了有计划支持泛型。
    xuanbg
        122
    xuanbg  
       2023-12-23 09:53:31 +08:00
    无论是错误还是异常,基本上就没有什么值得处理的。能够发生之后打印出发生的点、发生的原因和正在处理的数据就足够了。go 这种强迫你处理的方式,就非常非常不优雅,总有被强奸的感觉。
    tyrantZhao
        123
    tyrantZhao  
       2023-12-23 12:35:54 +08:00 via iPhone
    其实,这个完全够用,吐槽大概是因为跟潮流。
    javaisthebest
        124
    javaisthebest  
       2023-12-23 13:31:03 +08:00
    一坨屎一样的玩意

    官方的原生 erorr 不支持异常栈打印 你说做得好?

    go 吹除了中国吹 还有哪个国家在吹?

    连大本营 谷歌自己宁愿捧 flutter 都不愿意捧 go
    iseki
        125
    iseki  
       2023-12-23 14:43:02 +08:00 via Android
    Go 的问题主要是消极路径的代码在毫无收益的情况下污染了积极路径的代码,而且因为处理是完全手动的,所以很容易出现人为错误(忘了 if 忘了 return 什么的)。这个问题不要用可以加 linter 来辩解,语言本身设计缺陷需要加一堆其他东西就已经是个问题了,说 Go 做的好的,看看 Zig 和 Rust 这么做的吧。
    iseki
        126
    iseki  
       2023-12-23 14:46:31 +08:00 via Android
    Java 的 try catch 和受检异常怎么说呢想法是好的,但标准库中的类型划分个人认为有待提高,太多该被归于 Error 的东西被归类到 Exception 中了。
    iseki
        127
    iseki  
       2023-12-23 14:50:26 +08:00 via Android
    Go 中 goroutine 未捕获 panic 导致整个程序退出这是在这个整体设计下的一个必然,Java 的 Thread 不这么设计,它并不鼓励程序甚至是业务代码中动不动就开一个 Thread ; Kotlin 的 coroutine 不这么设计,是因为人家有结构化并发,君不见 Java 绿色线程出来后马上也在讨论结构化并发的问题了。
    Go 这边不搞这个,那如果 goroutine 在发生例外时不 crash 整个程序,可能就是个维护灾难了。
    lesismal
        128
    lesismal  
       2023-12-23 20:16:58 +08:00
    > java 也可以用处理错误的方式处理错误的. java 没限制你的返回类型, 你可以返回一个 result<err, data>, 然后再处理就是.

    @bthulu 我也没说 java 不能,我的意思是 java 非要用异常处理的方式去处理错误,社区这样用习惯了就继续习惯用就是了,但是拿这个来喷别的语言不这样做,就有点自以为是了,而且 go 也不是不能那样做、稍微封装下就可以了。
    lesismal
        129
    lesismal  
       2023-12-23 20:22:35 +08:00
    @yannxia #82

    或许 panic 的时候自动包装一层 stack 或许就没人这么喷 go 了,毕竟小白们好些人只看标准库没有就一顿喷,好像即使额外封装下(虽然封装也很简单)也实现不了他们说的这些似的。
    众多 CURDer 在某些垃圾语言的舒适区里呆的久了,但凡让他们自己亲自去上厕所、他们都嫌累,殊不知自己亲自上厕所可以舒筋活血。

    go 的标准库定位简单点挺好的
    lesismal
        130
    lesismal  
       2023-12-23 20:28:54 +08:00
    @iseki
    其他语言里但凡哪个语言的协程或者绿色线程之类的能像 erlang 或者 golang 这么方便的,再对比也行。我暂时还没看到一个其他语言有 erlang 、golang 这么并发友好的

    panic 导致整个程序退出这也能算问题吗,入门级的新手 go func 不 defer recover 那是写着玩,但凡正规点的公司稍微封装下框架脚手架都不至于,如果是用协程池也通常自带了一层 recover wrap ,或者知名 web 框架那些 recover 、logger 都是最基础的中间件、通常都要加的

    所以我建议,摒弃那种“但凡标准库没有就值得一喷”的思想,做工程可没人强制大伙只能使用标准库并且不要做任何封装。如果是那样要求,那也请先把 java 社区的各种轮子都拿掉再来对比,看看裸奔的时候谁更优雅,但这显然毫无意义
    xiaocaiji111
        131
    xiaocaiji111  
       2023-12-23 21:10:05 +08:00
    出发点是好的,每个 error 都要检查,实际上等于没设计,因为是靠返回值实现的,其他单返回值语言,也可以自己定一个 Wrap 里面包裹数据和结果。

    golang 的 error 配合语言特性,也就是 err 必须手动处理,确实有助于提升程序健壮性,写的时候让用户必须考虑成功和失败。但是读的时候却不那么友好,大量的 if err 就像庄稼地里的杂草。去除杂草才是从上到下的真正业务逻辑,这个时候想快速了解逻辑,第一步干啥,第二步干啥,一般是不关注 err 的。

    try catch 和 error 没必要说谁好谁坏,都有痛点和爽点。语言怎么设计的,怎么用就行,毕竟语言就是让计算机干活认为定义的规则。
    xiaocaiji111
        132
    xiaocaiji111  
       2023-12-23 21:12:02 +08:00
    @xiaocaiji111 人为定义的规则。
    擦,发出去就没法修改了
    iseki
        133
    iseki  
       2023-12-23 21:14:13 +08:00 via Android
    @lesismal 我似乎并没有鄙视 Go 这种 panic 即退出的设计,我只是说下这个设计在没有结构化并发时几乎是必然的,不知道你在激动什么……

    Go 的 goroutine 本身设计我不认为太大问题,但它的暴露形式就值得商榷了。难以直接被使用的 "go" 变成了关键字,errgroup 这种东西反而变成了 x 库。
    “标准库没有就值得一喷”,标准库什么都半残那还叫什么标准库?我也没说标准库必须要带一套结构化并发,毕竟 go 出现的相对来说还是比较早的,但是 go 关键字的存在,除了更好打广告外,几乎看不到什么必要性。
    iseki
        134
    iseki  
       2023-12-23 21:18:13 +08:00 via Android
    @lesismal 事实上如果 Java 在绿色线程出现之后不能及时安排好结构化并发,我敢说 unhandled exception 可以被静默忽略的特性一定会成为灾难。
    lesismal
        135
    lesismal  
       2023-12-23 22:11:58 +08:00
    @iseki
    > 我似乎并没有鄙视 Go 这种 panic 即退出的设计,我只是说下这个设计在没有结构化并发时几乎是必然的,不知道你在激动什么……

    可能是顺着你前面几层楼看下来,感觉整体上是个对 go 不屑或者其他非积极的语气。有些词严格考究那确实算是中性,但是组合到一块就会让人觉得是在说“g 不咋地、也就那样、很多缺陷”的样子。

    > Go 的 goroutine 本身设计我不认为太大问题,但它的暴露形式就值得商榷了。难以直接被使用的 "go" 变成了关键字

    我没 get 到 "go 难以直接被使用" 的点在哪里,当年就是看到了"go"、"chan"这两个大杀器我才义无反顾开始写写 go ,写了一些后,就再也不想碰 c/cpp 和其他一些脚本了,更不要提 java 这种臃肿的丑八怪

    > errgroup 这种东西反而变成了 x 库。

    标准库的严格新增导致了特性的缓慢,但这也恰恰把很多垃圾挡在了标准库之外,各有利弊吧。而且这也不是什么难度很大的必须由标准库提供才行的功能,所以倒还好。
    至少,相比于 node module 的肆意泛滥,go 社区不至于这么乱七八糟

    > “标准库没有就值得一喷”,标准库什么都半残那还叫什么标准库?我也没说标准库必须要带一套结构化并发,毕竟 go 出现的相对来说还是比较早的,但是 go 关键字的存在,除了更好打广告外,几乎看不到什么必要性。

    我不知道什么是结构化并发,我觉得多进程多线程以及语言及协程这些,加上进程间通信线程同步或者其他各种用于并发的机制,这些是大厦的基础。从 c 到各种语言,这些程序架构/结构设计,本质上都是这些东西在搭积木。
    本来我自己对这些基础的东西比较熟,设计个架构、结构大概都能了然于胸,然而当我刚刚搜了下结构化并发,看得我更懵逼了,又是一堆概念机制需要学习,然而并没有让我觉得变得方便了。golang 的 go 、chan 两个关键字可以弄各种组合,时序的一致性的东西也都能保障,即使退回到 c/cpp ,多进程多线程、锁、各种 IPC 、以及 socket 各种也是类似的足够实现业务所需。c/cpp 这些老牌强者主要的劣势是开发效率,go 牺牲性能提高开发效率做的已经非常好了,go+chan 的组合,足以让我省去很多不必要的麻烦。

    > 事实上如果 Java 在绿色线程出现之后不能及时安排好结构化并发,我敢说 unhandled exception 可以被静默忽略的特性一定会成为灾难。

    这些非系统线程的语言级协程方案,都需要语言级上支持各种系统调用之类的接口与语言 runtime 调度的结合,否则任意一个调用可能直接阻塞住一个系统线程的行为都会造成大量的 cpu 闲置,如果各个线程都被这种阻塞占了,系统效率可就太低下了。我不知道 java 绿色线程出现后有没有解决这个问题,如果也像 go 一样都支持了,那还有得对比,否则仍然是不适合普及,否则估计很大比例的开发者分辨不出来哪里可能会导致问题。
    然而即使支持了又如何,java 的臃肿、吃内存,就足以让人生厌。
    lesismal
        136
    lesismal  
       2023-12-23 22:20:29 +08:00
    在 go 之前,erlang 的”进程“就已经存在多年,它的并发友好是真的挺不错,但主要还是在连接之间、模块之间不需要复杂交互的场景下更简单易用。涉及到复杂内部交互的,我觉得 actor 也并不算是友好。至于函数式更不知道说什么好,喜欢它的人也基本上都不怎么关心性能,而且类似“代码量少就是美”的这种审美看上去很好,代码少的前提式隐藏了大量细节,几种不同的简单写法之间就可能存在由于拷贝带来的很大的性能差异,所以美倒是“美”了,工程上的更重要的东西却被忽略抛弃了。erlang 的指令性能也不强。整体下来,我觉得 erlang 作者当初发明它用来做电信业务这种“进程”间不需要太多交互、并且主要处理 IO 的业务式很 ok 的,但并不适合拿出来普及到所有业务,所以它手持一代目语言级“进程”(本质是协程)却没有大火起来,很是理所当然。
    lesismal
        137
    lesismal  
       2023-12-23 22:27:16 +08:00
    而除去 erlang ,在 golang 之前:
    我们做架构、框架,最头疼的往往就是进程线程模型、并发之间的通信和一致性这些基本点的设计。c/cpp 也好,java 也好,虽然不是什么难事,但代码仍然都相对麻烦。而 golang 里,go 、chan 、mutex 这三个关键字,几乎就搞定了这一切,绝大多数场景也不用去为 callback hell 头疼。

    c/c++的一些协程库,还有一些脚本语言的手动档 yield 协程,包括现在那些 async await ,相比于 go 都显得更加难于理解。所以我完全不觉得它们有资格用来跟 go 比较,如果没有 go ,相比于它们,我宁愿回头自己写系统线程、IPC 那些逻辑。
    lesismal
        138
    lesismal  
       2023-12-23 22:39:38 +08:00
    语言上大概有学院派、工程派这些不同种类的人,工程上又有 CURD 派、架构派这些不同种类的人。
    每个角度上的不同派别,各自的着重点都不一样。
    多数学院派讲究茴字的写法是否优雅却忽略了性能之类的指标考量,所以我经常看到这类人概念大把大把地往外掏,经常让我自愧不如、因为不知道他们在说什么,但多数时候等我缓过神来,探究这些概念、优雅的玩意背后真正的实用主义,都觉得似乎并没有什么卵用,然后也就不用耿耿于怀;
    工程派追求写法与实用的平衡,当然,实用的基础上也能非常优雅是最好的,但鱼和熊掌难两全,踏实做事情的人,我更倾向于实用主义;
    CURD 派,就如我前面所说,能让他们自己舒服的才是最好的,什么性能优雅完全不 care 的感觉,就比如异常处理这档子事,java 的社区惯性用法就真优雅吗?审美可是没有统一标准的,怕是中尉 javaer 自娱自乐自封这样最好罢了;
    架构派要考虑好多细节,但凡好点的,也都是工程派,讲求实用主义,多数 java 社区的所谓架构师,我个人都是不认同的,八成就是社区方案一把梭,什么东西适合什么场景都未必去思考过,学以致用,至于用得对不对,反正社区都是用这些东西。。。

    当然,业务大了都是堆屎山,大家都是混口饭吃,能挣钱就行。世界本就复杂、好的和不好的共存,大势所趋,被下一阶段的事物取代,代码这档子智慧进化进程中的一个阶段性技术,早晚是 AI 的天下
    lesismal
        139
    lesismal  
       2023-12-23 22:44:22 +08:00
    #137
    > 我们做架构、框架,最头疼的往往就是进程线程模型、并发之间的通信和一致性这些基本点的设计。

    补充:
    我们做架构、框架,最头疼的往往就是进程线程模型、并发之间的通信和一致性这些基本点的设计;
    我们做业务写逻辑,最头疼的就是写回调,不写回调性能不给力。
    FightPig
        140
    FightPig  
       2023-12-24 00:29:22 +08:00
    @cmdOptionKana 我说的是 go 吹,虽然我也用 go ,但就见不得 go 吹问就是 go 语言简洁,不用这个不用那个
    iseki
        141
    iseki  
       2023-12-24 00:52:44 +08:00
    @lesismal 你说的没错,进程间通信机制这样的东西就是大厦的基础,是砖头,但是编程时如果每次自己去搬这一个个砖头这既不符合 DRY 的原则也不够“少”。async await 算是结构化并发的一种落地形式。
    你不能说封装的程度高就一定是不好的,软件工程是权衡和妥协,君不见 Go 相比 C 不是也隐藏了许多细节吗,每个函数开头插入栈空间检查未必是个多么高效的选择,当年也有人因为用了“高级语言”就被喷浪费了机器性能。
    systemd 出现之前,如今一个 .service 文件搞定的事情要一位 bash 熟练工写上成吨的 .sh 还不一定好好解决问题。看上去 shell 里的每一个命令都好简单啊,但是组合起来的结果你能说这是“简洁”的吗。当然这个我不多说,我相信每一个工程师都很清楚。

    此外 Java 的轻量级线程和 Go 的 goroutine 差不多一回事,当然 syscall 该卡还是卡,go 也是在 IO 等等地方特殊处理掉的,这个不是应用程序自己做点什么就能解决的。(其它关于 Java 的内容我就不评论了,这个看场合见仁见智
    lesismal
        142
    lesismal  
       2023-12-24 02:33:38 +08:00
    @iseki
    > 但是编程时如果每次自己去搬这一个个砖头这既不符合 DRY 的原则也不够“少”。

    这个完全赞同,所以我选了 go 之后基本没再回头写多少 c/cpp 了,因为没必要,go 太省事了,而且绝大多数场景也不需要极致的性能、反而更需要开发效率

    > async await 算是结构化并发的一种落地形式。

    但这比起 go func()来,便利性可读性还是差得多

    > 你不能说封装的程度高就一定是不好的,软件工程是权衡和妥协,君不见 Go 相比 C 不是也隐藏了许多细节吗,每个函数开头插入栈空间检查未必是个多么高效的选择,当年也有人因为用了“高级语言”就被喷浪费了机器性能。
    > systemd 出现之前,如今一个 .service 文件搞定的事情要一位 bash 熟练工写上成吨的 .sh 还不一定好好解决问题。看上去 shell 里的每一个命令都好简单啊,但是组合起来的结果你能说这是“简洁”的吗。当然这个我不多说,我相信每一个工程师都很清楚。

    我当然不是反对封装程度高,而是反对不必要的过度封装。
    而且要分开不同的层次,一个是语言级的,一个是框架级的。
    go 也好 java 也好,它们在 runtime 内的实现,都是语言级、不需要使用者去关心太多内部细节。
    而框架这一层,至少我觉得 Java 是属于过度封装的,比千层饼的层数还多。。好处是更可靠,坏处当然是牺牲性能、以及真遇到问题时使用者难于深入到一层一层内部去发现和解决问题。

    > 此外 Java 的轻量级线程和 Go 的 goroutine 差不多一回事,当然 syscall 该卡还是卡,go 也是在 IO 等等地方特殊处理掉的,这个不是应用程序自己做点什么就能解决的。(其它关于 Java 的内容我就不评论了,这个看场合见仁见智

    这个 `当然 syscall 该卡还是卡` 要看是怎么个卡的方式,是卡协程但协程会被调度、还是卡了系统线程。
    我前面 #135 里提到的 `系统调用之类的接口` 其实没说完整,准确点讲应该是 `把系统调用那些封装起来然后提供给用户的接口`,例如 go 标准库 net.Conn.Read:虽然它是阻塞的,但它不会阻塞系统线程,实现方式就是当前读不到数据时就调度了并且等待可读,runtime 里的 iocp/epoll/kqueue 等待可读事件到来再唤起。go 里直接去调用可能阻塞的 syscall 也还是可能阻塞,但标准库对于 socket fd 封装出来的 net.Conn 之类的接口都是被 poller+调度这样搞定了的,所以使用标准库的这些是真的可以写同步代码也不会导致线程池阻塞耗尽之类的。
    Java 的轻量级线程如果没有配套实现全套的类似 go net.Conn 这些的话,那可能还是没有解决阻塞系统线程的问题,那至少从语言级上,就不是一个类型、不是同一可用级别的协程系统、是没有可比性的
    zzhaolei
        143
    zzhaolei  
       2023-12-24 10:51:33 +08:00
    1. go 可以预先声明好 error ,然后进行判断,相当于是有类型的
    2. error 确实缺少异常堆栈信息
    3. 不想处理错误也可以直接 panic ,然后在最外层使用 recover ,和 try catch 有点像

    ```go
    package main

    import (
    "errors"
    "fmt"
    )

    var MyError error = errors.New("this custom error")

    func test() error {
    return MyError
    }

    func test1() error {
    return errors.New("this custom error")
    }

    func main() {
    err := test()
    if err == MyError {
    fmt.Println("this my error")
    }

    err = test1()
    if err == MyError {
    fmt.Println("this my error")
    } else {
    fmt.Println("this not my error")
    }
    }
    ```
    iseki
        144
    iseki  
       2023-12-24 14:49:47 +08:00 via Android   ❤️ 1
    @lesismal
    关于 async await 这个啊,给你看个 Kotlin 的例子
    coroutineScope{
    launch{ doSth() }
    async{ getSth() }.await()
    }
    launch async 各开了一个协程,任何一个未捕获的报错都会导致沿着父子兄弟关系向上逐层取消(直到捕获或者是监视器域)此外 scope 内协程执行完毕前控制流也不会离开 scope 。这是 Kotlin 的 “结构化并发”。

    你说 Java 封装的太狠这件事我一定程度上赞同,春天系我也不喜欢用…
    此外轻量级线程确实是你说的这种意思,IO 等做了处理,否则就没意义了。
    lesismal
        145
    lesismal  
       2023-12-24 20:33:16 +08:00   ❤️ 1
    @iseki #144

    我不听我不听🤣🤣🤣,就你这个这个 async await 的解释我就看的累心,比起传统进程线程比起 go 协程也太不直观了,而且还有你说的一大堆什么捕获报错父子兄弟控制流一大堆,我学不动。。。
    iseki
        146
    iseki  
       2023-12-24 20:47:06 +08:00   ❤️ 1
    @lesismal 🤣🤣🤣这种特性可以极大降低编写并发代码的心智负担呐,我不信你不用 errgroup.Go 它就类似这种东西,只不过是个手动挡的🤣🤣🤣
    lesismal
        147
    lesismal  
       2023-12-25 02:22:55 +08:00
    @iseki #146
    不怕你笑话,你要不说,我还不知道有 errgroup 这玩意,刚搜了下,看样子不错,以后可以考虑用下。。。
    我会的不多,用到的时候才会去查,很多用不到的干脆不懂。。。
    一些 golang 公众号里经常发 golang 面试题,我偶尔看一眼就是一堆的不确定不会做。。。
    你 github 账号是什么啊,我来 follow 一下
    iseki
        148
    iseki  
       2023-12-25 08:55:32 +08:00 via Android
    @lesismal iseki0🤣互 fo
    layxy
        149
    layxy  
       2023-12-25 09:33:38 +08:00
    java 异常增加未知性,哪里增加了,异常调用链都可以看到,哪一行报错也知道
    lesismal
        150
    lesismal  
       2023-12-25 14:20:41 +08:00
    @iseki 搞起🤣
    iseki
        151
    iseki  
       2023-12-26 02:51:35 +08:00
    @lesismal #145 <del>吹 Kotlin 时间到</del>

    launch{
    coroutineScope{ // 这个是作用域
    launch{ 协程 1 }
    val r = async{ 协程 2 ,async 有返回值 }
    r.await() // 获取协程 2 的返回值
    }
    // 以上协程全部执行完,才会到这里,在此之前卡在括号里(因为 scope);
    // 协程 1 2 是兄弟,他们共属于外面的协程,也就是这里,顶层的 launch ;
    // 因为没有 try-catch 捕获错误,一旦 1 2 出错,至少会一直取消到这里(协程支持取消)
    }

    写并发是不是心智负担大大降低🤣
    lesismal
        152
    lesismal  
       2023-12-26 10:34:39 +08:00
    @iseki #151

    跟线程、goroutine 相比还是不够直观。
    另外,这种临时起多个并发去处理一组事情并等待所有并发执行完再继续,并不是最主要场景。
    最主要场景:
    例如服务器,处理每个连接,为每个连接起单独协程然后写同步代码;
    例如爬虫,爬很多 url ,每个站一个协程然后递归去爬子 url ;
    这些都是通常具有不确定数量和不确定执行完成节点的任务。

    休想骗我学 kotlin ,要学也是 rust 🤣🤣
    wkong
        153
    wkong  
    OP
       2023-12-26 10:46:04 +08:00
    @lesismal 跟你一样的 我也才知道 errorgroup😂
    lesismal
        154
    lesismal  
       2023-12-26 10:49:31 +08:00
    @wkong 我到现在都还没学泛型相关的呢 😁
    wkong
        155
    wkong  
    OP
       2023-12-26 10:52:16 +08:00
    @lesismal 一样😂,我是用到什么才去学。
    lizhisty
        156
    lizhisty  
       246 天前
    @thinkershare 20w 行的大项目,目前还行
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2919 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 06:34 · PVG 14:34 · LAX 22:34 · JFK 01:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.