我为什么放弃go语言 Go 语言

我为什么从python转向go - 简书
我为什么从python转向go
应puppet大拿刘宇的邀请,我去西山居运维团队做了一个简短分享,谈谈为什么我要将我们的项目从python转向go。
坦白的讲,在一帮python用户面前讲为什么放弃python转而用go其实是一件压力蛮大的事情,语言之争就跟vim和emacs之争一样,是一个永恒的无解话题,稍微不注意就可能导致粉丝强烈地反击。所以我只会从我们项目实际情况出发,来讲讲为什么我最终选择了go。
为什么放弃python
首先,我其实得说说为什么我们会选择python。在我加入企业快盘团队之前,整个项目包括更早的金山快盘都是采用python进行开发的。至于为什么这么选择,当时的架构师葱头告诉我,主要是因为python上手简单,开发迅速。对于团队里面大部分完全没服务端开发经验的同学来说,python真的是一个很好的选择。
python的简单高效,我是深有体会的。当时私有云项目也就几个程序员,但是我们要服务多家大型企业,进行定制化的开发,多亏了python,我们才能快速出活。后来企业快盘挂掉之后,我们启动轻办公项目,自然也使用python进行了原始版本的构建。
python虽然很强大,但我们在使用的时候也碰到了一些问题,主要由如下几个方面:
python是一门动态强类型语言。但是,仍然可能出现int + string这样的运行时错误,因为对于一个变量,在写代码的时候,我们有时候很容易就忘记这个变量到底是啥类型的了。
在python里面,可以允许同名函数的出现,后一个函数会覆盖前一个函数,有一次我们系统一个很严重的错误就是因为这个导致的。
上面说到的这些,静态语言在编译的时候就能帮我们检测出来,而不需要等到运行时出问题才知道。虽然我们有很完善的测试用例,但总有case遗漏的情况。所以每次出现运行时错误,我心里都想着如果能在编译的时候就发现该多好。
其实这个一直是很多人吐槽python的地方,但python有它适合干的事情,硬是要用python进行一些高性能模块的开发,那也有点难为它了。
python的GIL导致无法真正的多线程,大家可能会说我用多进程不就完了。但如果一些计算需要涉及到多进程交互,进程之间的通讯开销也是不得不考虑的。
无状态的分布式处理使用多进程很方便,譬如处理http请求,我们就是在nginx后面挂载了200多个django server来处理http的,但这么多个进程自然导致整体机器负载偏高。
但即使我们使用了多个django进程来处理http请求,对于一些超大量请求,python仍然处理不过来。所以我们使用openresty,将高频次的http请求使用lua来实现。可这样又导致使用两种开发语言,而且一些逻辑还得写两份不同的代码。
同步网络模型
django的网络是同步阻塞的,也就是说,如果我们需要访问外部的一个服务,在等待结果返回这段时间,django不能处理任何其他的逻辑(当然,多线程的除外)。如果访问外部服务需要很长时间,那就意味着我们的整个服务几乎在很长一段时间完全不可用。
为了解决这个问题,我们只能不断的多开django进程,同时需要保证所有服务都能快速的处理响应,但想想这其实是一件很不靠谱的事情。
异步网络模型
tornado的网络模型是异步的,这意味着它不会出现django那样因为外部服务不可用导致这个服务无法响应的问题。话说,比起django,我可是非常喜欢tornado的,小巧简单,以前还写过几篇深入剖析tornado的文章了。
虽然tornado是异步的,但是python的mysql库都不支持异步,这也就意味着如果我们在tornado里面访问数据库,我们仍然可能面临因为数据库问题造成的整个服务不可用。
其实异步模型最大的问题在于代码逻辑的割裂,因为是事件触发的,所以我们都是通过callback进行相关处理,于是代码里面就经常出现干一件事情,传一个callback,然后callback里面又传callback的情况,这样的结果就是整个代码逻辑非常混乱。
python没有原生的协程支持,虽然可以通过gevent,greenlet这种的上patch方式来支持协程,但毕竟更改了python源码。另外,python的yield也可以进行简单的协程模拟,但毕竟不能跨堆栈,局限性很大,不知道3.x的版本有没有改进。
开发运维部署
当我第一次使用python开发项目,我是没成功安装上项目需要的包的,光安装成功mysql库就弄了很久。后来,是一位同事将他整个python目录打包给我用,我才能正常的将项目跑起来。话说,现在有了docker,是多么让人幸福的一件事情。
而部署python服务的时候,我们需要在服务器上面安装一堆的包,光是这一点就让人很麻烦,虽然可以通过puppet,salt这些自动化工具解决部署问题,但相比而言,静态编译语言只用扔一个二进制文件,可就方便太多了。
python非常灵活简单,写c几十行代码才能搞定的功能,python一行代码没准就能解决。但是太简单,反而导致很多同学无法对代码进行深层次的思考,对整个架构进行细致的考量。来了一个需求,啪啪啪,键盘敲完开速实现,结果就是代码越来越混乱,最终导致了整个项目代码失控。
虽然这也有我们自身的原因,譬如没好的代码review机制,没有好的项目规范,但个人感觉,如果一个程序员没经过良好的编码训练,用python很容易就写出烂的代码,因为太自由了。
当然,我这里并不是说用python无法进行大型项目的开发,豆瓣,dropbox都是很好的例子,只是在我们项目中,我们的python代码失控了。
上面提到的都是我们在实际项目中使用python遇到的问题,虽然最终都解决了,但是让我愈发的觉得,随着项目复杂度的增大,流量性能压力的增大,python并不是一个很好的选择。
为什么选择go
说完了python,现在来说说为什么我们选择go。其实除了python,我们也有其他的选择,java,php,lua(openresty),但最终我们选择了go。
虽然java和php都是最好的编程语言(大家都这么争的),但我更倾向一门更简单的语言。而openresty,虽然性能强悍,但lua仍然是动态语言,也会碰到前面说的动态语言一些问题。最后,前金山许式伟用的go,前快盘架构师葱头也用的go,所以我们很自然地选择了go。
go并不是完美,一堆值得我们吐槽的地方。
error,好吧,如果有语言洁癖的同学可能真的受不了go的语法,尤其是约定的最后一个返回值是error。项目里面经常会充斥这样的代码:
if _, err := w.Write(data1); err != nil {
returun err
if _, err := w.Write(data2); err != nil {
returun err
难怪有个梗是对于一个需求,java的程序员在写配置的时候,go程序员已经写了大部分代码,但是当java的程序员写完的时候,go程序员还在写err != nil。
这方面,倒是推荐了一个不错的解决方案。
包管理,go的包管理太弱了,只有一个go get,也就是如果不小心更新了一个外部库,很有可能就导致现有的代码编译不过了。虽然已经有很多开源方案,譬如godep以及现在才出来的gb等,但毕竟不是官方的。貌似google也是通过vendor机制来管理第三方库的。希望go 1.5或者之后的版本能好好处理下这个问题。
GC,java的GC发展20年了,go才这么点时间,gc铁定不完善。所以我们仍然不能随心所欲的写代码,不然在大请求量下面gc可能会卡顿整个服务。所以有时候,该用对象池,内存池的一定要用,虽然代码丑了点,但好歹性能上去了。
泛型,虽然go有inteface,但泛型的缺失会让我们在实现一个功能的时候写大量的重复代码,譬如int32和int64类型的sort,我们得为分别写两套代码,好冗余。go 1.4之后有了go generate的支持,但这种的仍然需要自己根据go的AST库来手动写相关的parser,难度也挺大的。虽然也有很多开源的generate实现,但毕竟不是官方的。
当然还有很多值得吐槽的地方,就不一一列举了,但是go仍旧有它的优势。
静态语言,强类型。静态编译能帮我们检查出来大量的错误,go的强类型甚至变态到不支持隐式的类型转换。虽然写代码感觉很别扭,但减少了犯错的可能。
gofmt,应该这是我知道的第一个官方提供统一格式化代码工具的语言了。有了gofmt,大家的代码长一个样了,也就没有花括号到底放到结尾还是新开一行这种蛋疼的代码风格讨论了。因为大家的代码风格一样,所以看go的代码很容易。
天生的并行支持,因为goroutine以及channel,用go写分布式应用,写并发程序异常的容易。没有了蛋疼的callback导致的代码逻辑割裂,代码逻辑都是顺序的。
性能,go的性能可能赶不上c,c++以及openresty,但真的也挺强悍的。在我们的项目中,现在单机就部署了一个go的进程,就完全能够胜任以前200个python进程干的事情,而且CPU和MEM占用更低。
运维部署,直接编译成二进制,扔到服务器上面就成,比python需要安装一堆的环境那是简单的太多了。当然,如果有cgo,我们也需要将对应的动态库给扔过去。
开发效率,虽然go是静态语言,但我个人感觉开发效率真的挺高,直觉上面跟python不相上下。对于我个人来说,最好的例子就是我用go快速开发了非常多的开源组件,譬如ledisdb,go-mysql等,而这些最开始的版本都是在很短的时间里面完成的。对于我们项目来说,我们也是用go在一个月就重构完成了第一个版本,并发布。
实际项目中一些Go Tips
到现在为止,我们几乎所有的服务端项目都已经转向go,当然在使用的时候也遇到了一些问题,列出来算是经验分享吧。
godep,我们使用godep进行第三方库管理,但是godep我碰到的最大的坑就是build tag问题,如果一个文件有build tag,godep很有可能就会忽略这个文件。
IO deadline,如果能自己在应用层处理的都自己处理,go的deadline内部是timer来控制,但timer内部采用一个array来实现的heap,全局共用一个锁,如果大并发量,并且timer数量过多,timeout变动太频繁,很容易就引起性能问题。
GC,这个前面也说了,多用内存池,对象池,另外,我还发现,如果对象的生命周期跟goroutine一致,对性能的提升也不错,也在go的group问过相关问题,大家猜测可能是因为一些对象其实是在goroutine的8k栈上面分配的,所以一起回收没有额外GC了。
Go gob,如果要做RPC服务,gob并不是一个很好的选择,首先就跟python的pickle不通用,然后为了做不同系统的数据传入,任何包都必须带上类型的详细信息,size太大。go里面现在还没一套官方的RPC方案,gRPC貌似有上位的可能。
虽然我现在选择了go,但是并不表示我以后不会尝试其他的语言。语言没有好坏,能帮我解决问题的就是好语言。但至少在很长的一段时间,我都会用go来进行开发。Let' go!!!
一位爱好文学的资深程序开发工程师。热爱工作又极度顾家的有为社会青年。1294人阅读
这就是生活(18)
有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题。
开门见山地说,我当初放弃Go语言(golang),就是因为两个“不爽”:第一,对Go语言本身不爽;第二,对Go语言社区里的某些人不爽。毫无疑问,这是非常主观的结论。但是我有足够详实的客观的论据,支撑这个看似主观的结论。
第0节:我的Go语言经历
先说说我的经历吧,以避免被无缘无故地当作Go语言的低级黑。
2009年底,Go语言(golang)第一个公开版本发布,笼罩着“Google公司制造”的光环,吸引了许多慕名而来的尝鲜者,我(Liigo)也身居其中,笼统的看了一些Go语言的资料,学习了基础的教程,因对其语法中的分号和花括号不满,很快就遗忘掉了,没拿它当一回事。
两年之后,2011年底,Go语言发布1.0的计划被提上日程,相关的报道又多起来,我再次关注它,[重新评估][1]之后决定深入参与Go语言。我订阅了其users、nuts、dev、commits等官方邮件组,坚持每天阅读其中的电子邮件,以及开发者提交的每一次源代码更新,给Go提交了许多改进意见,甚至包括[修改Go语言编译器源代码][2]直接参与开发任务。如此持续了数月时间。
到2012年初,Go 1.0发布,语言和标准库都已经基本定型,不可能再有大幅改进,我对Go语言未能在1.0定型之前更上一个台阶、实现自我突破,甚至带着诸多明显缺陷走向1.0,感到非常失望,因而逐渐疏远了它(所以Go 1.0之后的事情我很少关心)。后来看到即将发布的Go 1.1的Release Note,发现语言层面没有太大改变,只是在库和工具层面有所修补和改进,感到它尚在幼年就失去成长的动力,越发失望。外加Go语言社区里的某些人,其中也包括Google公司负责开发Go语言的某些人,其态度、言行,让我极度厌恶,促使我决绝地离弃Go语言。
第1节:我为什么对Go语言不爽?
Go语言有很多让我不爽之处,这里列出我现在还能记起的其中一部分,排名基本上不分先后。读者们耐心地看完之后,还能淡定地说一句“我不在乎”吗?
不允许左花括号另起一行
关于对花括号的摆放,在C语言、C++、Java、C#等社区中,十余年来存在持续争议,从未形成一致意见。在我看来,这本来就是主观倾向很重的抉择,不违反原则不涉及是非的情况下,不应该搞一刀切,让程序员或团队自己选择就足够了。编程语言本身强行限制,把自己的喜好强加给别人,得不偿失。无论倾向于其中任意一种,必然得罪与其对立的一群人。虽然我现在已经习惯了把左花括号放在行尾,但一想到被禁止其他选择,就感到十分不爽。Go语言这这个问题上,没有做到“团结一切可以团结的力量”不说,还有意给自己树敌,太失败了。
编译器莫名其妙地给行尾加上分号
对Go语言本身而言,行尾的分号是可以省略的。但是在其编译器(gc)的实现中,为了方便编译器开发者,却在词法分析阶段强行添加了行尾的分号,反过来又影响到语言规范,对“怎样添加分号”做出特殊规定。这种变态做法前无古人。在左花括号被意外放到下一行行首的情况下,它自动在上一行行尾添加的分号,会导致莫名其妙的编译错误(Go 1.0之前),连它自己都解释不明白。如果实在处理不好分号,干脆不要省略分号得了;或者,Scala和JavaScript的编译器是开源的,跟它们学学怎么处理省略行尾分号可以吗?
极度强调编译速度,不惜放弃本应提供的功能
程序员是人不是神,编码过程中免不了因为大意或疏忽犯一些错。其中有一些,是大家集体性的很容易就中招的错误(Go语言里的例子我暂时想不起来,C++里的例子有“基类析构函数不是虚函数”)。这时候编译器应该站出来,多做一些检查、约束、核对性工作,尽量阻止常规错误的发生,尽量不让有潜在错误的代码编译通过,必要时给出一些警告或提示,让程序员留意。编译器不就是机器么,不就是应该多做脏活累活杂活、减少人的心智负担么?编译器多做一项检查,可能会避免数十万程序员今后多年内无数次犯同样的错误,节省的时间不计其数,这是功德无量的好事。但是Go编译器的作者们可不这么想,他们不愿意自己多花几个小时给编译器增加新功能,觉得那是亏本,反而减慢了编译速度。他们以影响编译速度为由,拒绝了很多对编译器改进的要求。典型的因噎废食。强调编译速度固然值得赞赏,但如果因此放弃应有的功能,我不赞成。
错误处理机制太原始
在Go语言中处理错误的基本模式是:函数通常返回多个值,其中最后一个值是error类型,用于表示错误类型极其描述;调用者每次调用完一个函数,都需要检查这个error并进行相应的错误处理。这种模式跟C语言那种很原始的错误处理相比如出一辙,并无实质性改进。实际应用中很容易形成多层嵌套的if else语句,可以想一想这个编码场景:先判断文件是否存在,如果存在则打开文件,如果打开成功则读取文件,如果读取成功再写入一段数据,最后关闭文件,别忘了还要处理每一步骤中出现错误的情况,这代码写出来得有多变态、多丑陋?实践中普遍的做法是,判断操作出错后提前return,以避免多层花括号嵌套,但这么做的后果是,许多错误处理代码被放在前面突出的位置,常规的处理逻辑反而被掩埋到后面去了。而且,error对象的标准接口只能返回一个错误文本,有时候调用者为了区分不同的错误类型,甚至需要解析该文本。除此之外,你只能手工强制转换error类型到特定子类型。至于panic
- recover机制,致命的缺陷是不能跨越库的边界使用,注定是一个半成品,最多只能在自己的pkg里面玩一玩。Java的异常处理虽然也有自身的问题(比如Checked Exceptions),但总体上还是比Go的错误处理高明很多。
垃圾回收器(GC)不完善、有重大缺陷
在Go 1.0前夕,其垃圾回收器在32位环境下有内存泄漏,一直拖着不肯改进,这且不说。Go语言垃圾回收器真正致命的缺陷是,会导致整个进程不可预知的间歇性停顿。像某些大型后台服务程序,如游戏服务器、APP容器等,由于占用内存巨大,其内存对象数量极多,GC完成一次回收周期,可能需要数秒甚至更长时间,这段时间内,整个服务进程是阻塞的、停顿的,在外界看来就是服务中断、无响应,再牛逼的并发机制到了这里统统失效。垃圾回收器定期启动,每次启动就导致短暂的服务中断,这样下去,还有人敢用吗?这可是后台服务器进程,是Go语言的重点应用领域。以上现象可不是我假设出来的,而是事实存在的现实问题,受其严重困扰的也不是一家两家了(截止到2014年初)。在实践中,你必须努力减少进程中的对象数量,以便把GC导致的间歇性停顿控制在可接受范围内。除此之外你别无选择(难道你还想自己更换GC算法、甚至砍掉GC?那还是Go语言吗?)。跳出圈外,我近期一直在思考,一定需要垃圾回收器吗?没有垃圾回收器就一定是历史的倒退吗?(可能会新写一篇博客文章专题探讨。)
禁止未使用变量和多余import
Go编译器不允许存在被未被使用的变量和多余的import,如果存在,必然导致编译错误。但是现实情况是,在代码编写、重构、调试过程中,例如,临时性的注释掉一行代码,很容易就会导致同时出现未使用的变量和多余的import,直接编译错误了,你必须相应的把变量定义注释掉,再翻页回到文件首部把多余的import也注释掉,……等事情办完了,想把刚才注释的代码找回来,又要好几个麻烦的步骤。还有一个让人蛋疼的问题,编写数据库相关的代码时,如果你import某数据库驱动的pkg,它编译给你报错,说不需要import这个未被使用的pkg;但如果你听信编译器的话删掉该import,编译是通过了,运行时必然报错,说找不到数据库驱动;你看看程序员被折腾的两边不是人,最后不得不请出大神_。对待这种问题,一个比较好的解决方案是,视其为编译警告而非编译错误。但是Go语言开发者很固执,不容许这种折中方案。
创建对象的方式太多令人纠结
创建对象的方式,调用new函数、调用make函数、调用New方法、使用花括号语法直接初始化结构体,你选哪一种?不好选择,因为没有一个固定的模式。从实践中看,如果要创建一个语言内置类型(如channel、map)的对象,通常用make函数创建;如果要创建标准库或第三方库定义的类型的对象,首先要去文档里找一下有没有New方法,如果有就最好调用New方法创建对象,如果没有New方法,则退而求其次,用初始化结构体的方式创建其对象。这个过程颇为周折,不像C++、Java、C#那样直接new就行了。
对象没有构造函数和析构函数
没有构造函数还好说,毕竟还有自定义的New方法,大致也算是构造函数了。没有析构函数就比较难受了,没法实现RAII。额外的人工处理资源清理工作,无疑加重了程序员的心智负担。没人性啊,还嫌我们程序员加班还少吗?C++里有析构函数,Java里虽然没有析构函数但是有人家finally语句啊,Go呢,什么都没有。没错,你有个defer,可是那个defer问题更大,详见下文吧。
defer语句的语义设定不甚合理
Go语言设计defer语句的出发点是好的,把释放资源的“代码”放在靠近创建资源的地方,但把释放资源的“动作”推迟(defer)到函数返回前执行。遗憾的是其执行时机的设置似乎有些不甚合理。设想有一个需要长期运行的函数,其中有无限循环语句,在循环体内不断的创建资源(或分配内存),并用defer语句确保释放。由于函数一直运行没有返回,所有defer语句都得不到执行,循环过程中创建的大量短暂性资源一直积累着,得不到回收。而且,系统为了存储defer列表还要额外占用资源,也是持续增加的。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃。像这类长期运行的函数,http.ListenAndServe()就是典型的例子。在Go语言重点应用领域,可以说几乎每一个后台服务程序都必然有这么一类函数,往往还都是程序的核心部分。如果程序员不小心在这些函数中使用了defer语句,可以说后患无穷。如果语言设计者把defer的语义设定为在所属代码块结束时(而非函数返回时)执行,是不是更好一点呢?可是Go
1.0早已发布定型,为了保持向后兼容性,已经不可能改变了。小心使用defer语句!一不小心就中招。
许多语言内置设施不支持用户定义的类型
for in、make、range、channel、map等都仅支持语言内置类型,不支持用户定义的类型(?)。用户定义的类型没法支持for in循环,用户不能编写像make、range那样“参数类型和个数”甚至“返回值类型和个数”都可变的函数,不能编写像channel、map那样类似泛型的数据类型。语言内置的那些东西,处处充斥着斧凿的痕迹。这体现了语言设计的局限性、封闭性、不完善性,像是新手作品——且不论其设计者和实现者如何权威。
没有泛型支持,常见数据类型接口丑陋
没有泛型的话,List、Set、Tree这些常见的基础性数据类型的接口就只能很丑陋:放进去的对象是一个具体的类型,取出来之后成了无类型的interface{}(可以视为所有类型的基础类型),还得强制类型转换之后才能继续使用,令人无语。Go语言缺少min、max这类函数,求数值绝对值的函数abs只接收/返回双精度小数类型,排序接口只能借助sort.Interface无奈的回避了被比较对象的类型,等等等等,都是没有泛型导致的结果。没有泛型,接口很难优雅起来。Go开发者没有明确拒绝泛型,只是说还没有找到很好的方法实现泛型(能不能学学已经开源的语言呀)。现实是,Go
1.0已经定型,泛型还没有,那些丑陋的接口为了保持向后兼容必须长期存在着。
实现接口不需要明确声明
这一条通常是被当作Go语言的优点来宣传的。但是也有人不赞同,比如我。如果一个类型用Go语言的方式默默的实现了某个接口,使用者和代码维护者都很难发现这一点(除非仔细核对该类型的每一个方法的函数签名,并跟所有可能的接口定义相互对照),自然也想不到与该接口有关的应用,显得十分隐晦,不直观。支持者可能会辩解说,我可以在文档中注明它实现了哪些接口。问题是,写在文档中,还不如直接写到类型定义上呢,至少还能得到编译器的静态类型检查。缺少了编译器的支持,当接口类型的函数签名被改变时,当实现该接口的类型方法被无意中改变时,实现者可能很难意识到,该类型实现该接口的隐含约束事实上已经被打破了。又有人辩解说,我可以通过单元测试确保类型正确实现了接口呀。我想说的是,明明可以通过明确声明实现接口,享受编译器提供的类型检查,你却要自己找麻烦,去写原本多余的单元测试,找虐很爽吗?Go语言的这种做法,除了减少一些对接口所在库的依赖之外,没有其他好处,得不偿失。
省掉小括号却省不掉花括号
Go语言里面的if语句,其条件表达式不需要用小括号扩起来,这被作为“代码比较简洁”的证据来宣传。可是,你省掉了小括号,却不能省掉大括号啊,一条完整的if语句至少还得三行吧,人家C、C++、Java都可以在一行之内搞定的(可以省掉花括号)。人家还有x?a:b表达式呢,也是一行搞定,你Go语言用if else写至少得五行吧?哪里简洁了?
编译生成的可执行文件尺寸非常大
记得当年我写了一个很简单的程序,把所有系统环境变量的名称和值输出到控制台,核心代码也就那么三五行,结果编译出来把我吓坏了:EXE文件的大小超过4MB。如果是C语言写的同样功能的程序,0.04MB都是多的。我把这个信息反馈到官方社区,结果人家不在乎。是,我知道现在的硬盘容量都数百GB、上TB了……可您这种优化程度……怎么让我相信您在其他地方也能做到不错呢。(再次强调一遍,我所有的经验和数据都来自Go 1.0发布前夕。)
不支持动态加载类库
静态编译的程序当然是很好的,没有额外的运行时依赖,部署时很方便。但是之前我们说了,静态编译的文件尺寸很大。如果一个软件系统由多个可执行程序构成,累加起来就很可观。如果用动态编译,发布时带同一套动态库,可以节省很多容量。更关键的是,动态库可以运行时加载和卸载,这是静态库做不到的。还有那些LGPL等协议的第三方C库受版权限制是不允许静态编译的。至于动态库的版本管理难题,可以通过给动态库内的所有符号添加版本号解决。无论如何,应该给予程序员选择权,让他们自己决定使用静态库还是动态库。一刀切的拒绝动态编译是不合适的。
不支持方法和函数重载(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语言自身以并发为核心的定位有致命的矛盾。
上面列出的是我目前还能想到的对Go语言的不爽之处,毕竟时间过去两年多,还有一些早就遗忘了。其中一部分固然是小不爽,可能忍一忍就过去了,但是很多不爽积累起来,总会时不时地让人难受,时间久了有自虐的感觉。程序员的工作生活本来就够枯燥的,何必呢。
必须要说的是,对于其中大多数不爽之处,我(Liigo)都曾经试图改变过它们:在Go 1.0版本发布之前,我在其官方邮件组提过很多意见和建议,极力据理力争,可以说付出很大努力,目的就是希望定型后的Go语言是一个相对完善的、没有明显缺陷的编程语言。结果是令人失望的,我人微言轻、势单力薄,不可能影响整个语言的发展走向。1.0之前,最佳的否定自我、超越自我的机会,就这么遗憾地错过了。我最终发现,很多时候不是技术问题,而是技术人员的问题。
第2节:我为什么对Go语言的某些人不爽?
这里提到的“某些人”主要是两类:一、负责专职开发Go语言的Google公司员工;二、Go语言的推崇者和脑残粉丝。我跟这两类人打过很多交道,不胜其烦。再次强调一遍,我指的是“某些”人,而不是所有人,请不要对号入座。
Google公司内部负责专职开发Go语言的核心开发组某些成员,他们倾向于闭门造车,固执己见,对第三方提出的建议不重视。他们常常挂在嘴边的口头禅是:现有的做法很好、不需要那个功能、我们开发Go语言是给Google自己用的、Google不需要那个功能、如果你一定要改请fork之后自己改、别干提意见请提交代码。很多言行都是“反开源”的。通过一些具体的例子,还能更形象的看清这一层。就留下作为课后作业吧。
我最不能接受的就是他们对1.0版本的散漫处理。那时候Go还没到1.0,初出茅庐的小学生,有很大的改进空间,是全面翻新的最佳时机,彼时不改更待何时?1.0是打地基的版本,基础不牢靠,等1.0定型之后,处处受到向后兼容性的牵制,束手缚脚,每前进一步都阻力重重。急于发布1.0,过早定型,留下诸多遗憾,彰显了开发者的功利性强,在技术上不追求尽善尽美。
Go语言的核心开发成员,他们日常的开发工作是使用C语言——Go语言的编译器和运行时库,包括语言核心数据结构map、channel、scheduler,都是C开发的——真正用自己开发的Go语言进行实际的大型应用开发的机会并不多,虽然标准库是用Go语言自己写的,但他们却没有大范围使用标准库的经历。实际上,他们缺少使用Go语言的实战开发经验,往往不知道处于开发第一线的用户真正需要什么,无法做到设身处地为程序员着想。缺少使用Go语言的亲身经历,也意味着他们不能在日常开发中,及时发现和改进Go语言的不足。这也是他们往往自我感觉良好的原因。
Go语言社区里,有一大批Go语言的推崇者和脑残粉丝,他们满足于现状,不思进取,处处维护心中的“神”,容不得批评意见,不支持对语言的改进要求。当年我对Go语言的很多批评和改进意见,极少得到他们的支持,他们不但不支持还给予打击,我就纳闷了,他们难道不希望Go语言更完善、更优秀吗?我后来才意识到,他们跟乔帮主的苹果脑残粉丝们,言行一脉相承,具有极端宗教倾向,神化主子、打击异己真是不遗余力呀。简简单单的技术问题,就能被他们上升到意识形态之争。现实的例子是蛮多的,有兴趣的到网上去找吧。正是因为他们的存在,导致更多理智、清醒的Go语言用户无法真正融入整个社区。
如果一个项目、团队、社区,到处充斥着赞美、孤芳自赏、自我满足、不思进取,排斥不同意见,拒绝接纳新方案,我想不到它还有什么前进的动力。逆水行舟,是不进反退的。
第3节:还有比Go语言更好的选择吗?
我始终坚持一个颇有辩证法意味的哲学观点:在更好的替代品出现之前,现有的就是最好的。失望是没有用的,抱怨是没有用的,要么接受,要么逃离。我曾经努力尝试过接受Go语言,失败之后,注定要逃离。发现更好的替代品之后,无疑加速了逃离过程。还有比Go语言更好的替代品吗?当然有。作为一个屌丝程序员,我应该告诉你它是什么,但是我不说。现在还不是时候。我现在不想把这两门编程语言对立起来,引发另一场潜在的语言战争。这不是此文的本意。如果你非要从现有信息中推测它是什么,那完全是你自己的事。如果你原意等,它或许很快会浮出水面,也未可知。
第4节:写在最后
我不原意被别人代表,也不愿意代表别人。这篇文章写的是我,一个叫Liigo的80后屌丝程序员,自己的观点。你完全可以主观地认为它是主观的,也完全可以客观地以为它是客观的,无论如何,那是你的观点。
这篇文字是从记忆里收拾出来的。有些细节虽可考,而不值得考。——我早已逃离,不愿再回到当年的场景。文中涉及的某些细节,可能会因为些许偏差,影响其准确性;也可能会因为缺少出处,影响其客观性。如果有人较真,非要去核实,我相信那些东西应该还在那里。
Go语言也非上文所述一无是处,它当然有它的优势和特色。读者们判断一件事物,应该是优劣并陈,做综合分析,不能单听我一家负面之言。但是它的那些不爽之处,始终让我不爽,且不能从其优秀处得以完全中和,这是我不得不放弃它的原因。
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:800597次
积分:18209
积分:18209
排名:第441名
原创:864篇
转载:71篇
评论:230条

我要回帖

更多关于 c语言从入门到放弃 的文章

 

随机推荐