Envoy 这样的工程构建已经是非常复杂了,当然 Go 大型工程也不简单。 但是入门写 Go 基本就一条路,入门 C++ 就很依赖人的主观判断。
学了两天 bazel ,又学了一天 cmake ,加深了这个想法。
1
lanlanye 2023-02-08 01:57:07 +08:00 via Android 2
难说,工程实践的结果是越写越像 Java ,然后越来越觉得那个异常处理反人类……
|
2
TWorldIsNButThis 2023-02-08 01:58:32 +08:00 via iPhone
Java 已经在应用层革过一遍 c++的命了
|
3
securityCoding 2023-02-08 08:17:11 +08:00 via Android
@lanlanye 已经不是反人类的问题,对代码的破坏程度简直丧心病狂
|
4
xuyang2 2023-02-08 09:32:27 +08:00 34
我觉得 go 的 if err != nil 没啥可黑的
层层嵌套的 throw cache 才是反人类 |
5
pursuer 2023-02-08 09:50:33 +08:00
这不是语言的问题,是生态分裂造成的,现在编译器就好几个,gcc clang msvc ,c++委员会标准库推进慢,各个平台对 c/c++的接口存在差异。
现在的新语言很多都是唯一实现 java(不考虑 android 的话) go rust 啥的。 当然也有一个分裂比较严重的 js ,好在 js 的灵活性在一定程度上减轻了这个问题,但是依然诞生的 webpack rollup swc esbuild 等一大堆构建工具。 |
6
TtTtTtT 2023-02-08 09:54:13 +08:00
一些编程哲学放在一边。
Goroutine 的代码实在是太难懂了,跟 Akka 一样属于好写,但是完全看不懂。 |
7
8355 2023-02-08 10:05:05 +08:00 5
Go 才是现代编程语言标准
同样的需求大概率只有一种写法 而且加上代码格式化 不会让你有在魔法世界的感觉 对新手友好度很强 不需要考虑什么某某函数 某某方法有这样那样的问题 |
10
RedisMasterNode 2023-02-08 10:08:46 +08:00
@TtTtTtT 怎么会难懂呢...或许你可以举一些认为难懂的例子大家康康具体是哪里不容易阅读
|
11
zcreg 2023-02-08 10:10:35 +08:00 6
Go 让人舒服的一个点就是读别人的代码,不会有那些花里胡哨的写法
|
12
fioncat 2023-02-08 10:14:07 +08:00 15
我发现一堆人真的无脑黑 Go 的错误处理。
Go 错误处理的好处在于强迫你认真对待每个 error 。 某些 Javaer 一有 exception 就无脑直接 throw ,他们肯定理解不了这种设计。 |
13
libook 2023-02-08 10:18:46 +08:00
与其他语言很鲜明的区别是,Go 是专门为生产工程场景设计的一款[产品];为了解决生产过程中的痛点,牺牲了一些部分技术人员看重的爽点。
|
14
xxv0 2023-02-08 10:20:20 +08:00
@xuyang2 什么是 try...catch...的层层嵌套,你是指在调用方与被调用方都 try...catch...,还是说在同一个函数的 try 块里再次 try...catch...,按理说这两种写法都是不对的。
|
15
zbatman 2023-02-08 10:21:14 +08:00 2
OP 一句没提 Java ,评论句句在踩 Java ,有趣
|
16
chendy 2023-02-08 10:23:20 +08:00
所以说 go 更适合当 cpp 用,写基础设施
写业务也就图一乐 |
18
tt67wq 2023-02-08 10:29:16 +08:00
@TtTtTtT 深有同感,到处都是 channel 的异步,完全找不到消息从哪里来到哪里去,一两个模块的异步还行,有的十来个模块相互异步调用,非常令人头秃
|
21
dog82 2023-02-08 10:38:52 +08:00
没 go mod 之前,很痛苦的
|
22
GeruzoniAnsasu 2023-02-08 10:44:48 +08:00 2
@RedisMasterNode
语法不复杂,但是要写出 对 的程序,那可复杂到天上去了。要知道 golang 只有协程,但它不提供 **异步语法** golang 没有 await ,这意味着你要完全自己手动处理所有 chan 和 并发开始的 goroutine 的关系和时序。2023 年了连 c++都能 await 协程了,golang 却还在用 select 和 pipe 手搓异步逻辑,我愿称其为 「 unix 原教旨主义」。 举个例子好了。你有一个 spwaner , 它能并发地生成若干 worker ,worker 的执行时长不确定。 现在有一个要求,所有 worker 的执行结果要按启动顺序写回到同一个与 spwaner 共享的 chan 里,开始你的头脑风暴。 |
23
Nazz 2023-02-08 10:52:37 +08:00
@GeruzoniAnsasu async 有传染性, 同步方式写异步代码对开发者更友好, 但是牺牲了性能.
|
24
RedisMasterNode 2023-02-08 10:59:41 +08:00
@GeruzoniAnsasu 我看你的描述很多时候只需要 wait group 吧。。wait group 的使用非常简单,只有需要 goroutine 间通信的时候才会需要 channel 呀,业务应用里面 goroutine 大部分场景都是用来并行做一些事情,例如并行发起 http 调用,我 golang 用了有小几年了没有感觉到什么不适而且觉得很好理解
当然你可能在描述一些多个 channel 之间共同协作,需要知道互相的结果,需要传递数据的情况,我不了解在其他语言怎么做的,但是我觉得常规开发里面写出这样的逻辑设计本来就已经对可读性不友好了,不能说只怪 golang 吧 |
26
yazinnnn 2023-02-08 11:15:20 +08:00
monad 鄙视一下 throw 和 try catch 也就算了, 这年头 if err != nil 都能鄙视 throw 了吗
|
27
star9029 2023-02-08 11:24:07 +08:00
c++ build system 是这样的,不过 cmake/xmake 的工程能 work ,而且 cmake 有大量成熟项目(这并不影响他难用)
|
29
TtTtTtT 2023-02-08 11:53:25 +08:00
|
30
ericls 2023-02-08 11:56:38 +08:00 via iPhone
我最近才开始真正写 go,
总之很喜欢 我也说不出具体原因 tooling 也很好 想写的东西甚至乱来 根据报错也能边学边写 这种喜欢可能有一部分来自于正在学习新东西的兴奋 但也只是一小部分 |
31
angrylid 2023-02-08 11:57:06 +08:00 3
又到了爷最爱的斧子党和锯子党互相鄙视环节。
有这时间不如多砍两棵树。 |
32
xiangyuecn 2023-02-08 12:03:18 +08:00
@xuyang2 #4 没写过 go ,如果 编写代码漏写了 if err != nil 会产生什么有趣的问题吗? 还是说不写 if err != nil 编译不过?
|
34
sadfQED2 2023-02-08 12:19:11 +08:00 via Android
@xiangyuecn 跟 java 漏写 try 差不多
|
35
blankmiss 2023-02-08 12:41:58 +08:00 1
if err != nil 还不如 try catch
|
37
hhjswf 2023-02-08 12:46:58 +08:00 via Android 1
如果觉得 try catch 恶心,aop 可以解决。go 有什么优雅一点异常处理
|
38
loading 2023-02-08 13:09:57 +08:00 via Android
@xiangyuecn 直接全局替换 err 为 _,直接就忽略了。
|
39
lanlanye 2023-02-08 13:37:18 +08:00 1
@xuyang2 很有问题啊,因为大多数需要异常处理的函数都得返回至少两个结果 (result, error) ,深层调用时每一层做的事就是执行函数,如果有 error 就往上一层抛,也会导致链式调用无法正常写出来,比如 `person.Pet().Name()` ,如果 Pet()方法是一个可能失败的 lazy load ,调用时就根本写不成这样。
目前我只见过 Gorm 那样把 error 直接放进返回值结构里的做法可以缓解这个问题,或者希望 Go 学一学 Rust 。 |
40
lanlanye 2023-02-08 13:39:08 +08:00
@fioncat 我一开始也是这么认为的,但它确实造成了不便,可以参考我在楼上的回复。另外 Rust 的处理方式就很好,它同样强迫你处理每一个 error 。
|
41
lanlanye 2023-02-08 13:42:34 +08:00
@GeruzoniAnsasu 我想了想,开辟一个用于保存结果的数组,启动 worker 的时候传入对应顺序的数组下标,直接把结果写进对应位置……应该可行吧
|
42
Smilecc 2023-02-08 13:45:24 +08:00
@Nazz await 未必一定具有传染性,本质上是因为 JS 或 Rust 等语言都是无栈协程,实现方式决定具有传染性,Go 的协程是有栈的,不依赖状态机做上下文切换,我认为是可以实现的
|
44
leonshaw 2023-02-08 13:51:32 +08:00 1
@GeruzoniAnsasu #22 没明白你说的,对 Go 来说大部分情况只要把无栈协程模式的 await 直接改成同步调用就行了,并不需要启 goroutine 。
举的例子只要在 goroutine 把结果写到一个 slice 对应位置就可以了,“回到同一个与 spwaner 共享的 chan 里”是伪需求,因为 chan 是 Go 特有的。 |
45
5h4nh 2023-02-08 14:33:01 +08:00
@fioncat 不赞同。我用 sourcegraph.com 查了一下 `if err != nil`,基本都是 `return err`,没有处理。而且还有不少人直接写 `_`。相反我认为 Java 的 checked exception 机制,强制要求 caller 写 try-catch 或者 caller 签名也加上,才是所谓「强迫你认真对待每个 error 」。另外,你说「某些 Javaer 一有 exception 就无脑直接 throw 」,我觉得他们如果换用 Go ,情况只会更糟糕吧..
|
46
5h4nh 2023-02-08 14:35:53 +08:00 1
@Nazz 我也觉得 Go 的源码很难读,其中一个原因就是变量名太追求 “Unix 风格”,我觉得有点过头了。比如这个 `sudog`.. https://stackoverflow.com/questions/68569386/whats-the-mearning-of-sudog-in-the-channel-struct-in-go
|
48
macscsbf 2023-02-08 14:50:23 +08:00
知乎上在哪里看到的,go 的最大特色是无聊
|
49
chenqh 2023-02-08 14:53:13 +08:00
既然 golang 代码看起来这么简单,为什么我看 crowedsec 看不懂呢?
|
50
th00000 2023-02-08 14:56:24 +08:00 2
|
51
learningman 2023-02-08 15:00:28 +08:00
@GeruzoniAnsasu #22 启动的时候带上个序号,返回结果的时候带上序号,waitgroup 等待所有 worker 完成。
就算有 async await 不也是这么操作吗,难道你想 for 1 to n await result ?这样写是符合直觉,但又不是唯一的标准答案。 |
52
Slurp 2023-02-08 15:14:56 +08:00 6
靠元组实现标签联合的垃圾类型系统。基于此出来的错误处理也是一坨大便,这也有人吹?
|
53
wupher 2023-02-08 17:05:49 +08:00
catch / throw 当然不完美
if err!= nil 多层嵌套有时更变态,毕竟 runtime exception 你还可以不处理。 学了 Rust 之后确实相信这才是更优雅的设计。 |
54
quicksand 2023-02-08 17:05:57 +08:00
各有千秋吧,不同人肯定喜好都不一样的,这也是语言多样性的原因,没必要强行来比较。我最近也在学 go ,感觉不舒服的点就是注释文档,感觉 javadoc 这方面做的更好一些。
|
55
JamesMackerel 2023-02-08 17:19:44 +08:00
看到贵贴,想来请教一下 go 的 thread local (或类似机制)的进展……
我知道可以用 Context 然后把每个 function 都加个 Context 参数,可是除了这种方法还有没有别的办法? |
56
liuxu 2023-02-08 17:36:05 +08:00
最后发现 php 依然是最好的语言,而你们都会来写 rust
|
57
GeruzoniAnsasu 2023-02-08 17:48:56 +08:00
@RedisMasterNode @Nazz @leonshaw
不是的,wait group 只能在所有任务完成前一直阻塞住。而作为一个 spawner ,你需要时刻维护一个有长度的队列,当队列空出来时立即解除正在预约( schedule )任务的 routine 的阻塞,wait group 显然不合适。 注意我们的目标是,让结果按照添加的顺序依次输出,而不是一次性等待所有的结果一起输出。 有异步语法的语言,在这个场景的做法是 - 一个有长度的阻塞队列 - 当外界 scheduling 新任务时,spwaner 向队列获取一个空槽,如果队列已满,那么 spanwer 和 请求者都会被阻塞 - 如果获取了空槽,将任务放入空槽,获得一个 promise - 创建新 promise, 在这个 promise 里 { await 任务队列的尾部任务(因为我们需要按任务的添加顺序而不是任务完成顺序来返回),await 到之后返回上一步获得的 promise } - 把上面这个 promise 加到 out 队列里,每次提取结果时 await out 队列的头部 而 golang 要模拟这个做法的话,首先它没有 promise ,也没有 goroutine 的 handler ,然后要实现跟上述等价的 spawner 必须使所有调用 spanwer 的线程共享同一个 channel ,意味着 chan 要么是全局的,要么扔到 context 里。先简单考虑全局唯一 chan 的做法。(但复制 chan 用 context 传这种逆天玩意我也写过) 提取任务槽这步没问题,但怎么模拟一个 promise ? - c := make(chan,1) ; go func(){c<-do();)} 那怎么获取任务队列的尾部任务并 await 它? - 如果任务队列只是个简单的 channel 是做不到的,因此需要一个 slice + channel ,可是 slice 就没有锁了,你这时候要考虑一个可阻塞环境( chan )下的锁问题,头开始疼起来了 怎么返回 await 了 c 的新 promise ? - …… 对了,这个新 promise 还要放到 out 队列里 - ………… |
58
Nazz 2023-02-08 17:54:50 +08:00
@GeruzoniAnsasu 添加任务的时候加序列号, 线程同步后给输出结果排序
|
59
GeruzoniAnsasu 2023-02-08 18:00:12 +08:00
@Nazz @learningman @leonshaw @lanlanye @RedisMasterNode
我提醒你们一下关于放到对应序号结果槽的实现: - 这个结果 array (它有大小,我这里用 array 来称呼,并不是指实现),是有「洞」的,需要有个机制能按顺序检查每个位置是否完成了,没完成要能阻塞住,意味着 array 里放的是锁或 chan 或任意什么东西总之是一个可锁对象,但有 promise 的情况下不需要这种可锁对象 - 我们不能一次性等待一批 worker 全部完成,而是要时刻能分派已完成的 worker 占用的任务槽 - spwaner 本身要可以等待或阻塞 |
60
Nazz 2023-02-08 18:23:43 +08:00 via Android
|
61
Nazz 2023-02-08 18:26:18 +08:00
@GeruzoniAnsasu 使用有锁队列保存任务; 任务完成后去队列拿下一个任务, 递归地调用;
|
62
CRVV 2023-02-08 19:28:08 +08:00 1
@GeruzoniAnsasu
package main import ( "fmt" "math/rand" "time" ) func worker(ch chan int, x int) { d := rand.Intn(10) time.Sleep(time.Millisecond * 10 * time.Duration(d)) ch <- x } func main() { var queue []chan int for i := 0; i < 10; i++ { ch := make(chan int) go worker(ch, i) queue = append(queue, ch) } for _, ch := range queue { fmt.Println(<-ch) } } |
63
CRVV 2023-02-08 19:43:07 +08:00 1
@GeruzoniAnsasu
这个东西不难写,只不过它的写法和 JavaScript 惯用的写法可能不一样。 如果你觉得我发的这个不符合你的要求,你可以先用 Promise 写一版我来翻译成 Go 总体上 Go 不用写 "await" 这几个字母,其它和带异步且多线程的语言完全一样。当然 Go 自带的语言功能少一些,不限于异步并发关系时序这些,所有方面的功能都少。 > 有 promise 的情况下不需要这种可锁对象 多线程环境下的 promise 本身也带锁或者类似的机制。单线程的 JavaScript 是另一回事。 > 我们不能一次性等待一批 worker 全部完成,而是要时刻能分派已完成的 worker 占用的任务槽 这个和 Promise 有关系么?我没觉得用 Promise 可以简化这件事情的实现,拿到一个结果了再开下一个任务,都一样吧。 > spwaner 本身要可以等待或阻塞 没看懂,什么地方可以等待?你是指可以 await spwaner() 么? |
64
lxdlam 2023-02-08 20:16:44 +08:00 1
@GeruzoniAnsasu
针对你的 task ,准备一个跟结果一直长度的 []chan 就可以了,扫一遍每个 channel 就可以针对每一个 chan 的阻塞策略,很直接。https://go.dev/play/p/YbtujcTry_J 。 至于 Promise ,你需要的只是一个 Task 结构,注意到结果本身是否 ready 可以依靠超时 + channel ,解决,封装一个类似的结构是 naive 的,在 github 上能找到非常多的类似的库。 可以去看一些官方 talk 理解 CPS 机制的原理,而不是尝试把某个机制 mapping 过来。 |
65
leonshaw 2023-02-08 20:23:16 +08:00
@GeruzoniAnsasu 本质上,是你后一个 promise await 了前一个。相应地 Go 里面为每个 goroutine make 一个 channel ,在写结果前等前一个 channel 就行了:
in := make(chan func() any) out := make(chan any, 1) go func() { for result := range out { consume(result) } }() prevDone := make(chan struct{}) close(prevDone) for do := range in { done := make(chan struct{}) go func(do func() any, prevDone <-chan struct{}, done chan<- struct{}) { result := do() <-prevDone out <- result close(done) }(do, prevDone, done) prevDone = done } close(out) |
66
lxdlam 2023-02-08 20:35:31 +08:00
@GeruzoniAnsasu
刚注意到有个 context miss 了,我也补充几个点: - 使用 mpsc 跟 spsc 是非常简单的,一个基于 token 的 bucket 可以简单控制好 task 的数量,共用 token bucket 就可以控制每个 spawner 的数量。注意到这里也可以简单地基于 chan 封装一个,不需要所谓的阻塞队列。 - spawner/worker 基于 message passing 的 channel 可以解决所有 promise 的场景,包括超时等待等。 - 所谓的全局 channel ,js 的 microtask queue 和 marcotask queue 同样是全局的,甚至基于这两个场景你如果需要定制化 queue 的调度逻辑你需要对 runtime 有更加深入地理解,而 go 基于 token bucket 做定制可以做更多的事情。 |
67
learningman 2023-02-08 21:44:00 +08:00
@GeruzoniAnsasu #54 你别加条件,我说的实现是给你的最初版本的需求的
|
68
wangritian 2023-02-08 23:12:27 +08:00
你们不要再打了啦
|
69
swulling 2023-02-08 23:19:28 +08:00 via iPhone
编译一个 envoy 三个小时,醉了。
|
70
StevenRCE0 2023-02-09 00:12:04 +08:00 1
err 判空不算大问题,但是显然是 throwable 更适合工程啊……
人们在改进错误处理,然后到某些选手这儿直接就说不出错误不就行了,属实流汗黄豆。 |
71
nino 2023-02-09 00:24:29 +08:00
@GeruzoniAnsasu 首先没有细看你的需求。但是 go 只是标准库不提供 async await Promise 这些并发原语而已,要模拟出来很简单的啊,有了之后不就和你写 JS 一样了。作为 JS 和 Go 都写过的人,可以负责任的讲,Go 并发这块灵活性比 JS 强多了,可以写出很有表现力的代码。
goroutine + chan + sync 包里那堆东西,什么并发程序都能写出来。 |
73
dbskcnc 2023-02-09 10:09:30 +08:00
什么舒服 /合适就用什么,大部分情况,我 go 用得挺舒服的. c/c++ 确实麻烦很多
|
74
xsen 2023-02-09 11:04:06 +08:00 1
@dbskcnc #72 前后用过很多语言,如 c/c++/python/java/javascript/dart 等等,到现在的 go ,相对来说用 go 的体验是最舒服的——不管是开发、调试,还是打包部署诸如此类。当然,也包括跨平台、交叉编译等
|
75
zxCoder 2023-02-24 16:00:03 +08:00
便捷,是大便的便吧
|
76
echoless 2023-02-28 10:29:21 +08:00
|