作为一名Java程序员,我为什么不在生产项目中转向Go自Google在2009年发布Go语言的第一个正式版之后,这门语言就以出色的语言特性受到大家的追捧,尤其是在需要高并发的场景下,大家都会想到是不是该用Go。随后,在国内涌现出了一批以七牛为代表的使用Go作为主要语言的团队,而许世伟大神本人也在各种场合下极力推动Go在国内的发展,于是在这种大环境下,中国的Go开发者群体逐渐超越了其他地区。那么问题来了,业余时间好学是一回事,真正要将一个新东西运用到生产中则是另一回事。JavaScript的开发者可以义无反顾地选择Node.js,但是对于Java开发者来说,在下一个大项目里究竟是该选择Go,还是Java呢?郑重声明:本文并不是来探讨Go或者Java谁是更好的语言,每种语言都有自己的设计哲学和适用场景,今天主要是在探讨实际工程中的选择和权衡的问题,所以请不要上纲上线。语言本身首先,需要说明一下,作为一个技术决策者,在进行技术选型时并不能单方面地根据语言本身的特点直接下结论。实际情况下,大多数人会使用一系列的框架、库及工具,简而言之就是会考虑很多周边生态环境的因素,同时还要结合公司的特点、各种历史问题和实际客观因素等等一系列的考虑点综合下来才能完成决策。所以,接下来我们先从语言开始,一步一步来分析下在你的项目中选择Go是否合适。Go在高并发编程方面无疑是出众的,通过goroutine从语言层面支持了协程,这是Java等语言所无法比拟的,这也是大多数人在面对高并发场景选择Go的重要原因之一。虽然Java有Kilim之类的框架,但没有语言层的支持始终稍逊一筹。除此之外,Go的其他语法也很有趣,比如多返回值,在一定程度上为开发者带来了一定的便利性。试想,为了返回两到三个值,不得不封装一个对象,或者抹去业务名称使用Map、List等集合类,高级一点用Apache的Pair和Triple,虽然可行,但始终不如Go的实现来得优雅。在此之上,Go也统一了异常的返回方式,不用再去纠结是通过抛异常还是错误码来判断是否成功,多返回值的最后一个是Error就行了。Go在语言的原生类型中支持了常用的一些结构,比如map和slice,而其他语言中它们更多是存在于库中,这也体现了这门语言是从实践角度出发的特点,既然人人都需要,为什么不在语言层面支持它呢。函数作为一等公民出现在了Go语言里,不过Java在最近的Java8中也有了Lambda表达式,也算是有进步了。其他的一些特性,则属于锦上添花型的,比如不定参数,早在2004年的Java1.5中就对varargs有支持了;多重赋值在Ruby中也有出现,但除了多返回值赋值,以及让你在变量交换值时少写一个中间变量,让代码更美观一些之外,其他的作用着实不是怎么明显。说了这么多Go的优点,当然它也有一些问题,比如GC,说到它,Java不得不露出洁白的牙齿,虽然在大堆GC上G1还有些不尽如人意,但Java的GC已经发展了很多年,各种策略也比较成熟,CMS或G1足以应付大多数场景,实在有要求还能用AzulZingJVM。不过从最新的Go1.5的消息来看,Go的GC实现有了很大地提升,顺便一提的是GOMAXPROCS默认也从1变成了CPU核数,看来官方对Go在多核的利用方面更有信心了。许世伟在《Go语言编程》的前言中预言未来10年,Go会取代Java,位居编程榜之首,当时是2012年,为了看看2009年TIOBE年度编程语言如今的排名,笔者在撰写本文时特意去TIOBE看了下,最近的2015年8月排行榜,Java以19.274%位居榜首,Go已经跌出了前50,这不禁让人有些意外。但总体上来说,笔者认为Go在语言层面的表现还是相当出色的,解决了一些编程中的痛点,学习曲线也能够接受,特别是对于那些有C/C++背景的人,会感觉十分亲切。工程问题一个人写代码时可以很随性,想怎么写就怎么写,但当一个人变成一个团队后,这种随性或者说随便就会带来很多问题,于是就诞生了编码规范这玩意儿,大厂基本都有自己的编码规范,比如Google就有针对不下十种编程语言的规范。团队内约定一套编码规范能够很大程度上地确保代码的风格,降低阅读沟通的成本。Go内置了一套编码规范,违反了该规范代码就无法编译通过,可以说只要你是写Go的,那你的代码就不会太难看,当然Go也没有把所有东西就强制死,还有一些推荐的规范可以通过gofmt进行格式化,但这步不是必须的。虽然Go自己解决了这个问题,但并不能说Java在这方面是空白,Java发展至今周边工具无数,并不缺成熟的代码静态分析工具,比如CheckStyle、PMD和FindBugs,它们不仅能扫描编码规范的问题,甚至还能扫描代码中潜在的问题并给出解决方案,并且使用方便,在Java开发者社区中有很高地接受度,应该说大多数靠谱地开发者都会使用这些工具。除此之外,一些大厂也有自己的强制手段,比如百度内部也有很多语言的编码规范,而且大部分情况下如果没有通过编码规范的扫描,你是无法提交代码的;还有一些公司会在持续集成过程中加入代码扫描,有FindBugs高优先级的问题时必须修复才能进入下一个阶段。所以说Go在这个问题上的优势并不明显,或者说在一个成熟的环境下,这只是合格而已。这里需要强调笔者的一个观点:Go在语言本身和发行包中融入了很多最佳实践,正是这些前人的经验才让它看起来如此优秀。拿这么个海陆空混编特种部队去和Java、C、Ruby这些语言本身做对比,显得不太公平,所以本文在考虑问题时都会结合语言及其生态圈中的成员,毕竟这才更接近真实的情况。Go本身对项目结构有一套约定,代码放哪里,测试文件如何命名,编译打包后的结果输出到哪个目录,甚至还有gocover这种统计测试覆盖率的命令行,开发者不用在这些问题上太过纠结,再一次体现了Go注重工程实践的特点。回过头来,Java方面,Maven、Gradle都是注重于工程生命周期管理的工具,而且Maven更是历史悠久,被广泛用于各种项目之中。以Maven为例,不仅能够实现上述所有功能,还有很强的插件扩展能力,这里需要的只是一次性维护好pom.xml文件就行了,由于Maven的使用群很大,网上有大量的范例,甚至还有很多生成工程的工具和模板,所以使用成本并不高。这里还要衍生出一个话题,就是依赖管理,在开发代码时,势必需要依赖很多外部的东西,Go可以直接import远程的内容,这个特性很有创意,但并不能很好地解决版本的问题,在Maven或Gradle里,我们可以直接指定各个依赖项甚至是插件的版本,工具会自动从仓库中下载它们。如果需要同时在同一个系统的不同模块里依赖同一个库的不同版本,我们还能够通过OSGi这种略显复杂的手段来实现,在模块化方面,Jagsaw虽然被一延再延,但估计有望纳入Java9,这个特性也会解决不少问题。而根据Golang实践群中大家的讨论,似乎godep、gb和gvt都不尽如人意,在这点上看来Go还有一段路要走。综上所述,Go在工程方面的确有不少亮点,吸纳了很多最佳实践,甚至可以说用Go之后更容易写出规范的代码,有好的项目结构,但与生态圈完备的Java相比,Go并不占优势,因为最终代码的质量还是由人决定的,双方都不缺好的工具,所以这方面的特点并不能影响技术选型的决策。开发实践Talkischeap.Showmethecode.下面进入编码环节,先从Go引以为傲的并发开始,《Go语言编程》的前言中有这样一段代码:funcrun(argstring){//...}funcmain(){gorun(test)...}书中与之对比的Java代码有12行,而且还是线程,不是协程,对比很明显,但那是在2012年的时候,时至今日,Java已经发展到了Java8,3年了,看看如今的Java代码会是什么样的:publicclassThreadDemo{publicstaticvoidmain(String[]args){Stringstr=test;//为了和原先的Java版本对照,说明能传参进入线程内,在外声明了一个字符串,其实可以直接写在Lambda里newThread(()-{/*dosth.withstr*/}).start();}}不是协程仍是硬伤,但有了Lambda表达式,代码短了不少。不过话又说回来,这样的比较并没有太多意义,所以各位Go粉也不用站出来说Go也支持闭包,Go的版本也能精简。我们比的不是谁写的短,在Java实践中,大多数时候大家会选择线程池,而不是自己new一个Thread对象,DougLea大神的Java并发包非常的好用,而且很靠谱。另外,并发中处理的内容才是关键,新启一个线程或者协程才是万里长城,如果其中的业务逻辑有10个分支,还要多次访问数据库并调用远程服务,那无论用什么语言都白搭。所以在业务逻辑复杂的情况下,语言的差异并不会太明显,至少在Java和Go的对比下不明显,至于其他更高阶、表达力更强的语言(比如CommonLisp),大家就要拼智商了。还有一些情况中,由于客观因素制约,完全就无法使用Go,比如现在如火如荼的互联网金融系统里,与银行对接的系统几乎没有选择,都是Java实现的,因为有的银行只会给Jar包啊……给Jar包啊……Jar包啊……如果是个so文件,也许还能用cgo应付一下,面对一个Jar你让Go该何去何从?抛开这些让人心烦的问题,让我们再来看看现在比较常见的如何实现REST服务。说到这里,就一定要祭出国人出品的Beego框架。一个最简单的REST服务可以是这样的:packagemainimport(github.com/astaxie/beego)typeMainControllerstruct{beego.Controller}func(this*MainController)Get(){this.Ctx.WriteString(helloworld!)}funcmain(){beego.Router(/,&MainController{})beego.Run()}既然Go方面,我们使用了一套框架,那么Java方面,我们一样也选择一个成熟的框架,Spring在JavaEE方面基本可以算是事实标准,而SpringBoot更是大大提升了Spring项目的开发效率,看看同样实现一个REST服务,在SpringBoot里是怎么做的。首先,到start.spring.io根据需要生成项目骨架(其实完全可以方便地自己通过Maven手工配置依赖或者是用CLI工具来创建),为了后续的演示,这里我会选上“Web”、“Actuator”和“RemoteShell”,其实就是多了两个Maven的依赖,下文运维部分会提到,然后随便找个顺手的IDE打开工程,敲入如下代码就行了(import、包和类定义的部分基本都是IDE生成的)。packagedemo;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@SpringBootApplication@RestControllerpublicclassDemoApplication{@RequestMapping(/)publicStringsayHello(){returnhelloworld!;}publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}运行这段代码会自动启动内置Tomcat容器,访问就能看到输出了。因为其实就