驳2B文 "我为何放弃Go语言"

 

 

 

此篇文章流传甚广, 其实里面没啥干货, 并且里面不少观点是有问题的. 这个文章在 golang-china 很早就讨论过了. 最近由于 Rust 1.0 和 1.1 的发布, 致使这个文章又出来毒害读者. 因此写了这篇反驳文章, 指出其中的问题.javascript

原文连接:http://blog.csdn.net/liigo/article/details/23699459php

有好几回,当我想起来的时候,老是会问本身:我为何要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题。java

开门见山地说,我当初放弃Go语言(golang),就是由于两个“不爽”:第一,对Go语言自己不爽;第二,对Go语言社区里的某些人不爽。毫无疑问,这是很是主观的结论。可是我有足够详实的客观的论据,用以支撑这个看似主观的结论。nginx

文末附有本文更新日志。c++

确实是很是主观的结论, 由于里面有很多有问题的观点(用来忽悠Go小白还行).git

第0节:个人Go语言经历

先说说个人经历吧,以免被平白无故地看成Go语言的低级黑。程序员

2009年末,Go语言(golang)第一个公开版本发布,笼罩着“Google公司制造”的光环,吸引了许多慕名而来的尝鲜者,我(Liigo)也身居其中,笼统的看了一些Go语言的资料,学习了基础的教程,因对其语法中的分号和花括号不满,很快就遗忘掉了,没拿它当一回事。github

在2009年Go刚发布时, 确实是由于“Google公司制造”的光环而吸引了(包括文章做者和诸多IT记者)不少低级的尝鲜者. 还好, 通过5年的发展, 这些纯粹由于光环来的投机者所剩已经很少了(Google趋势). 目前, 真正的Go用户早就将Go用于实际的生产了.golang

说到 其语法中的分号和花括号不满, 我想说这只是你的 我的主观感觉, 还有不少人对Go的分号和花括号很满意, 包括水果公司的的 Swift 的语言设计者也很满意这种风格(Swift中的分号和花括号和Go基本相同).正则表达式

若是只谈 我的主观感觉, 我也能够说 Rust 的 fn 缩写也很蛋疼!

两年以后,2011年末,Go语言发布1.0的计划被提上日程,相关的报道又多起来,我再次关注它,从新评估以后决定深刻参与Go语言。我订阅了其users、nuts、dev、commits等官方邮件组,坚持天天阅读其中的电子邮件,以及开发者提交的每一次源代码更新,给Go提交了许多改进意见,甚至包括修改Go语言编译器源代码直接参与开发任务。如此持续了数月时间。

这个到是事实, 在 golang-china 有很多吵架的帖子, 感兴趣的能够去挖下, 我就不展开说了.

到2012年初,Go 1.0发布,语言和标准库都已经基本定型,不可能再有大幅改进,我对Go语言未能在1.0定型以前更上一个台阶、实现自我突破,甚至带着诸多明显缺陷走向1.0,感到很是失望,于是逐渐疏远了它(因此Go 1.0以后的事情我不多关心)。后来看到即将发布的Go 1.1的Release Note,发现语言层面没有太大改变,只是在库和工具层面有所修补和改进,感到它尚在幼年就失去成长的动力,愈加失望。外加Go语言社区里的某些人,其中也包括Google公司负责开发Go语言的某些人,其态度、言行,让我极度厌恶,促使我决绝地离弃Go语言。

真的不清楚楼主说的能够在 Go1.0 以前短期内能实现的 重大改进和诸多明显缺陷 是什么.

若是是楼主说前面的 其语法中的分号和花括号不满 之类的重大改进, 我只能说这只是你的 我的主观感觉 而已, 你的不少想法只能说服你本身, 没办法说服其余绝大部分人(不要觉得像C++或Rust那样什么特性都有就NB了, 各类NB特性加到一块儿只能是 要你命3000, 而绝对不会是什么 银弹).

Go 1.1的Release Note,发现语言层面没有太大改变. 语言层没有改变是是由于 Go1 做出的向后兼容的承诺. 对于工业级的语言来讲, Go1 这个只能是优势. 若是连语言层在每一个版本都会出现诸多大幅改进, 那谁还敢用Go语言来作生产开发呢(我认可Rust的改动很大胆, 但也说明了Rust还处于比较幼稚和任性的阶段)?

说 Go语言社区里的某些人执拗 的观点我是赞成的. 可是这些 执拗 的人是能够讲道理的, 可是他们对不少东西的要求很高(特别是关于Go的设计哲学部分). 只要你给的建议有依据(语言的设计哲学是另一回事情), 他们绝对不会盲目的拒绝(只是讨论的周期会比较长).

关于楼主提交的给Go文件添加BOM的文章, 须要补充说明下.

在Go1.0发布的时候, Go语言的源文件(.go)明确要求必须是UTF8编码的, 并且是无BOM的UTF8编码的(G公司的Protobuf也不支持带BOM的UTF8编码).

注意: 这个 无BOM的UTF8编码 的限制仅仅是 针对 Go语言的源文件(.go).

这个限制并非说不容许用户处理带BOM的UTF8的txt文件!

我以为对于写Go程序来讲, 这个限制是没有任何问题的, 到目前为止, 我还历来没有使用过带BOM的.go文件.

不只是由于带BOM的.go文件没有太多的意义, 并且有不少的缺陷.

BOM的原意是用来表示编码是大端仍是小端的, 主要用于UTF16和UTF32. 对于 UTF8 来讲, BOM 没有任何存在的意义(正是Go的2个做者发明了UTF8, 完全解决了全球的编码问题).

可是, 在现实中, 由于MS的txt记事本, 对于中文环境会将txt(甚至是C/C++源文件)看成GBK编码(GBK是个烂编码), 为了区别究竟是GBK仍是UTF8, MS的记事本在前面加了BOM这个垃圾(被GBK占了茅坑), 这里的bom已经不是表示字节序本意了. 不知道有没有人用ms的记事本写网页, 而后生成一个带bom的utf8网页确定颇有意思. 这是MS的记事本的BUG: 它不支持生成无BOM的UTF8编码的文本文件!

这些是现实存在的带BOM的UTF8编码的文本文件, 可是它们确定都不是Go语言源文件!

因此说, Go语言的源文件即便强制限制了无BOM的UTF8编码要求, 也是没有任何问题的(并且我还但愿有这个限制).

虽而后来Go源文件接受带BOM的UTF8了, 可是运行 go fmt 以后, 仍是会删除掉BOM的(由于BOM就是然并卵). 也就是说 带 BOM 的 Go 源文件是不符合 Go语言的编码风格的, go fmt 会强制删除 BOM 头.

前面说了BOM是MS带来的垃圾, 可是BOM的UTF8除了然并卵以外还有不少问题, 由于BOM在string的开头嵌入了垃圾, 致使正则表达式, string的连接运算等操做都被会被BOM这个垃圾所污染. 对于.go语言, 即便代码彻底同样, 有BOM和无BOM会致使文件的MD5之类的校验码不一样.

因此, 我以为Go用户不用纠结BOM这个可有可无的东西(语言源文件不是文本编辑器, 不必支持各类文件格式).

在上一个10年,我(Liigo)在我所属的公司里,深度参与了两个编程语言项目的开发。我想,对于如何判断某个编程语言的优劣,或者说至少对于如何判断某个编程语言是否适合于我本身,我应该仍是有一点发言权的。

第1节:我为何对Go语言不爽?

Go语言有不少让我不爽之处,这里列出我如今还能记起的其中一部分,排名基本上不分前后。读者们耐心地看完以后,还能淡定地说一句“我不在意”吗?

1.1 不容许左花括号另起一行

关于对花括号的摆放,在C语言、C++、Java、C#等社区中,十余年来存在持续争议,从未造成一致意见。在我看来,这原本就是主观倾向很重的抉择,不违反原则不涉及是非的状况下,不该该搞一刀切,让程序员或团队本身选择就足够了。编程语言自己强行限制,把本身的喜爱强加给别人,得不偿失。不管倾向于其中任意一种,必然得罪与其对立的一群人。虽然我如今已经习惯了把左花括号放在行尾,但一想到被禁止其余选择,就感到十分不爽。Go语言这这个问题上,没有作到“团结一切能够团结的力量”不说,还有意给本身树敌,太失败了。

我以为Go最伟大的发明是 go fmt, 今后Go用户不会再有花括弧的位置这种无聊争论了(固然也少了很多灌水和上tiobe排名的机会). 只给用户一条路,不给任何走歧途的机会, 确保正确、高效。

是这优势, Swift 语言也使用和 Go 相似的风格(固然楼主也可能鄙视swift的做者).

1.2 编译器莫名其妙地给行尾加上分号

对Go语言自己而言,行尾的分号是能够省略的。可是在其编译器(gc)的实现中,为了方便编译器开发者,却在词法分析阶段强行添加了行尾的分号,反过来又影响到语言规范,对“怎样添加分号”作出特殊规定。这种变态作法前无古人。在左花括号被意外放到下一行行首的状况下,它自动在上一行行尾添加的分号,会致使莫名其妙的编译错误(Go 1.0以前),连它本身都解释不明白。若是实在处理很差分号,干脆不要省略分号得了;或者,Scala和JavaScript的编译器是开源的,跟它们学学怎么处理省略行尾分号能够吗?

又是楼主的 我的主观感觉, 不过我很喜欢这个特性. Swift 语言也是相似.

1.3 极度强调编译速度,不惜放弃本应提供的功能

程序员是人不是神,编码过程当中免不了由于大意或疏忽犯一些错。其中有一些,是你们集体性的很容易就中招的错误(Go语言里的例子我暂时想不起来,C++里的例子有“基类析构函数不是虚函数”)。这时候编译器应该站出来,多作一些检查、约束、核对性工做,尽可能阻止常规错误的发生,尽可能不让有潜在错误的代码编译经过,必要时给出一些警告或提示,让程序员留意。编译器不就是机器么,不就是应该多作脏活累活杂活、减小人的心智负担么?编译器多作一项检查,可能会避免数十万程序员从此多年内无数次犯一样的错误,节省的时间不可胜数,这是功德无量的好事。可是Go编译器的做者们可不这么想,他们不肯意本身多花几个小时给编译器增长新功能,以为那是亏本,反而减慢了编译速度。他们以影响编译速度为由,拒绝了不少对编译器改进的要求。典型的因噎废食。强调编译速度当然值得赞扬,但若是所以放弃应有的功能,我不同意。

编译速度是很重要的, 若是编译速度够慢, 语言再好也不会有人使用的. 好比C/C++的增量编译/预编译头文件/并发编译都是为了提升编译速度. Rust1.1 也号称 比 1.0 的编译时间减小了32% (注意: 不是运行速度).

固然, Go刚面世的时候, 编译速度是其中的一个设计目标.

不过我想楼主, 可能想说的是由于编译器本身添加分号而致使的编译错误的问题. 我以为Go中 { 不能另起一行是语言特性, 若是修复这个就是引入了新的错误.

其余的我真想不起来还有哪些 调编译速度,不惜放弃本应提供的功能 (不要提泛型, 那是由于尚未好的设计).

最重要是的保持Compiler的靠谱、精简、高效,而不是功能花哨,bug一堆。这样有利于作流水优化、指令集精减、易于跨平台、下降维护负担。

1.4 错误处理机制太原始

在Go语言中处理错误的基本模式是:函数一般返回多个值,其中最后一个值是error类型,用于表示错误类型极其描述;调用者每次调用完一个函数,都须要检查这个error并进行相应的错误处理:if err != nil { /这种代码写多了不想吐么/ }。此模式跟C语言那种很原始的错误处理相好比出一辙,并没有实质性改进。实际应用中很容易造成多层嵌套的if else语句,能够想想这个编码场景:先判断文件是否存在,若是存在则打开文件,若是打开成功则读取文件,若是读取成功再写入一段数据,最后关闭文件,别忘了还要处理每一步骤中出现错误的状况,这代码写出来得有多变态、多丑陋?实践中广泛的作法是,判断操做出错后提早return,以免多层花括号嵌套,但这么作的后果是,许多错误处理代码被放在前面突出的位置,常规的处理逻辑反而被掩埋到后面去了,代码可读性极差。并且,error对象的标准接口只能返回一个错误文本,有时候调用者为了区分不一样的错误类型,甚至须要解析该文本。除此以外,你只能手工强制转换error类型到特定子类型(静态类型的优点没了)。至于panic - recover机制,致命的缺陷是不能跨越库的边界使用,注定是一个半成品,最多只能在本身的pkg里面玩一玩。Java的异常处理虽然也有自身的问题(好比Checked Exceptions),但整体上仍是比Go的错误处理高明不少。

话说, 软件开发都发展了半个世纪, 仍是无实质性改进. 不要觉得弄一个异常的语法糖就是革命了.

我只能说错误和异常是2个不一样的东西, 将全部错误看成异常那是SB行为.

try..catch原理是jump/longjump,这种东西会增长底层复杂性,而且容易滥用,很差维护,并且可能会增长10W数量级别gorutine上下文swich负担。

正由于有异常这个所谓的银弹, 致使不少等着别人帮忙擦屁股的行为(注意 shit 函数抛出的绝对不会是一种类型的 shit, 而被其间接调用的各类 xxx_shit 也可能抛出各类类型的异常, 这就致使 catch 失控了):

int main() { try { shit(); } catch( /* 到底有几千种 Exception ? */) { ... } } 

Go的建议是 panic - recover 不跨越边界, 也就是要求正常的错误要由pkg的处理掉. 这是负责任的行为.

再说Go是面向并发的编程语言, 在海量的 goroutine 中使用 try/catch 是否是有一种不三不四的感受呢?

1.5 垃圾回收器(GC)不完善、有重大缺陷

在Go 1.0前夕,其垃圾回收器在32位环境下有内存泄漏,一直拖着不愿改进,这且不说。Go语言垃圾回收器真正致命的缺陷是,会致使整个进程不可预知的间歇性停顿。像某些大型后台服务程序,如游戏服务器、APP容器等,因为占用内存巨大,其内存对象数量极多,GC完成一次回收周期,可能须要数秒甚至更长时间,这段时间内,整个服务进程是阻塞的、停顿的,在外界看来就是服务中断、无响应,再牛逼的并发机制到了这里通通失效。垃圾回收器按期启动,每次启动就致使短暂的服务中断,这样下去,还有人敢用吗?这但是后台服务器进程,是Go语言的重点应用领域。以上现象可不是我假设出来的,而是事实存在的现实问题,受其严重困扰的也不是一家两家了(2013年末ECUG Con 2013,京东的刘奇提到了Go语言的GC、defer、标准库实现是性能杀手,最大的痛苦是GC;美团的沈锋也提到Go语言的GC致使后台服务间隔性停顿是最大的问题。更早的网络游戏仙侠道开发团队也曾受Go垃圾回收的沉重打击)。在实践中,你必须努力减小进程中的对象数量,以便把GC致使的间歇性停顿控制在可接受范围内。除此以外你别无选择(难道你还想本身更换GC算法、甚至砍掉GC?那仍是Go语言吗?)。跳出圈外,我近期一直在思考,必定须要垃圾回收器吗?没有垃圾回收器就必定是历史的倒退吗?(可能会新写一篇博客文章专题探讨。)

这是说的是32位系统, 这绝对不是Go语言的重点应用领域!! 我能够说Go出生就是面向64位系统和多核心CPU环境设计的. (再说 Rust 目前好像还不支持 XP 吧, 这可不能够算是影响巨大?)

32位当时是有问题, 可是对实际生产影响并不大(请问楼主仍是在用32位系统吗, 还只安装4GB的内存吗). 若是是8位单片机环境, 建议就不要用Go语言了, 直接C语言好了.

并且这个问题早就不存在了(你们能够去看Go的发布日志).

Go的出生也就5年时间, GC的完善和改进是一个持续的工做, 2015年8月将发布的 Go1.5将采用并行GC, 每次 "stop the world" 时间低于 10 毫秒, 具体请参考 GopherCon2015: Go GC: Solving the Latency Problem in Go 1.5.

关于GC的被人诟病的地方是会致使卡顿, 可是我觉得这个主要是由于GC的实现还不够完美而致使的. 若是是完美的并发和增量的GC, 那应该不会出现大的卡顿问题的.

固然, 若是非要实时性, 那用C好了(实时并不表示性能高, 只是响应时间可控).

对于Rust之类没有GC的语言来讲, 想很方便的开发并发的后台程序那几乎是不可能的.

不要老是吹Rust能代替底层/中层/上层的开发, 咱们要看有谁用Rust真的作了什么.

1.6 禁止未使用变量和多余import

Go编译器不容许存在被未被使用的变量和多余的import,若是存在,必然致使编译错误。可是现实状况是,在代码编写、重构、调试过程当中,例如,临时性的注释掉一行代码,很容易就会致使同时出现未使用的变量和多余的import,直接编译错误了,你必须相应的把变量定义注释掉,再翻页回到文件首部把多余的import也注释掉,……等事情办完了,想把刚才注释的代码找回来,又要好几个麻烦的步骤。还有一个让人蛋疼的问题,编写数据库相关的代码时,若是你import某数据库驱动的pkg,它编译给你报错,说不须要import这个未被使用的pkg;但若是你听信编译器的话删掉该import,编译是经过了,运行时必然报错,说找不到数据库驱动;你看看程序员被折腾的两边不是人,最后不得不请出大神:import _。对待这种问题,一个比较好的解决方案是,视其为编译警告而非编译错误。可是Go语言开发者很执拗,不允许这种折中方案。

这个问题我只能说楼主的吐槽真的是没水平.

为什么不使用的是错误而不是警告? 这是为了将低级的bug消灭在编译阶段(你们能够想下C/C++的那么多警告有什么卵用).

并且, import 即便没有使用的话, 是用反作用的, 由于 import 会致使多个init函数和全局变量的初始化,致使程序不可控. 若是某些代码没有使用, 为什么要执行 init 这些初始化呢?

若是是由于调试而添加的变量, 那么调试完删除不是很正常的要求吗?

若是是由于调试而要导入fmtlog之类的包, 删除调试代码后又致使 import 错误的花, 楼主难道不知道在一个独立的文件包装下相似的辅助调试的函数吗?

import (
	"fmt" "log" ) func logf(format string, a ...interface{}) { file, line := callerFileLine() fmt.Fprintf(os.Stderr, "%s:%d: ", file, line) fmt.Fprintf(os.Stderr, format, a...) } func fatalf(format string, a ...interface{}) { file, line := callerFileLine() fmt.Fprintf(os.Stderr, "%s:%d: ", file, line) fmt.Fprintf(os.Stderr, format, a...) os.Exit(1) } 

import _ 是有明确行为的用法, 就是为了执行包中的 init 等函数(能够作某些注册操做).

将警告看成错误是Go的一个哲学, 固然在楼主看来这是白痴作法.

1.7 建立对象的方式太多使人纠结

建立对象的方式,调用new函数、调用make函数、调用New方法、使用花括号语法直接初始化结构体,你选哪种?很差选择,由于没有一个固定的模式。从实践中看,若是要建立一个语言内置类型(如channel、map)的对象,一般用make函数建立;若是要建立标准库或第三方库定义的类型的对象,首先要去文档里找一下有没有New方法,若是有就最好调用New方法建立对象,若是没有New方法,则退而求其次,用初始化结构体的方式建立其对象。这个过程颇为周折,不像C++、Java、C#那样直接new就好了。

C++的new是狗屎. new致使的问题是构造函数和普通函数的行为不一致, 还有加不加(),行为不一致, 这个补丁特性真的没啥优越的.

我仍是喜欢C语言的 fopen 和 malloc 之类构造函数, 构造函数就是普通函数, Go语言中也是这样.

C++中, 除了构造不兼容普通函数, 析构函数也是不兼容普通函数. 这个而引入的坑有不少吧.

1.8 对象没有构造函数和析构函数

没有构造函数还好说,毕竟还有自定义的New方法,大体也算是构造函数了。没有析构函数就比较难受了,无法实现RAII。额外的人工处理资源清理工做,无疑加剧了程序员的心智负担。没人性啊,还嫌咱们程序员加班还少吗?C++里有析构函数,Java里虽然没有析构函数可是有人家finally语句啊,Go呢,什么都没有。没错,你有个defer,但是那个defer问题更大,详见下文吧。

defer 能够覆盖析构函数的行为, 固然 defer 还有其余的任务. Swift2.0 也引入了一个简化版的 defer 特性.

1.9 defer语句的语义设定不甚合理

Go语言设计defer语句的出发点是好的,把释放资源的“代码”放在靠近建立资源的地方,但把释放资源的“动做”推迟(defer)到函数返回前执行。遗憾的是其执行时机的设置彷佛有些不甚合理。设想有一个须要长期运行的函数,其中有无限循环语句,在循环体内不断的建立资源(或分配内存),并用defer语句确保释放。因为函数一直运行没有返回,全部defer语句都得不到执行,循环过程当中建立的大量短暂性资源一直积累着,得不到回收。并且,系统为了存储defer列表还要额外占用资源,也是持续增长的。这样下去,过不了多久,整个系统就要由于资源耗尽而崩溃。像这类长期运行的函数,http.ListenAndServe()就是典型的例子。在Go语言重点应用领域,能够说几乎每个后台服务程序都必然有这么一类函数,每每还都是程序的核心部分。若是程序员不当心在这些函数中使用了defer语句,能够说后患无穷。若是语言设计者把defer的语义设定为在所属代码块结束时(而非函数返回时)执行,是否是更好一点呢?但是Go 1.0早已发布定型,为了保持向后兼容性,已经不可能改变了。当心使用defer语句!一不当心就中招。

前面说到 defer 还有其余的任务, 也就是 defer 中执行的 recover 能够捕获 panic 抛出的异常. 还有 defer 能够在 return 以后修改命名的返回值.

上面2个工做要求 defer 只能在函数退出时来执行.

楼主说的 defer 是相似 Swift2.0 中 defer 的行为, 可是 Swift2.0 中 defer 是没有前面2个特性的.

Go中的defer是以函数做用域做为触发的条件的, 是会致使楼主说的在 for 中执行的错误用法(哪一个语言没有坑呢?).

不过 for 中 局部 defer 也是有办法的 (Go中的defer是以函数做用域):

for { func(){ f, err := os.Open(...) defer f.Close() }() } 

在 for 中作一个闭包函数就能够了. 本身不会用不要怪别人没告诉你.

Swift 的块级 defer 也不方便实现如下的场景:

func (t *T) Serve() {
    if debug { log.Println(t, "starting") defer log.Println(t, "exiting") } // stuff } 

Nigel Tao 给的 解释:

The longer answer is that while there's benefit of a scope-scoped defer, there's also benefit in a function-scoped defer. This code: func foo(filename string) error { var r io.Reader if filename != "" { f, err := os.Open(filename) if err != nil { return err } defer f.Close() r = f } else { r = strings.NewReader(fakeInput) } // More code that reads from r. etc } 

1.10 许多语言内置设施不支持用户定义的类型

for in、make、range、channel、map等都仅支持语言内置类型,不支持用户定义的类型(?)。用户定义的类型无法支持for in循环,用户不能编写像make、range那样“参数类型和个数”甚至“返回值类型和个数”均可变的函数,不能编写像channel、map那样相似泛型的数据类型。语言内置的那些东西,到处充斥着斧凿的痕迹。这体现了语言设计的局限性、封闭性、不完善,可扩展性差,像是新手做品——且不论其设计者和实现者如何权威。延伸阅读:Go语言是30年前的陈旧设计思想,用户定义的东西几乎都是二等公民(Tikhon Jelvis)。

说到底, 这个是由于对泛型支持的不完备致使的. 记得1.5之后能够自定义strct来支持 for,channel, map等。

Go语言是没啥NB的特性, 可是Go的特性和工具组合在一块儿就是好用.

这就是Go语言NB的地方.

1.11 没有泛型支持,常见数据类型接口丑陋

没有泛型的话,List、Set、Tree这些常见的基础性数据类型的接口就只能很丑陋:放进去的对象是一个具体的类型,取出来以后成了无类型的interface{}(能够视为全部类型的基础类型),还得强制类型转换以后才能继续使用,使人无语。Go语言缺乏min、max这类函数,求数值绝对值的函数abs只接收/返回双精度小数类型,排序接口只能借助sort.Interface无奈的回避了被比较对象的类型,等等等等,都是没有泛型致使的结果。没有泛型,接口很难优雅起来。Go开发者没有明确拒绝泛型,只是说尚未找到很好的方法实现泛型(能不能学学已经开源的语言呀)。现实是,Go 1.0已经定型,泛型尚未,那些丑陋的接口为了保持向后兼容必须长期存在着。

Go有本身的哲学, 若是能有和目前哲学不冲突的泛型实现, 他们是不会反对的.

若是只是简单学学(或者叫抄袭)已经开源的语言的语法, 那是C++的设计风格(或者说C++历来都是这样设计的, 有什么特性就抄什么), 致使了各类脑裂的编程风格.

编译时泛型和运行时泛型多是没法彻底兼容的, 看这个例子:

type Adder<T> interface { Add(a, b T) T } 

请问 Adder<int> 和 Adder<float> 是一个接口吗?

type Adder interface { Add(a, b interface{}) interface{} } 

对于这种场景, interface{} 虽然性能不是最好, 可是接口倒是一致的:

并且, 目前已经有 go generate 能够弥补范型和宏部分的不足.

golang-china 关于该文的讨论中有涉及到泛型的讨论.

感受Go即便真有泛型, 也得等到Go2.0了(猜想Go2.0能在2020年诞生10周年发布).

1.12 实现接口不须要明确声明

这一条一般是被看成Go语言的优势来宣传的。可是也有人不赞同,好比我。若是一个类型用Go语言的方式默默的实现了某个接口,使用者和代码维护者都很难发现这一点(除非仔细核对该类型的每个方法的函数签名,并跟全部可能的接口定义相互对照),天然也想不到与该接口有关的应用,显得十分隐晦,不直观。支持者可能会辩解说,我能够在文档中注明它实现了哪些接口。问题是,写在文档中,还不如直接写到类型定义上呢,至少还能获得编译器的静态类型检查。缺乏了编译器的支持,当接口类型的函数签名被改变时,当实现该接口的类型方法被无心中改变时,实现者可能很难意识到,该类型实现该接口的隐含约束事实上已经被打破了。又有人辩解说,我能够经过单元测试确保类型正确实现了接口呀。我想说的是,明明能够经过明确声明实现接口,享受编译器提供的类型检查,你却要本身找麻烦,去写本来多余的单元测试,找虐很爽吗?Go语言的这种作法,除了减小一些对接口所在库的依赖以外,没有其余好处,得不偿失。延伸阅读:为何我不喜欢Go语言式的接口(老赵)。

Go是面向组合的, 和UNIX的哲学相似. 使用Go你要知道 io 放的是什么, fmt 包放的是什么, 习惯以后会很方便.

你不能说UNIX的命令行工具sort没有实现强的接口依赖检测会有不少问题. 若是你非要乱用sort的捣蛋话固然有不少问题.

可是Go给想组合和合做的人使用的,组合优于继承.

不要提 老赵 那个文章了, 我发了反驳文章后他已经闭嘴了: http://my.oschina.net/chai2010/blog/122400

对于IDE环境, Go的工具 go oracle能够回答某类型实现了哪些接口这类问题.

1.13 省掉小括号却省不掉花括号

Go语言里面的if语句,其条件表达式不须要用小括号扩起来,这被做为“代码比较简洁”的证据来宣传。但是,你省掉了小括号,却不能省掉大括号啊,一条完整的if语句至少还得三行吧,人家C、C++、Java均可以在一行以内搞定的(能够省掉花括号)。人家还有x?a:b表达式呢,也是一行搞定,你Go语言用if else写至少得五行吧?哪里简洁了?

“代码比较简洁”, 谁告诉你是这个缘由了? 不懂别瞎说!

必须花括弧的缘由是C语言中 if else 的悬挂问题:

if(1) ....; if(2) ...; else ...; 

请问上面的 else 是属于哪一个 if 的?

必须加花括弧能够避免上面的问题.

而小括弧又不是必须的所以就去掉了(Swift一样用了Go的设计).

至于 x?a:b 虽然是简洁, 可是容易泛滥 (x?a:b)?(x?a:b):(x?a:(x?a:(...))).

Go不是由于 简洁 的 x?a:b 而禁止三元操做符, 而是为了防止泛滥使用而禁止三元操做符.

1.14 编译生成的可执行文件尺寸很是大

记得当年我写了一个很简单的程序,把全部系统环境变量的名称和值输出到控制台,核心代码也就那么三五行,结果编译出来把我吓坏了:EXE文件的大小超过4MB。若是是C语言写的一样功能的程序,0.04MB都是多的。我把这个信息反馈到官方社区,结果人家不在意。是,我知道如今的硬盘容量都数百GB、上TB了……可您这种优化程度……怎么让我相信您在其余地方也能作到不错呢。(再次强调一遍,我全部的经验和数据都来自Go 1.0发布前夕。)

C语言的0.04MB程序若是崩了(Windows64环境TDM-GCC生成128KB), 你就只能知道它崩了.

而Go1.0的4MB程序若是崩了, 你能够知道在哪一个文件的哪行代码崩了, 这就是差异!

对于Go1.5, Windows64环境, 使用 fmt.PrintlnHello world 生成的 exe 有 2.4 MB.

对于 Rust1.1, Windows64环境, 生成的 exe 有 2.3 MB.

作了一个数组越界致使崩溃的测试, Go生成的2.4MB的程序能够输出致使崩溃的文件名和行号:

panic: runtime error: index out of range goroutine 1 [running]: main.main() D:/path/to/main.go:7 +0x1b9

至关于CXX/C 加 -g -O 参数使生成文件含debug/trace信息,这样会增长文件大小。-w 去掉DWARF调试信息,获得的程序就不能用gdb调试了
不建议s和w同时使用。也能够压缩生成文件。

Rust 生成的exe只能输出如下没啥用的信息:

thread '<main>' panicked at 'index out of bounds: the len is 2 but the index is 100', C:/bot/slave/stable-dist-rustc-win-gnu-64/build/src/libcollections\vec.rs: 1359 

关于exe大小的问题能够关注 Issue6853.

1.15 不支持动态加载类库

静态编译的程序固然是很好的,没有额外的运行时依赖,部署时很方便。可是以前咱们说了,静态编译的文件尺寸很大。若是一个软件系统由多个可执行程序构成,累加起来就很可观。若是用动态编译,发布时带同一套动态库,能够节省不少容量。更关键的是,动态库能够运行时加载和卸载,这是静态库作不到的。还有那些LGPL等协议的第三方C库受版权限制是不容许静态编译的。至于动态库的版本管理难题,能够经过给动态库内的全部符号添加版本号解决。不管如何,应该给予程序员选择权,让他们本身决定使用静态库仍是动态库。一刀切的拒绝动态编译是不合适的。

假设系统由100多个exe组成了, 那总共也就是不超过1GB的磁盘空间, 没以为有多大.

并且DLL依赖的地狱难道忘记了吗.

Go 1.8及之后支持plugin, 还能够玩玩hot-plugin,https://github.com/campoy/golang-plugins。

1.16 其余

  • 不支持方法和函数重载(overload)
  • 导入pkg的import语句后边部分居然是文本(import ”fmt”)

  • 没有enum类型,全局性常量难以分类,iota把简单的事情复杂化

  • 定义对象方法时,receiver类型应该选用指针仍是非指针让人纠结

  • 定义结构体和接口的语法稍繁,interface XXX{} struct YYY{} 不是更简洁吗?前面加上type关键字显得罗嗦。

  • 测试类库testing里面没有AssertEqual函数,标准库的单元测试代码中充斥着if a != b { t.Fatal(...) }。

  • 语言太简单,以致于不得不放弃不少有用的特性,“保持语言简单”每每成为拒绝改进的理由。

  • 标准库的实现整体来讲不甚理想,其代码质量大概处于“基本可用”的程度,真正到企业级应用领域,每每就会暴露出诸多不足之处。

  • 版本都发展到1.2了,goroutine调度器依旧默认仅使用一个系统线程。GOMAXPROCS的长期存在彷佛暗示着官方历来没有足够的信心,让调度器正确安全地运行在多核环境中。这跟Go语言自身以并发为核心的定位有致命的矛盾。(直到2015年下半年1.5发布后才有改观)

  • 官方发行版中包含了一个叫oracle的辅助程序,与Oracle数据库毫无关系,却彻底无视二者之间的名称混淆。

  • 不支持函数重载减轻了读代码的负担, 是好事情. 可变的a+b行为比可变的Add(a,b)难发现多了

  • import导入文本绝对是优势, 由于能够支持不少以特殊字符命名的路径: import "_-aa/bb~/dd/xx", 只有包名知足ID命名规则就能够了, 前缀部分能够很随意

  • receiver 就是普通函数: func(self T, ...) 和 func(self *T, ...) 的差异不是很明显吗

  • type 开始规则更统一, 和 var x int 和 func Add(a, b int) int 类型后缀的规则是一致的(Rust中的变量和函数也是类型后置吧), 好比 type MyInt inttype MyFunc func(...), 并且也很是便于解析和查找(正则^type就能够定位了)

  • 若是要加 AssertEqual 的话, 那么什么叫 equal 呢? 2个map或struct如何才是叫相等, chan成员呢? 别老是想着增长功能, 增长功能的同时带来的问题和复杂性难道不须要考虑吗?

  • 语言太简单难道不是优势吗? C++语言够复杂, 建议楼主深刻学习

  • 标准库的一大原则就是基本可用, 标准库不是一个大杂烩, 我想"少便是多"的哲学你是不会理解的

  • Go1.5默认N个系统线程, N为CPU核心数目. 默认值并非没有信心, 而是对于不一样的程序, 须要几个线程最好是一个比较困难的事情(好比gui程序为什么不用多线程呢).

  • oracle 就是一个普通的单词, 为什么不能使用? 楼主会不会由于买了水果的手机, 之后就不认识 apple 这个单词了?

上面列出的是我目前还能想到的对Go语言的不爽之处,毕竟时间过去两年多,还有一些早就遗忘了。其中一部分当然是小不爽,可能忍一忍就过去了,可是不少不爽积累起来,总会时不时地让人难受,时间久了有自虐的感受。程序员的工做生活原本就够枯燥的,何须呢。

必需要说的是,对于其中大多数不爽之处,我(Liigo)都曾经试图改变过它们:在Go 1.0版本发布以前,我在其官方邮件组提过不少意见和建议(甚至包括提交代码CL),极力力排众议,能够说付出很大努力,目的就是但愿定型后的Go语言是一个相对完善的、没有明显缺陷的编程语言。结果是使人失望的,我人微言轻、势单力薄,不可能影响整个语言的发展走向。1.0以前,最佳的否认自我、超越自个人机会,就这么遗憾地错过了。我最终发现,不少时候不是技术问题,而是技术人员的问题。

给Go提交的CL的要求是很是高, 楼主的BOM提法我以为能够讨论, 可是不要觉得CL增长了特性就必须得经过.

还好, Go团队没有接受你上面的诸多建议, 要否则我估计我如今已经放弃Go了.

第2节:我为何对Go语言的某些人不爽?

这里提到的“某些人”主要是两类:1、负责专职开发Go语言的Google公司员工;2、Go语言的推崇者和脑残粉丝。我跟这两类人打过不少交道,不胜其烦。再次强调一遍,我指的是“某些”人,而不是全部人,请不要对号入座。

对于一, 执拗的G员工, 你要经过逻辑来讲服他们, 若是本身都没有干货, 别人凭什么要采纳你的建议(上面的绝大部分建议我就反对)?

对于二, 脑残粉丝谁都烦, 但愿楼主下次吐槽能给点干货, 别把本身也整成了脑残粉.

Google公司内部负责专职开发Go语言的核心开发组某些成员,他们倾向于闭门造车,执拗己见,对第三方提出的建议不重视。他们经常挂在嘴边的口头禅是:现有的作法很好、不须要那个功能、咱们开发Go语言是给Google本身用的、Google不须要那个功能、若是你必定要改请fork以后本身改、别干提意见请提交代码。不少言行都是“反开源”的。经过一些具体的例子,还能更形象的看清这一层。就留下做为课后做业吧。

对于技术而言, 我更喜欢独裁者. 所谓的开源烂民主那是活稀泥的.

你能够尝试去提一个Linux内核也增长GUI模块的建议试试.

我最不能接受的就是他们对1.0版本的散漫处理。那时候Go还没到1.0,初出茅庐的小学生,有很大的改进空间,是全面翻新的最佳时机,彼时不改更待什么时候?1.0是打地基的版本,基础不牢靠,等1.0定型以后,到处受到向后兼容性的牵制,束手缚脚,每前进一步都阻力重重。急于发布1.0,过早定型,留下诸多遗憾,彰显了开发者的功利性强,在技术上不追求尽善尽美。

Go1.5的地基已经很是的牢固, 这个你不用担忧.

Go语言的核心开发成员,他们平常的开发工做是使用C语言——Go语言的编译器和运行时库,包括语言核心数据结构和算法map、channel、scheduler,都是C开发的——真正用本身开发的Go语言进行实际的大型应用开发的机会并很少。虽然标准库是用Go语言本身写的,但他们却没有大范围使用标准库的经历。实际上,他们缺乏使用Go语言的实战开发经验,每每不知道处于开发第一线的用户真正须要什么,没法作到设身处地为程序员着想。缺乏使用Go语言的亲身经历,也意味着他们不能在平常开发中,及时发现和改进Go语言的不足。这也是他们每每自我感受良好的缘由。

缺乏使用Go语言的亲身经历, 楼主也真的敢信口开河. 你不是觉得G公司开发Go真的是用来玩的吧.

再说Go1.5已经彻底没有C代码了, 这下你该闭口了吧.

Go语言社区里,有一大批Go语言的推崇者和脑残粉丝,他们知足于现状,不思进取,到处维护心中的“神”,容不得批评意见,不支持对语言的改进要求。当年我对Go语言的不少批评和改进意见,极少获得他们的支持,他们不但不支持还给予打击,我就纳闷了,他们难道不但愿Go语言更完善、更优秀吗?我后来才意识到,他们跟乔帮主的苹果脑残粉丝们,言行一脉相承,具备极端宗教倾向,神化主子、打击异己真是竭尽全力呀。简简单单的技术问题,就能被他们上升到意识形态之争。现实的例子是蛮多的,有兴趣的到网上去找吧。正是由于他们的存在,致使更多理智、清醒的Go语言用户没法真正融入整个社区。

你的不少批评和改进意见都是狗屎(包括BOM那个). 你这样的用户没有融入社区时好事情, Go语言只要在生产环境好用就能够了.

若是一个项目、团队、社区,处处充斥着赞美、孤芳自赏、自我知足、不思进取,排斥不一样意见,拒绝接纳新方案,我想不到它还有什么前进的动力。逆水行舟,是不进反退的。

惋惜世界不是以你的意志改变的, Go还将继续快速发展, 你是很难受吧?

第3节:还有比Go语言更好的选择吗?

我始终坚持一个很有辩证法意味的哲学观点:在更好的替代品出现以前,现有的就是最好的。失望是没有用的,抱怨是没有用的,要么接受,要么逃离。我曾经努力尝试过接受Go语言,失败以后,注定要逃离。发现更好的替代品以后,无疑加速了逃离过程。还有比Go语言更好的替代品吗?固然有。做为一个屌丝程序员,我应该告诉你它是什么,可是我不说。如今还不是时候。我如今不想把这两门编程语言对立起来,引起另外一场潜在的语言战争。这不是此文的本意。若是你非要从现有信息中推测它是什么,那彻底是你本身的事。若是你原意等,它或许很快会浮出水面,也未可知。

不就是号称银弹的 Rust 吗, 可是 然并卵. 我也断言一句: Rust 最终只能是小众语言, 想代替 Go语言/C语言 根本是没戏的(Swift开源后基本能够秒杀Rust).

第4节:写在最后

我不原意被别人表明,也不肯意表明别人。这篇文章写的是我,一个叫Liigo的80后屌丝程序员,本身的观点。你彻底能够主观地认为它是主观的,也彻底能够客观地觉得它是客观的,不管如何,那是你的观点。

这篇文字是从记忆里收拾出来的。有些细节虽可考,而不值得考。——我早已逃离,不肯再回到当年的场景。文中涉及的某些细节,可能会由于些许误差,影响其准确性;也可能会由于缺乏出处,影响其客观性。若是有人较真,非要去核实,我相信那些东西应该还在那里。

Go语言也非上文所述一无可取,它固然有它的优点和特点。读者们判断一件事物,应该是优劣并陈,作综合分析,不能单听我一家负面之言。可是它的那些不爽之处,始终让我不爽,且不能从其优秀处得以彻底中和,这是我不得不放弃它的缘由。

走好, 不送!


Liigo 2014-4-29 补记1:

Go语言社区还有一个很奇特的现象,就是中国社区独大,国外社区要小的多。有外国网友还专门写了一篇文章研究《为何Golang中国社区独大》这个问题(文中也提到了我这篇博文)。一般来讲,在IT和软件领域,向来都是国外先进国家引领技术潮流,而后国内缓慢跟进。而到了Go语言这里,偏偏反过来了,彷佛暗示着在国外的主流软件开发技术人员并不怎么待见Go语言,Go只是在国内受到一帮人的盲目推崇而已,至于这帮人的眼光如何,反正我不看好。

在工做中都已经用上了, 你还在想象别人是在盲目推崇, 是你本身在梦游吧.

Liigo 2014-4-29 补记2:

著名的编程语言研究专家王垠写了一篇《对 Go 语言的综合评价》(晚于本博文发表约三五天),也是整体上持批判态度,看衰Go语言。读者们能够对照阅读。

王的垠语言出来了吗? 等着10年后他再次扇本身的脸(参考Windows无用那篇).

Liigo 2014-4-29 补记3:

Go语言的拥护者们,彷佛连Go语言的“核心优点”都说不出几条。知乎上颇有人气的一条问答《为何要使用 Go 语言,Go 语言的优点在哪里》,连静态编译、GC、跨平台都拿出来讲了(无视C/C++/Java),甚至连简单易学(无视Python/易语言)、“丰富的”标准库(跟谁比?敢跟Java/C#/Python比么?)、好用的工具链(gofmt)都扯出来了,可见除了“并发、网络”以外,他们也讲不出另外的什么核心优点了,只能靠一些周边的东西凑数。

不须要NB的特性, 只须要简单/好用/实用就行.

Liigo 2015-1-31 补记4:

全世界认为Go语言很差的可不仅是我Liigo一我的。国外著名的问答网站Quora上面有我的气很高的提问,“为何不要用Go语言”(英文网页),看看那排名最前的两个答案,以及广大程序员们给这两个答案的数百个“赞”,都足以说明Go语言自身的问题是客观存在的。人民群众的眼睛是雪亮的。

就是数万个“赞”又怎么样? 关键是不少地方Go已经用起来了.

Liigo 2015-4-1 补记5:

文中1.10(黑魔法)和1.12(接口)章节增长了两处“延伸阅读”连接,被引用的连接后面均有大量网友评论。此举主要是为了说明本文观点并不是一家之言。

Liigo 2015-5-29 补记6:

补充说明Go语言直到2015年下半年1.5发布后才将GOMAXPROCS设置为大于1的默认值(HN),他们文中认可以前一直默认设置为1是由于调度器不完善(与我此文最初发表时的猜想一致)。

原来这是你的功劳!

Liigo 2015-6-2 补记7:

补充两篇英文:Why Go Is Not Good(做者Will Yager重点批评了Go语言的设计不佳甚至是倒退),Leaving Go(做者Danny Gratzer放弃Go语言的缘由主要是:没有泛型,充满黑魔法)。这两篇文章都是针对具体问题作具体分析的,与本文写做精神一致,务实不务虚。其中提到的对Go语言不满的地方,本文也多有涉及,结论相似。

放弃Go语言很正常, 也有不少放弃X语言投奔Go语言的例子.

关于对做者倾向性质疑的声明:

读者看到本文全都是Go语言负面性的内容,没有涉及一点Go语言好的地方,于是质疑做者的盲目倾向。出现这种结果彻底是由于文章主题所限。此前本文末尾也简单提到过,评估一件事物,应当优劣并陈,优点项加分,劣势项减分,作综合评估分析。若是有突出的重大优点,则能够容忍一些较大的劣势;但若是有致命的劣势或多项大劣势,则再大的优点也没法与之中和。中国乒乓球界讲领军人物必须作到“技术全面,特长突出,没有明显弱点”,我甚为赞同。用这句话套用Go语言,能够说“技术不全面(人家本身说成简洁),有一点特长(并发),有明显的弱点(包括但不限于本文列出的这些)”。如此一来,优点都被劣势中和了,劣势仍是那么突出,天然是得负分,天然是弃用,天然是没有好印象。我在这里能够说观点鲜明、态度明确,不和稀泥。与其看那些盲目推崇Go语言的人和文章,笼统的说“好”,不如也顺便看看本文,具体到细节地说“很差”。凡是具体到细节的东西,都是容易证明或证伪的,比笼统的东西(不管是"黑"仍是"粉")可信性更高一些。

不须要NB的特性, 只须要简单/好用/实用就行.

关于对做者阴谋论的声明:

有某些阴谋论者(例如谢某),说我因一个Pull Request被Go开发者拒绝而“怀恨至今”,暗示此文是故意报复、抹黑Go语言。我对Golang有恨吗?固然是有的,那是一个不爽接一个不爽(如本文一一罗列的那些),逐步累积,由量变造成质变的结果,是我对Golang综合客观评估以后的主观态度,并不是由哪个单独的事件所主导。要说Pull Request被拒绝,Rust开发者拒绝个人PR次数还少吗?好比 https://github.com/mozilla/rust/pull/13014 和 https://github.com/liigo/rust/tree/xp 和 https://github.com/rust-lang/rust/issues/12842,要是再算上被拒的Issues,那就多的数不清了。我显然不可能由于某些个别的事件,影响到我对某个事物的综合评估(参见前文)。那本文是“故意抹黑”Go语言吗?我以为不是,理由有二:一、这是做者的主观感觉,二、这些感觉是以许多客观事实为基础的。若是本文一一列出的那些现象,是不存在的,是虚构出来的,是凭空生成的,那么做者必定是“低级黑”。问题是,那些都是客观存在的事实。把事实说出来,怎么能叫“黑”呢?欢迎读者客观而详细的指正本文中的全部错误。

CL被据而怀恨至今真的是没冤枉楼主, 但愿下次抹黑Go能来点干货.


固然Go语言也不是完美的, 做为Windows下的Go用户, 说下我比较但愿的改进.

首先在下面的bug修复前,我并不十分关心Go的性能改进。

  • 修复 Issue11058, 让 Go 能够生成 dll, 这样我就能够基本抛弃 C++ 了
  • 修复 Issue9510, 这样 cgo 才能够放心地静态连接 c++ 库

上面2个bug是支持go和c库双向合做的关键(Linux和Darwin已经支持生成动态库). 而后就是 cgo 调用 c 函数的参数传递的性能能改善下.

在go1.5中, 新引入了 vendor 的试验性的特性, 所以go的包依赖管理算是基本解决.

长远看但愿语言方面能有如下的特性:

  • 范型支持, 能够简陋些, 可是不要破坏go已有的风格
  • 但愿能有不支持嵌套的三元表达式的支持
  • 大小写的导出规则对中文能友好一些
  • 接口瘾式转换致使的一些坑(errornil)
  • 官方的leveldb库和基于其封装的sql数据库
  • os 的文件系统作成接口, 提共自定义文件系统的挂载功能
  • image 包能增长 GrayA/GrayA32/RGB/RGB48 之类的类型支持
  • 性能改进

无关紧要的:

  • GUI支持
  • IDE支持

https://chai2010.cn/

相关文章
相关标签/搜索