Guardian.co.uk 从 Java 切换到 Scala

阅读数:6239 2011 年 5 月 25 日

话题:JavaScala语言 & 开发架构

用卫报(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 写。主要原因有三点:

  1. ScalaTest 提供了很灵活的测试 DSL。
  2. 我们希望写集成测试的过程充满愉悦,而不是令人厌倦。
  3. 只用 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 用在卫报自己的项目上,推动了内部革新;比如searchzeitgeist这些功能,以及手机版站点,iPhone 应用都是 Content API 驱动出来的。

查看英文原文: Guardian.co.uk Switching from Java to Scala


给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。