β

为什么 Go 语言如此不受待见?

程序师 46 阅读

有人问:

在 Quora 上,有个问题是比较 D/Rust/Go/Nim 等语言的表现,几乎一致地认为 Go 是最搓的,Rust 备受好评。各位看看何解?
Of the Emerging Systems Languages Rust, D, Go and Nimrod, Which Is the Strongest Language and Why?
他们有一个观点,能够直接操作硬件的才被定义为系统级语言,而另外定义是适用于 web 后端或者分布式。Go 由于其 gc 而被直接否定。

北南(Twitter核心服务组后端程序员):

其实go不管在国内还是国外已经很受待见了,国外google用的很多,uber也在用,国内有著名的今日头条,每日千亿级的访问妥妥的。多少语言终其一生都没有这么大的应用场景。而且go不能算system programming language了吧,我的理解是system programming language你总得能和c互动吧,或者题主说的直接操作硬件(我换个措辞,cgo是不建议在应用开发中使用的,所以说现在的go是不鼓励你和c互动的。虽然使用cgo是能够和c互动的,但能不能和要不要是两个概念,我建议是不要,使用cgo是得不偿失的。这个很多文章都有讨论,有兴趣大家可以自己搜)

go最坚持最核心的理念是简单和正交性(这也是我最想从go的设计中吸取经验的地方),所有其他东西包括性能以及大家最津津乐道的并发性都向这两点妥协。我看前面有个谷歌工程师的答主说它的目标是适合大团队开发,能解决google以前在C++开发(我觉得这里的C++开发应该是应用开发,而不是题主说的system programming)中遇到的问题。这才是go出世的真正目的。

至于答主们所说的范型也好,错误返回也罢,这都不在go的最高优先级上吧。你们说丑,人家也没说要把自己弄的漂亮。官方说,你们可以用别的方式来优雅的实现要用范型的地方。我是挺敬佩那帮大哥的,搞了这样一个语言,明知被骂也敢发布,但我支持他们坚持自己的信仰。

Rust/D/Nim都是正经(一直凉凉的)的系统编程语言,拿这哥仨出来和go比也没啥意思。我觉得有志之士应该拿 。net core出来和go比划比划。


应回复里的兄弟要求,稍微说一下正交性(Orthogonality)

这个词还挺时髦的,就是说每个模块之间互相不影响,或者说互相不知道。改了一个不会影响另外一个。那为什么说go正交性好呢?除了go那些基本库做的都比较简单和职责明确以外,主要是go interface的设计。

做过go的都知道,你没别的东西可以用啊。你只能让interface在模块之间传来传去。

如果我们用其他静态类型语言,往往我们要调用什么其他模块的方法,我们要使用对应的类型,比如说其他模块要传入一个Class AAA with Inteface BBB,我们就要作出一个这样的类型的对象实例给它。这期间难免要依赖AAA和BBB里的不少东西,至少我要知道有AAA和BBB的存在。

class MyClass extends AAA implements BBB {
   int aaa(){...}
   int bbb(){...}
}

go的话,你就照着其他模块的要求,做一个和它interface长得一模一样的就行。这个耦合就松了。

type MyClass interface { 
    aaa() int  ///AAA里要的方法
    bbb() int  ///BBB里要的方法
} 

所以模块间不是用传统的类型耦合,是靠长相耦合,你长得和我一样,你就是我了,而且你都不需要知道你是长得和我像,咱俩就正交了。再比如说只要提供httpHandle函数的,就是个HttpHandler,它自己都不需要知道自己是个HttpHandler。

这样做也是有得有失,仁者见仁智者见智吧,go正交性的目标我同意,但是做法。。。我觉得还可以再考虑考虑。


虽然我觉得scala在后端才真是单刀在手,天下我有。不过要说谁是java之后的下一代后端当家花旦,我投go一票。

luikore

Rust 和 Nim 确实好呀

Rust 可以说是 D 语言二代目, 没有 D 里的一些经验主义设计, 而且更函数式, 作为 a better C++ 当之无愧. Pattern matching, Block, Generic 这些东西, Go 有么? 不好的地方是集成 feature 略贪心, 指针那么多类型是有道理但是学习者容易被吓跑.

Nim 不是函数式的, 但 Nim 支持卫生宏, 可以做 AST 重写, 可以自定编译规则, 是静态语言中的黑客语言有木有! 自定编译规则甚至可以编译出比 C 代码还快的结果, 作为 a better C 当之无愧. 人家 GC 可以手动步进的啊, 想要什么 feature 自己加(list comprehension? 没问题), 加个 const 就可以做编译期计算了(想想 C++ 和 D 里复杂难以掌握的 template 和 static if 多蛋疼), 改写 AST 的 pattern language 也是简单易懂(想想 Java 的 annotation processing tool 怎么用的就蛋碎…), 更重要的一点: 没有那么多哲学骑着你禁止你怎么怎么做, Go 能么?

人类思维有个巨大的缺点就是从众定势, 当然社区大了开发者多了语言会更容易成熟和变得实用, 但如果更多人懂得多了解学习, 理性比较而不是跟风, 现在的编程语言可以发展得更好.

布丁@kyhpudding

回@Irons Du, 不是为了反对他的答案,是因为基于这些问题,能解释 Go 不受待见的其中一个原因:Go 面对的问题和整个解决思路跟 Google 软件的规模相关,而这个规模是罕见的。20 亿行源码放在一个统一的代码库,全部主干提交,理论上代码库里任何两点都可以互相调用,几万个工程师(我是其中一名猪队友)在全球各个时区协同。 Why Google Stores Billions of Lines of Code in a Single Repository . 这个设计前提导致了同样被黑得很惨的 C++ Style Guide ( The Philosophy of Google’s C++ Code ) 和 Go 的一些看着很奇葩的设计点。

另外这个软件规模是一个 问题 ,不是什么值得炫耀的东西。Go 为解决这些问题的设计,并不一定适合其他场景。回答这个问题本身,这里也不是说 Go 有多好,只是可以分享一些「为什么」,很有趣的设计权衡,例如 Quora 原文提到的 Go 几乎忽略了所有现代 PL 研究成果,但实际上这些成果在这个规模上还没能很好地工作。关于 Go 为什么是(或不是)系统编程语言的问题,我可以另外写一篇。

1:你能轻松知道哪些struct继承(实现)了哪些interface么?

能,Go guru. 2016-talks/slides.pdf at master · gophercon/2016-talks · GitHub 在超大软件库上一样很顺。显式 implements 声明上规模后会有问题,多余的依赖关系是其中之一,下面有关于依赖的更详细讨论。

2:你能轻松知道struct有哪些”成员函数”么?

能,godoc 啊

这两点正好说明了 Go 极端重视工具的设计思路,工具能解决大代码库上源代码级别无法解决的问题,比如全代码库索引、重构,计算改动影响范围触发集成测试等等。这里面也必须有权衡,例如,要为这个规模写编译器和各种工具,你最好别搞复杂的类型系统,不然事情会很困难。

3:手动维护defer能比RAII轻松?

RAII 很难。C++ destructor + exception, 这里的 exception 包括处理 destructor 的异常安全和 destructor 自己真的需要抛异常的情况。还有如果在 destructor 里放了重型操作,比如 flush 硬盘,defer 至少让你清楚地看到这种重操作会在哪里跑。这些问题当然都可以用很仔细的设计避免,但是在有几万个猪队友的时候,不要指望每个人都能做出好设计。

4:package只有一个层次

如果是指不能只 import 一个父节点而要显式 import 所有叶子节点。这是用来控制 dependency 的,不必要的 dependency 在大软件库是个严重问题。Go 奇葩的 import 多余 package 直接编译错误的规则也是这个目的。

5:访问控制只能限定在package之外。

个人体验,它省掉了很多语法规则,还工作得很好。有点不方便的是你看它 call 一个私有函数,但是在同一个文件里是找不到这个函数的定义的,它可能在同一个 package 的另一个文件里。这个是用工具补足的 —— 在内部的 code search 工具里我没感觉不方便,在 github 没有交叉引用的情况下看代码就比较郁闷。

6:基于源代码的开发(复用),这是否违背了以前书上说的实现隐藏(只暴露接口)?

没,主要是因为 Google 统一代码库,Go 一开始压根没考虑二进制库发布的问题。这跟软件工程的隐藏实现是两回事。依赖版本管理问题同理,因为统一代码库+全主干提交,这个问题在 Google 是不存在的…… 当然问题就是问题,现在外部使用越来越多他们也在逐步补锅了。

7:推崇error作为返回值是不对的。另外(panic+recover)对比下C++在C之上添加的异常处理(+RAII)的类型安全

推荐一篇微软 Midori 项目 (Rethinking the software stack) 语言 leader 的 Joe Duffy – The Error Model (超长)。error 功能不够好,但 C++ 和 Java 的 exception 机制在上规模后也有无法解决的工程和性能的问题,Optional是好,但是语言就要变复杂,这里面有 tradeoff. 另外,「异常安全」是个看起来遵守规则写就可以的简单事情,但实际上非常困难,比如事务的回滚,文中也有专门描述。

Shisoft Architect

因为 Go 语言的确是最搓的

就连 Go 语言最引以为傲的并发模型, 在某些语言里也就是一个库就能解决的事情( clojure/core.async )。

语言就要做好语言该做的事情。语言特性太弱,runtime 不够轻量,没办法做 system language 也是很正常的事情。也怪不了写 C/C++ 和 Rust 的人看不上它。

我第一次看到一个现代通用,宣称自己是 static typing 的编程语言下的第三方容器库清一色拿 string 做 key,interface{} 做 value 是很傻眼的。给我一种喜欢写 go 的都是之前习惯写 php 和 javascript 的错觉。

Go 语言社区最大的问题在于自身语言特性弱还给开发人员强加设计哲学,并宣称这种做法是绝对正确的,你们只要无脑去用就可以了。静态语言里我没见过哪个语言的 map 和 list 是内置的,并且还会帮你隐藏后面的实现算法。你如果需要一个特定的数据结构容器,只能去忍受一次次的类型转换。然后就又有一群人说 interface{} 用的多是写的人太搓。humm…那你告诉我这个项目( mijia/gopark )里满天飞的 interface{} 按照 Go 语言的设计哲学怎么消掉保证安全性,并且还不会有运行时的性能损失。

其他的。。。话说没有 enum 吗

所以 Go 语言也就是做做后端微服务之类劳动密集型的应用,还没到可以和 java 现在的核心应用竞争的程度。

祭阿泣

几个实际例子。

goroutine泄漏。goroutine虽然方便,但是粗心的开发者用爽了之后,会滥用goroutine,导致和内存泄露类似的问题(再搞一个垃圾goroutine回收机制?2333)。

官方包bug。之前用cgo封了个很简单的HttpClient给C调用,go1.5的gc在C程序中不完美,导致有时链接不会正常释放,程序还会时不时hang在os.yield上。go1.6更新日志的第一句就是cgo完美支持gc,但是我试了一下这个问题依然存在。还有什么不支持低版本ld(去go源码中注释掉一行代码,然后重新编译go就能“支持”了)。

官方包文档。http包,transport,client这些类如果想要自己订制一些功能,那么最好要搞清楚什么类开了什么goroutine,是不是端口需要手动close,goroutine有没有shutdown。这些文档里没有说。

风格。我真的很不喜欢写一句话就写n句if err!=nil{…},而且写完之后还要想尽办法去构造单元测试来覆盖这个分支。如果你觉得这个地方实在是不会出现err,那么就用_来吃掉这个err吧,然而以后每个看代码的人都会看到这个_,然后想想为什么这里可以吃掉这个err,浪费时间不喜欢。太过于严谨,需要更折中一些,个人觉得。

王岳楠

在一家省分行用go每天处理二千万条数据入数据仓库做数据分析,后台用oci连oracle,前台web只用了gorilla的mux库,三个月来系统很稳定,数据处理速度及报表交互速度很快。做过一个与核心对接的交易查询系统,使用cgo与核心中间件对接,也很稳定,数据库mysql。还用go做了一个预算管理、一个服务器资源监控系统和一个基于activity的workflow,做完的感觉就是我今后很长时间还是会用go。以前做项目用过java,Python。Java的缺点是语法太啰嗦,虚拟机还得调优。Python不能充分利用多核,部署也麻烦。golang优点很明显:语法像Python一样灵活优雅,后期运维部署简单方便到让我感动(经过python部署折磨后),没有泛型之类的语法我也基本把上述系统的业务逻辑都完成了,我不是个语言控,语言不必大而全,复杂了我也玩儿不转,也不需要语法糖对别人炫耀(时间长了我也看不懂),对于我来说就是把系统业务逻辑快速完成,开发部署越简单越好,系统一定要稳定。golang满足了我一切要求,真的有它就go了。

Irons Du

1:你能轻松知道哪些struct继承(实现)了哪些interface么?
2:你能轻松知道struct有哪些”成员函数”么?
3:手动维护defer能比RAII轻松?更安全?怕不怕顺序问题?怕不怕写露了?而且是函数作用域的。
4:package只有一个层次,容易出现冲突。
5:访问控制只能限定在package之外。而且没有static 函数等等。
6:基于源代码的开发(复用),这是否违背了以前书上说的实现隐藏(只暴露接口)?
7:推崇error作为返回值是不对的。另外(panic+recover)对比下C++在C之上添加的异常处理(+RAII)的类型安全, defer 也没有显式的try catch直观和精确控制。
8:go是入门简单,学好难。难在多线程编程。Go能更容易写出多线程程序。但对于一个需求明确的任务,我并不觉得通过仔细斟酌设计的C++多线程程序比Go差,反而更好,很多地方更容易控制。当然这需要能力。但既然没得能力,我相信同样也写不好Go的多线程程序。

—————–

ps,前[1-5]对于小项目问题不大。

冒泡

只说我个人对go的意见,由于用得不深可能有些说得不对的地方

语言设计虽然要有创新,但语法这种东西搞太多创新反而会提高学习成本,比如在C++和go中切换一阵子,常常写错string s和s string,当然这是一个喜好问题,花点力气转过来也不是困难

语法过于封闭和霸道,譬如map、range这种可以作为一个库或方法的都实现为关键字,当然,你说这些常用,那没啥问题,但是我们能不能对于自定义的结构,定义它自身的hash、eq来作为map的key,以及能不能定义自己的方法让它可以被range呢,貌似没找到办法,不是很确定,如果有请告诉我呀

非入侵接口是有些优势,但会带来比较大的效率损耗,虽然1.7出来后整体速度提了一截,但用不用interface的比对还是差距比较大的,另外这个设计本身的一些缺陷网上也有文章专门叙述,略了

没泛型,比如,怎么定义一个可以和自身比较的类型的通用接口呢,即类似Java中<T extends Comparable<T>>这种,如果用interface的话,貌似不能限定“只能和自身类型”,那只好运行时动态检查?我因为这个问题,看了sort库的代码,发现是从另一个角度绕过的,而且sort这么搞只能有效支持类似数组的结构了,那我想写个通用红黑树怎么设计接口呢,感觉比较困难

错误处理很多人吐槽过了。。。其实我倒是没那么强烈的意见,就是写一些小脚本太啰嗦

cgo的性能,大部分语言的C扩展很多时候是用来改写核心模块而提高效率,go却不是,cgo的设计是有自己的苦衷,但能不能提供一种不走环境切换的选择呢

曹东

每种语言都有适用场景,不太好同维度对比。

go的gc确实是一大痛点。我们的项目中,就单独做了定制化的gc优化,确实是一件头痛的事,一点都不gopher。但,go很年轻,也一直在迭代,进步也是大家有目共睹的。八月份马上要放出1.7release了,在gc上做了进一步优化。

go的卖点:1,goroutine(同步方式编写异步代码,so easy);2,轻松高并发;3,少即指数级的多(语法简单,但组合出的变化却很多)4,开发效率高;5,编译速度快、部署简单(以前静态连接是一个卖点,后面版本开始支持动态链接库后走偏了);6,亲爹支持。

其实go的社区还是很活跃的,国内我所知的,很多公司开始将后台、中间件迁移到go了,像百度的BFE7层接入系统、360的长连接推送系统、美团的广告中间件、滴滴的认证系统、七牛云平台、链家、vmware、uber中国。。。名单会很长很长

姜太公

因为在做Docker相关的事情,整个Docker生态基本上都是Go语言的,也不得不跟着使用Go。有几个方面确实觉得很不爽

首先是错误处理,由于没有异常机制,只能写大量的if err != nil。代码里充斥着这种判断。实际上很多时候我们并不需要出错之后进行恢复,处理不了,只想把错误往上抛。比如读文件,用Python我可以这样写

with open('file') as f:
    data = f.read()

文件不存在怎么办?io错误怎么办?大多数时候我们没办法原地处理错误,只能继续上抛,让调用方处理。用Go怎么写?

var f os.File
if f, err := os.Open("file"); err != nil {
    return err
}
var data []byte
if data, err := ioutil.Read(f); err != nil {
    return err
}

其次是出错时候的错误信息,不想Java/Python,异常信息里包含了运行栈,Go的error就是一个普通接口,通常只有一句简单错误信息。看到错误日志里的信息,不去搜代码你都不知道哪个地方出的错。

还有坑爹的包管理,这个前面也有人吐槽了。强制的目录结构也挺坑爹的。当初我想给自己的Go项目加一个自动构建的脚本,我在项目里使用了godep,自带所有的依赖,这样别人从github clone代码到本地之后运行./build就能构建了。解决同时clone之后build出错!我过去一看,依赖倒是没问题,项目本身的包找不到!Go要求代码必须放在$GOPATH/src/下面,比如我的项目必须放在$GOPATH/src/ http://github.com/xxxx/hello 目录下。

接口的实现方式,由于只要实现了所有的方法就算实现了接口,不用显式声明,所以光看代码根本没法找到到底实现了哪个/哪几个接口。

黄川

个人觉得不是不受待见, 而是人自身的问题:

第一:
现在招聘GO语言的工作很少,从大环境来看就是这样,七牛算一个,然并卵,我不会因为一份工作去七牛所在的地区工作。

第二:
现在Java还是大行其道,大的技术公司主要编程需要还都是java或者C++,现在又有node这种怪胎(不要觉得我碰node,为什么node这么火,还不是入门门槛低,前端都会写,但是软件工程,系统架构有几个人懂,能写出好代码才怪,写点小工具或者小型网站还是马马马虎虎的,毕竟快,让我们写后端的童鞋写一个网站没几个月写不出来,界面不会写啊,让我哭一会),拉回来,一家公司想要转新的语言,是多么大的风险,没有冲劲的领导不敢干这事,所以Go始终还是小众语言的存在,但是随着Docker的普及,慢慢地大家也开始关注GO了,这是好事,还有微服务的推广,跨语言的业务开发极大的帮助了Go的推广。但是现阶段还是安心写Java吧。

第三:
我们知道业务代码再往下下就倒系统了,GO对系统API的调用做得很好,直接syscall调用,可以联编C代码,多好啊,但是你会么,或者说看这篇文章的人里面有几个人自己研究过Linux或者Freebsd的底层接口,退一步,除了写业务代码之外沉下心研究过操作系统底层的有几个?对于分布式原理的理解的有几个?(吹牛逼的自己闭嘴,我说的分布式原理,不是别的网站里面看几篇文章就说自己懂分布式)还有锁,分布式锁等概念,在并发编程里面都是需要注意的,有几个人愿意花时间在这上面。

所以,不是Go不受待见,而是人自己固步自封,你必须承认Go就是比java快,就是比PHP快,能做的事情更多,系统资源的使用更加淋漓尽致,虽然比不上C与C++,但是也算接近,而且编译更快,自带内存管理,语法清晰。但是,你还是更喜欢把时间花在打游戏上,另外,喜欢就是喜欢,管别人待不待见Go呢。

作者:程序师
用程序师的眼光看世界
原文地址:为什么 Go 语言如此不受待见?, 感谢原作者分享。

发表评论