用卫报(guardian.co.uk)编辑的话说,这家报纸拥有世界上第二大的读者群,仅次于纽约时报;而它的开发团队正在逐渐从Java迁移到Scala。迁移工作是从Content API开始的,使用这套API可以查找并聚合卫报的内容。
Guardian.co.uk网站大概有10万行代码。它用的是很典型的Java开源技术栈:Spring、Apache Velocity、Hibernate,数据库用的是Oracle。它的Content API和网站一样,都是用Java做的,但开发团队决定把它换成另一套基于JVM的语言──Scala。Web平台开发团队的主管Graham Tackley在采访中说:
我们过去几年都在用Java,也一直做的挺好。不过作为一家新闻网站来看,我们需要对各种事件做出快速响应。我们现在用的Java平台可以每两周就发布一次 www.guardian.co.uk。比起很多企业级Java应用来,这已经很好了,但是跟其他网站相比,就差的没法说了。
所以我们早就开始寻找各种工具、流程、语言,希望能交付的更快一些。包括使用轻量级的Java框架,如Google Guice;完全不一样的Java开发方式,如Play框架;也用过其他框架,如Python+Django。在尝试的过程中,我们用了一段时间 Scala,但是跟其他的实践不同,我们在尝试期间,没有用Scala写过任何产品代码。
我们迫切期望着Content API的第一个非beta版本能够成为一个标志,它的发布将意味着这个API走进了持续演进的第一个迭代,从此以后,每当我们发现从前没有想到的有意义的功能,这套API都可以快速进化。但我们得保证API的客户端不会受到影响,所以我们需要一系列面面俱到的集成测试。我们一开始用Java来写集成测试,写了一段时间以后,决定都换成用Scala写。主要原因有三点:
- ScalaTest提供了很灵活的测试DSL。
- 我们希望写集成测试的过程充满愉悦,而不是令人厌倦。
- 只用Scala写测试,就不直接影响产品代码,我们还能忍着怒气继续用Java。
但是,用Scala写了4周测试代码以后,我们再也忍受不了只用Java来写产品代码了,于是决定把大部分代码都换成Scala。
InfoQ: 总的来说,你们是怎么做移植的呢?是一次性的把所有Java代码都用Scala重写,还是这两种语言共存了一段时间?
Content API的beta版本用的是一个私有的搜索引擎。当前版本已经换成了Apache Solr这款很优秀的搜索平台(这儿可以看到卫报是如何使用Solr的),而且跟beta版的风格差异很大──beta版在一件事情上做的很好,它让我们看到了API不应该变成什么样子。在真正开始用Scala之前,我们决定先把API重新实现一遍,而不是重用原先的代码库。
这项工作用了三个人六周的时间,然后才开始引入Scala,这样就有了一个稍微干净点的起点。但说到要把当前的项目停上几星期用来切换到Scala,我们当时还没做好这个准备,所以还是先一点点移植现有的集成测试。当时我们用的Maven做构建,引入Scala就很简单了,maven-scala- plugin可以构建混合Java/Scala的项目,只要按照它的说明来就行。这个插件可以允许Java和Scala代码共存,还能相互依赖。我们一个类一个类的重写,情况比我们预想的要好得多:它直接就能工作了。
在重写产品代码的时候,我们用了同样的办法:在几周的时间里,每碰到一点代码,就重写一点。到最后又用了几天来扫尾。
InfoQ: 你们用到了哪些类库/框架?
因为这门语言我们都没接触过,所以我们打算控制一下,不引入太多的新东西。我们依然用了普通的servlet,Google Guice做依赖注入,这也是我们现在构建Java应用的方式。还用了SolrJ,这是跟Apache Solr通信的Java客户端。用Joda-time来做时间处理,用Mockito做mock(这个类库也能跟Scala代码协同工作)。
有时候,为了保证定期交付,我们会有意保留一些我们熟悉的东西:比如它是用javax.xml.stream.XMLStreamWrite生成XML格式的端点(endpoint),而不是Scala那套很强大的XML API。因为我们在换用Scala之前已经把这些给做完了,这些代码可以工作,容易理解,就留下了。不过生成Json格式的端点的时候,我们就换成了 Lift用的JSON库──lift-json,因为它写出来的代码比Java的JSON库要干净很多。
InfoQ: 你们用的是什么IDE?Scala对IDE的支持怎么样?
我们用的是Jetbrains IntelliJ IDEA 10。有些用的社区版,有些用的收费版。代码补全、检查调用点、类和方法跳转这些功能基本上一直都挺好用。但是它不能够很完美的识别出无效语法并且加上红色标记。查找ScalaTest的测试方法也有问题。不过比起我们之前所熟悉的环境来,这些不便之处换来的是一门强大而卓越的语言。
InfoQ: 你们项目里面是不是大多数人都是Java程序员?他们学Scala容易么?
是啊,我们所有人都有很丰富的Java开发经验。最开始的四人小组学Scala学的很开心:经常是一个人发现了一个Scala的新特性,然后就激动不已,跟整个团队分享。那种冲动已经很久没有在用Java 的时候出现了。我们在共同学习,所以这个过程还不错。最开始的几周里,偶尔出现这种情况,我们打算用很体面的Scala的编程方式来实现一些功能,但就是搞不明白该怎么写。而且最关键的是,这儿要是用Java的话早就写完了,这就更让人泄气了。我们有几次受不了了,回家的时候说,“明天就换成Java 吧。”但每天早上都会迎来一个新的开始,我们继续往前走。
然后又有十个Java程序员来写Scala代码。每个人的学习方式和速度都不一样,这也司空见惯了。但他们都走过来了,到现在要是碰到非得写Java不可的情况,每个人都会抓狂。
我们曾经拿学习Scala跟迁移到新平台上(比如Python/Django 和 Ruby on Rails)做过对比。用Scala的话,原先Java的知识大概有75%还能用得上。同样的库、同样的IDE、同样打jar包和war包的方式,运行时环境和特征都一样。优秀的Java程序员可以用一天实现学会用Scala编写Java风格的代码,然后再学会闭包和隐式类型转换的强大之处,很快就能比用 Java更有效率了。
InfoQ: 我听到人们对Scala最为诟病的是,这门语言太过复杂了。我觉得这些声音大多应该是由于Scala的可读性造成的:同样的代码,如果用更为严格一点的语言(比如Java)来写的话,就会比Scala更容易理解。你觉得这种批评合理么?你是怎么想的?
没错,可读性应该是代码最重要的品质。我不在乎代码到底是命令式的还是函数式的,我也不在乎它到底是合乎Scala的规范,还是只把它当作无分号的Java 来用,我只在乎它是不是好理解。在我们学到Scala新特性的时候,我们会根据代码是不是能够更明确的体现出实现意图来决定用不用。比如,我们试过 Scala的Either类来消除一些if表达式,但团队一致认为,if表达式更容易读懂,所以我们就没用Either类。
我承认,由于Java语法的严格,那些几行几行的代码看起来是容易懂,但是对于真正能用起来的代码来说,我觉得这根本不是问题。因为我不需要去理解实现细节,我想要理解的是代码意图。优秀的设计和OO技术可以增强Java代码的表现性,但我读Java代码的时候,仍然常常见木不见林。在表现代码意图的层次上,Scala突破了Java的很多限制。
举个例子来看,Content API需要判断到底是要以XML或JSON格式返回结果,还是重定向到HTML浏览器上。它可以接收一个字符串,其格式为 format=query,也可以接受一个.xml或是.json的扩展名,也可以从http header的Accept字段里面读取扩展名类型。下面是实现代码,我觉得这可以很好的证明Scala强大的表现力(它会一步步调到Scala的 Option类):
def negotiateFormatParameter =getParam("format"). orElse(getExtension). orElse(getExtensionFromAcceptHeader). getOrElse("html")这同样也可以很好的证明,要说可读性这回事,最起码也得看看要读完多少代码才能搞明白一个功能。换成Java的话,就得写上很多跟问题域完全无关的代码,比如非空检查、getter/setter、用来做依赖注入的构造函数、操作容器。这些地方用Scala都可以写的很精炼。当然,你也可以说这类代码大部分都可以用IDE生成,但我还是得去读你写的构造函数和getter/setter啊,要不然我怎么知道你有没有在里面做什么其他处理
找个经典的例子来比较一下看看:
Java:
public class WelcomeClass { private String name; public WelcomeClass(String name) { this.name = name; } public String sayHello() { return "Hello " + name; } }Scala:class WelcomeClass(name: String) { def sayHello = "Hello " + name }Java 版的代码三次告诉我”name“是String类型,并且五次提到“name”。Scala版的代码只提到“name”两次,也只说了一遍”name“是 String类型。这虽然是个很简单的例子,但也能说明Scala的优势:少了格式约束,少了重复,也就少了树木多了森林,换句话说,读代码的人更容易看到实现意图而不是细节。
我也注意到,一行孤零零的Scala代码要稍微多花上一点时间才能明白它干了啥,但它急剧减少了代码行数,够本还有余呢。
InfoQ: 说到Scala的复杂性,你觉得这门语言的某些特性──我觉得这里主要是符号命名和隐式转换──会给真实应用带来麻烦么?
实际上隐式转换帮了我们很大忙。我前面说过,我们用SolrJ跟Solr通信,这是个很优秀的类库,但是它有Java库的通病──太喜欢返回null了。为了不让大量的非空检查污染了代码库,我们就把一些核心的类隐式转换成了别的类,而在新类里面就拥有一些Scala风格的方法。所以这个特性非但没有给我们在实际应用中带来麻烦,还帮我们解决了问题。另外值得一提的是,IntelliJ Scala插件现在几乎可以理解所有的隐式转换了,所以如果你不明白到底发生了什么,control + 左键单击就能带你到发生调用的地方。
我们其实也在避免使用重量级的符号库,避免用符号来做方法名,但我觉得这是Scala的一个很重要的特性,也跟其他特性一样,都有可能被过度使用。有的时候这个特性很有用:我们有个方法从http请求中把用来做查询的字符串提取出来,这个方法名就是”?",放在代码里面很好理解。用Scala的时候,你得比用Java投入更多的精力来让别人更容易理解你的意图。能力越强,责任越大,说的就是这个意思。你不能因为这种力量容易被人误用就不想要它。
InfoQ: 人们对Scala企业应用的另一个关注点是,每一次Scala发行新版本总会破坏向后的兼容性,比如在Scala 2.8上编译的应用程序就没办法跟在之前的版本上编译出来的二进制文件一起用。你觉得这个问题严重么?你是怎么处理Scala项目之间兼容性问题的呢?
我们一开始用的是Scala 2.7.7,等2.8.0和2.8.1发布以后,就马上迁移过去了。这个过程很顺利,到2.8.0版本用了不到一天(这还只是因为我打算消除所有 deprecation的警告信息),然后又换到了2.8.1。我们用到的所有库都针对Scala的各个版本相应的发布过多个版本,所以这方面没出现问题。
唯一出问题的地方是我自己的项目,当时我用的是2.8预发行版。但人家已经明确表明这是预发行版了,我还要用,那就是我自己的事了。
我们现在打算用simple-build-tool来构建Scala,换掉Maven。前者能够更方便的针对Scala的不同版本发布内部使用的库。
我宁愿面对升级带来的不兼容,也不愿意看到在Java里面有些东西永远不曾变过,也永远无法改变。我们常用的 HttpServletRequest.getHeader方法到现在还是返回java.util.Enumeration,可Enumeration早在Java 1.2的时候就废除了。
InfoQ: 你现在招人做Scala开发的情况怎么样啊?目前找优秀的Scala程序员跟找Java程序员一样容易么?
我们的主要关注点在于招的是优秀的程序员,而不是专门招Scala程序员。我们希望找到热衷于通晓多门语言的web开发者,至少用过Groovy,或者是Scala、Clojure、Ruby、Python。这种人往往都会喜欢Scala开发的工作机会。
InfoQ: 现在guardian.co.uk一共有多少行Scala产品代码了?
guardian.co.uk每两周发布一次,它的核心代码在两周前还只有一个Scala类。我们之前一直有规定,在guardian.co.uk的代码库上不许写Scala代码。这是为了确保我们所有人都为迎接Scala做好了准备(也免得我总是动出重写的心思来)。
不过我们的microapps服务已经用Scala实现了,在它的驱动下,网站的很多组件也换成了Scala,包括Search(用Lift写的)、 Most Viewed、Punctuated Equilibrium Mystery Bird,在每个页面上显示相关内容的组件也是Scala实现的。
另外,我们新开发的身份校验平台也是用Scala写的,它还在开发阶段,不过第一个版本已经上线了。
InfoQ: 你们打算将来继续用Scala么?
我们发现Scala可以以较少的代码量提升交付速度。这让我们团队重新焕发了生机。我们会继续挑选合适的工具工作,无论是Scala,还是Python、.NET、PHP、Bash。
过去的六个月里,我们新开的基于JVM的项目都用的是Scala,没有一个用Java。我们接下来的新项目肯定也不会用Java了,而且Java 7也没什么让人满意的新特性,发布时间又一拖再拖。
Tackley为想学习Scala的开发人员推荐了Martin Odersky的书《Programming in Scala》,该书的第二版包括了Scala 2.8的内容。(译者注:去年国内出版了一本Scala中文图书,书名为“Scala程序设计:Java虚拟机多核编程实战”。由译者与同事郑晔翻译完成。只是该书中的Scala版本为2.7.4。)他还提到:
Scala REPL(命令行)可以用来试验Scala代码,效果很不错。而且,不管别人怎么说,在刚开始的日子里,只管大胆的把Scala当作无分号的Java用,用上几天、几星期,或是几个月。如果你就到这一步为止了,虽然会错过很多精彩的故事,但这也算得上是不错的体验。你可以循序渐进的来不断学习和接受 Scala的新特性。我觉得,正是因为这种学习方式的存在,才使得Scala成为Java程序员进阶的不二选择。
抛开技术方面不谈,从业务的角度来看,guardian.co.uk的Content API和开放平台服务栈(Content API是其中的一部分)也是很有意思的,因为在UK和其他国家,越来越多的优秀报纸把自己的内容放在墙内而不是共享,卫报则提出了与众不同的方案。迄今为止,金融时报,新闻国际旗下的泰晤士报和星期日泰晤士报都走的封闭路线,而最近纽约时报还推出了付费阅读模式。BBC的资深记者John Humphrys在太阳报(新闻国际旗下的一个小开型日报)上跟人争论说,“优秀的记者必须要得到报酬,就像我们付钱给水管工修水管一样,不然这事就没人干了。”但Tackley对此有不同观点:
我们坚信,数字出版的未来一定是走出去跟互联网集成、协作,而不是退缩到网后。
Content API可以把卫报的影响力和品牌扩展到我们自己力所不能及的范围。这都是使用API的第三方和合作伙伴帮我们做到的,他们会投资某个领域,然后使用相关的卫报内容。
Content API的访问权限分为若干等级:非注册用户可以访问内容元数据,但不能访问具体内容,每秒查询次数也有限制;注册以后,可以看到文章内容──包括嵌入式广告,每秒查询次数也有限制;顶级用户是那些卫报的合作伙伴,我们会签署一份合适的商业协议。
我们有一家不错的合作伙伴,叫做WhatCouldICook.com,它是个个人开发的网站。它调用API来解析获取我们发布的所有食谱,然后换成大众喜闻乐见的形式展示出来。与此同时,我们的读者也会从中受益,因为我们把whatcouldIcook.com的一些功能也聚合了进来。比如在卫报网站右边就有搜索食谱的功能。
我们基于API还做了wordpress插件,所有wordpress用户都可以在他们的博客上引用相关的卫报内容。
像这样在特定领域内的创新,没有Content API就是做不到的。我们还把Content API用在卫报自己的项目上,推动了内部革新;比如search、zeitgeist这些功能,以及手机版站点,iPhone应用都是Content API驱动出来的。
查看英文原文: Guardian.co.uk Switching from Java to Scala