看了一个 go 语言,感觉语法略为不习惯。

2016-01-27 09:42:17 +08:00
 fire5
还是大 python 舒服。。
22946 次点击
所在节点    Python
151 条回复
noli
2016-02-02 19:05:48 +08:00
@codeaqua

对排斥的东西当然是一黑到底了,人之常情。只不过令你等不高兴了,那你来啊,我从来不介意你们说我黑子,喷子,无理取闹——反正我又不掉一根猫。

对待排斥的东西,有的人斯文一点就呵呵,心里暗笑你个傻逼;像我这样粗鄙一点的就开喷,然后增加点谈资——心胸狭隘的人自然会抵制会反对,有眼光的人可能会看到别的不同的观点或者没接触过的领域;然后我继续很爽;我没觉得这有什么不好。

就算你说我偏执狂,也不会动摇我说的事情本身具有的合理性——如果恰好我说的是对的,那么我对我说的偏执不是有另外一种叫法叫做“对真理的追求”?呵呵。

我排斥 go 语言就是因为它是编程语言的倒退,当然我不排除它在某些领域确实很使用;这也算就算了,但是叫嚣什么“解决了异步模型侵入性问题”—— 这个本来就是一个 setjump longjmp 问题的扩展而已——我就觉得你们这帮 go 语言粉真是无知者无畏啊……是不是你们只听说过 goroutine 没听说过 coroutine 啊?
codeaqua
2016-02-02 19:09:49 +08:00
@noli “有的人斯文一点就呵呵,心里暗笑你个傻逼”, 我有什么话喷你了吗?还是你自己 yy 的?还有 coroutine 我比你知道的多,我也是软粉, c#和 typescript 我都在用,你别再给我们其他软粉招黑了可以吗?
codeaqua
2016-02-02 19:12:18 +08:00
还有 6L 也是,简直给 gopher 招黑,
noli
2016-02-02 19:15:52 +08:00
@codeaqua 为了帮你们科普,我这么热情主动,你们还说我偏执狂。你们自己说说到底谁才值得被 BS ?

https://en.wikipedia.org/wiki/Coroutine

“ According to Donald Knuth, the term coroutine was coined by Melvin Conway in 1958, after he applied it to construction of an assembly program.[1] The first published explanation of the coroutine appeared later, in 1963.[2] ”

看到没有, 1963 年就有这 coroutine 这种概念了。再看看下面的 coroutine 支持的语言列表?有没有什么感觉?

go 语言所解决的问题,只不过就是把 io 相关的 API ,跟 coroutine 和线程池 结合在一起,做成了一个语言。然后,再顺便按照发明人自己的特殊癖好,把 exception 砍一下,加了个 gc 。只要你喜欢,随便哪个语言封装一下库都可以做好这种功能。

我能理解你们为什么这么兴奋——因为以前你们用别的语言不知道怎么解决这类问题,现在有了,感觉自己的能力好像强了很多——是的, golang 降低了一点这些门槛,然而 golang 没有解决而你们也没有遇见过的问题依然存在。
codeaqua
2016-02-02 19:22:18 +08:00
@noli 你真以为就你知道 coroutine ?玩异步的谁不知道?,动不动就嘲讽,别再丢人现眼了, MD ,实在是忍不住喷你了,再见
fire5
2016-02-02 19:37:54 +08:00
同学们,这个话题结束了吧。
noli
2016-02-02 19:38:20 +08:00
@codeaqua 哈哈,如有误伤,实属无奈,记得下次正确评估对手,毕竟跟疯子或者喷子开战,肯定是掉身价的,哈~ 走好不送。

我相信多数人都知道 coroutine 是什么鬼。
但是从 形式上 或者 实践上,怎么把 coroutine 和 处理大量 IO 的问题结合起来,不知道的人肯定更多——不然也不会有那么多人就在瞎吹 golang 了。
zrinthect
2016-02-02 19:44:51 +08:00
哈哈哈。恨铁不成钢。
zhujinlong
2016-02-02 20:04:07 +08:00
不喜欢处理 err 的同学,你们写 C 的时候是怎么办的?
aheadlead
2016-02-03 08:59:19 +08:00
@noli 逻辑推断错误

人身攻击很讨厌啊…
lazydao
2016-02-03 09:17:07 +08:00
虽然有人身攻击,但认真讨论技术还是要👍。
goool
2016-02-03 13:02:53 +08:00
@noli

很惊讶的看到,并且完全不同意 107 楼的说法:“实际上,软件工程的经验就是 exception 的代码结构比 返回 err 可重用性和健壮性高得多。”

基于我的浅薄理解, Go 的设计者认为程序中出现执行分支的大多数情况,都是 *需要* 编码者 *分析* 并 *解决* 的。如果 F 调用了 G ,现在 G 返回了一个 err ,说明 F 交给 G 的任务,已经不在 G 的 *正常* 设计之内了,简单来说, F 给了一个 G 能力之外的工作,这是 F *必须* 解决的问题。这种设计迫使你关注每一个 err ,进而迫使你: 1 、 KISS ; 2 、模块化; 3 、分层。

而在极少数的情况用到的 panic/recover ,从字面来说,就不是用于业务逻辑的,而是用于防御和恢复, panic/recover 建立的是一个系统边界,用于处理类似这样的问题: 1 、出问题了你完全不懂吗,按一下机箱上的 Reset 按钮; 2 、我被打了一拳晕倒了,让我重新冷静一下再站起来; 3 、机械卡住了,让我们回到第一步,先抖动几下(也许就不卡了),重新开始。
goool
2016-02-03 13:12:40 +08:00
大量使用 exception , try/catch 的代码,具有这样的气质:我给你一个任务,你完成就好,你搞不定?好吧,这事我也没办法了,向上抛吧。

大量使用 if err != nil 的代码,是这样的:我给你一个任务,你完成就好,你搞不定?好吧,帮你擦屁股吧。
noli
2016-02-03 14:38:04 +08:00
@goool 关注点并不是返回 err 或者 try catch 的出发点是什么,先别忙着讨论气质。

让一个 stream socket 发送若干字节算不算 stream socket 能力之外的事情?不算吧? stream socket 尝试发送之后发现 tcp 连接已经因为超时断掉了,再算不算 *正常* 设计内应该考虑的事情? 算的吧?

好的,既然 stream socket 发送字节和要处理超时连接中断这种事情,都负荷你说的,可以使用 err 的场合。那么现在,我要通过 send_x_target(x, targets) 并发地启动 x 个 stream socket 发送到 targets 列表所指定的目标地址,然后找出有哪些地址发不过去了。

请问你要怎么设计 send_x_target 的返回值?

如果你说,这种场合不适合用返回值:请问要怎么解决这类多个 IO 复合的问题?

没想明白这类问题的抽象形式,我觉得是没有资格来讨论 try catch / err 是怎么设计的吧。
goool
2016-02-03 15:25:43 +08:00
@noli 就你的具体问题,看我理解的对不对?

你要有一个函数 send_x_target ,向多个目标 socket 发送数据。那么我可以合理的假定,对于调用者来说,每个目标 socket 是无差别的,也就是说,其中的 socket s1 超时了,与 socket s2 超时了,对于调用者来说是无差别的,采用同样的策略处理(如忽略、报错、丢弃、重试、日志等)即可。那么在这个假定之下, send_x_target 函数再加一个 failurePolicy 参数,用于决定某种失败发生时应采用何种策略。好,这是 send_x_target 函数的功能。

然后是返回值,我想它返回一个 promise 列表即可,其中每个 promise 都是某个 socket 的处理结果,由调用者决定什么时机在 promise 上 wait 以取得相应的结果。
noli
2016-02-03 18:41:25 +08:00
@goool

返回一个 promise 列表并不是 send_x_target 的初衷:找出有哪些地址发不过去了。

我再具体一点来说吧,这个地址发不过去,从 connect 到 send 完成,是有几种可能的情况的:

1. 构造 socket 出错,例如因为 打开文件数太多之类的
2. connect 出错,可能是 Connection Refused 或 Timed out
3. send 出错,可能是等待回复超时,也可能是连接中断

然而,这里只有情况 2 里面的 Connection Refused ,才属于 “这个地址发不过去” (因为 Timed out 可能只是本机或者远程机器响应不过来)

3 这种情况,可能需要一点复杂的重试策略来完成任务
如果遇到 1 , 根本不在设计的范围内, send_x_target 也不知道该怎么办

我不知道 golang 要怎么弄才够简洁,如果是 C#,我会这样来表达:

https://segmentfault.com/n/1330000004411185
noli
2016-02-03 18:53:08 +08:00
@goool

我预计 go 的做法会一点都不 KISS
我期待你可以给我一点惊喜。
bombless
2016-02-03 19:44:16 +08:00
> 返回错误,和抛出异常,根本就是不一样的结构;既不是顺序也不是分支,而是跳转,而返回错误依然是一个顺序结构之中。

这个理解应该是有点问题的,异常也是一个逐步返回的过程,这个过程我们叫 unwinding

其实我不太理解这段回复想表达的要点是什么……

异常和返回错误的比较显著的差异在于它破坏了类型检查,你无法预期被调用者会抛出什么异常。你写代码的时候被调用者可能返回某些异常你都考虑到了,等被调用者被修改后这部分预期也许就不再成立。
它不像函数返回值那样是可以通过类型来限定然后通过工具(包括编译器)来检查。
即使是 Go 这样比较弱的类型系统都可以受益于通过返回值来表达异常这个设计。
noli
2016-02-04 11:39:30 +08:00
@bombless 我不理解你想说的是什么。“无法预期调用者会抛什么异常” 这个难道是你说的 “破坏了类型检查的原因?

可是异常之所以叫做异常,就是因为它存在  没有被捕获 的可能性,才叫异常的吧?异常没有被捕获就会在抛出点导致 crash ; 而 err 被意外地忽略了,然后继续执行,程序被预期以外的数据影响到别的地方的代码然后导致 crash ,这种情况不是更应该被警惕吗?

“返回错误,和抛出异常,根本就是不一样的结构;既不是顺序也不是分支,而是跳转,而返回错误依然是一个顺序结构之中。”

这句话你可以这么来理解: 在一个 try catch block 中间的代码,你可以认为是用一个独特的 goroutine 来运行的;这个 goroutine 与 try catch block 之外的上下文之间有一个 channel 用来通知异常。所以,当异常发生的时候, try catch block 中间的代码只是被中止了执行并没有自行退栈,而异常被传递了出去;返回 err 则不一样,函数被认为已经完成了随着 return 会退栈。

在局部上,你可以认为,在 try catch block 之间的每一行代码之间,编译器都会自动地加上等价于 golang 的 if err != nil { notify_exception(...) } 这样的代码(事实上 C++ 的编译器就是用类似的原理来实现的,会记住代码帧执行到哪一步) —— 或许这也是某些人认为 返回 err 和 try catch 除了手工增加防御性的代码之外,并没有本质的区别 ,然而 ——

notify_exception 把控制权交给了上层代码,这个上层代码并不仅仅是 try catch block 所在的上下文代码,还包括了调用栈上更靠近栈底的所有函数帧上的代码,就是说,那个用于通知异常的 channel 的 scope 是跨越 多个调用层次的;而没有 exception handling 机制的语言,例如 go 例如 C ,要做到这样的效果,就必须在相关的代码上层层防御——有没有嗅到一阵代码耦合的气味?

用 C 反而会更安全一点,因为 C 只能返回一个值,而调用者 *总*是* 有责任去判断这个返回值是否符合预期,譬如 bsd socket 的 recv 返回类型是 ssize_t , recv 的简单形式是告诉调用者接收了多少个字节(理应是非负数)然而 ssize_t 也说明了它会返回负数值 (异常发生),在这种情况下,其实就是一个弱化版的 checked exception 也就是非常接近 Java 的那种做法的本质——你*应该* 在调用的现场就处理好所有的异常,所以层层防御并没有在 C 里面成为现实,但即使是这样, C 代码依然非常容易耦合抽象程度很低。而 Java 的 checked exception 得益于其标准库的完备性,在编译的时候就已经强制保证了所有 exception 都要正确地被 catch 。 C 还是要看程序员个人修养。

然而 golang 用多值返回来描述异常,就打破了这种类似于 checked exception 隐含的强制性。多了一个 err 返回出来,就意味着懒惰的程序员总是有办法把责任往上扔,并且不怎么影响函数的返回值的设计,因为可以多值返回嘛——这跟懒惰的程序员总是接住所有的 exception 又不处理简直是一样的;

你甚至可以自己去看看 golang 自己的官方例子,产生了一个 err 之后,有什么东西可以阻止偷懒程序员或者做得昏头昏脑的程序员,继续去使用无意义的返回值去做任何事情吗?难道把 err print 出来事情就完了吗?

更坏的是,经过多层往上推卸责任之后,上层代码已经无法知道 扔上来的 err 到底是个什么鬼了——越是靠近调用栈栈底的函数,这个麻烦就越大,因为调用链越长,可能调用到的其他函数的范围越大。这个时候你必须猜这个 err 的所有可能性,想小心翼翼地处理这个 err 只能看运气,遗漏了处理某些具体类型的 err 的风险总是存在。
从这个角度来说, C++ 可以 throw 任何东西上去实际上也不是什么好的设计,尽管 C++ 还可以用 catch(...) 的语法来保证接住抛上来的任何东西,在需要阻止异常扩散的时候还有最后一招。

如果使用类似 C# python 等等的这种异常机制,首先是解除了处理 err 的耦合,并且总是保证,如果异常没有被恰当的接住就立即 crash , crash 的时候还可以保留现场,告诉 debug 的人是哪里导致的 crash ( golang 是做不到的,想好好地 log error 只能靠所有人都自觉),实际上这样才能更严格的要求调用者考虑清楚异常的类型;如果写的时候没有考虑清楚,那么跑的时候总是会的。

就算懒人程序员用类似于 catch(...) 这样的方式来偷懒绕过所有的检查和避免 crash ,它也没有办法把错误数据的影响扩散到其他地方,不会危害到其他地方的代码安全性。

综上所述, golang 的多值返回 err 的设计,就是事情没干得更好,倒是更容易让人做出更坏的事情出来——简直就是开历史的倒车。
noli
2016-02-04 11:51:27 +08:00
想起 @codeaqua 说 try catch 会破坏正常代码的 flow ,我想想也是笑了,好像 golang  的 defer 不会改变 flow ? python 的 yield 难道也不改变 ?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/253629

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX