个人简介 Paul King是ASERT公司的带头人,该公司总部位于澳大利亚布里斯班市,主要业务是向客户提供软件开发、培训及指导服务。Paul投身开源项目已近20年,是包括Groovy在内的大量项目的活跃贡献者。Paul多次在国际会议上演讲,并在各种软件杂志和专栏上发表文章。同时,他还是《Groovy in Action》一书的合著者。
Strange Loop是一个开发者组织的软件大会。改革、创新与未来就发生于既有领域“之间”的那些不可思议的关系之中。Strange Loop积极推动语言与技术在这一关系中的融合,将当今最尖端的技术、企业系统和学术研究联系到一起。该会议特别关注于数据存储、非传统语言、并发和分布式系统、前端Web、语义网和移动应用方面的新动向。
1. Paul,你能跟我们谈谈过去几年间Groovy语言发生了哪些改变吗?
好的。我想,Groovy最近被越来越多的项目使用,其中原因不仅在于Groovy本身已经出现了某些激动人心的演变,而且还因为涌现出了大量基于Groovy的框架和项目。就语言本身而言,最近发生的最大变化要算引入了所谓的编译时元编程(Compiletime Metaprogramming),也称AST宏(AST Macro)或AST转换(AST Transformations)。这样,编译器就可以在其中操纵针对该语言构建的抽象语法树。你现在就可以编写转换(Transformation),它接受语法树,在编译时以某种有益的方式对其进行转换。当具备这样的能力之后,你可以完成一些非常有用的事情,如灵活的语言结构、书写DSL等。
它增强了Groovy与生俱来的某些运行时元编程特性。从语言本身来看,这的确是过去两年多发生的最酷的事情。对于像Spock(一个测试框架),Easyb(另一个具有同类血统的测试框架)以及那些利用该能力的其他框架来讲,它具有积极的意义,不可或缺。此外,令人兴奋的是框架也越来越多了!已经发展多年的Grails现在是Groovy领域Web框架的排头兵,现在又有了Griffon。它堪称是Grails的桌面版。Grails面向的是Web应用,Griffon则针对桌面应用,当然也可用于Swing应用。
新框架还有一个。在并发领域,我们有了GPars,这样就有了一个不错的库可以解决你的所有并发和并行需求。Gradle是新近出现的一个非常令人兴奋的构建框架。我刚刚结束一个关于Gradle的访谈。在这一领域发生了很多令人兴奋的事情。在我的客户圈和企业圈中,构建是最让人痛苦的事情之一,Gradle的出现真是条好消息。另一件痛苦的事情就是测试,这是Spock、Easyb和其他测试框架可以帮你消除痛苦的领域。前端也发生了大量令人兴奋的事情。此外,另一件正在发生的变化或许就是Groovy的工具支持变得成熟了。
虽然还不完美,我认为有些地方有待改善,但我们现在得到了非常好的IDE支持。只要你在使用Eclipse、IntelliJ和Netbeans,它们都得到了很好的支持。比如我在使用IntelliJ时,尽管Groovy是动态语言,但该IDE还是可以进行完整的静态类型推断。在Java代码或Scala代码上应用的相同智能感知,现在正发生在我的Groovy代码上,波浪线不是红色而是黄色,表示:“这看上去有点可疑。你正打算把一个日期类型赋给一个数字类型。你确定要这样做吗?”。要是你知道一些古怪但超棒的元编程,并且正打算拿它练手,在敲击键盘时,IDE大部分时候都会捕获到所有错误,跟用静态语言时一样。真是不错!
其他工具也同样提供了增强,我们现在有了CodeNarc,从某种程度来讲,它就是Groovy版的CheckStyle;在动态语言中,你没法做太多的静态分析,但它【CodeNarc】会给你警告。CheckStyle或PMD会给你提供相同的信息,CodeNarc则给你提供针对Groovy的警告信息,而且你还可以轻易地写下自己的规则。因此,你可以开始进行代码风格检查了。如果你打算应用所有的软件工程实践,如测试驱动开发、确保有好的代码风格、确保没有太多重复。如今,诸如Simian这样的工具已经对Groovy有了很不错的支持。好多你想要的东西在动态语言刚出来的时候没有,你可能会想做些妥协来换取动态语言中的高级特性。现在,这些东西已经出现了。
鉴于如今Groovy中不仅仅具备动态语言给你提供的高生产率,而且还可以产出高质量的结果,轻松应用你的职业实践,它的前景的确非常喜人。
2. Groovy是除了Java之外在JVM上实现的首批语言之一,随着植根于JVM上的语言越来越多,这些语言间会需要大量的通信和共享,同时JVM本身也已变得对语言更友好。这对Groovy语言有什么帮助?
我想这里可谈的内容有很多。事实上,现在JVM上有很多优秀的语言,只是在流行程度上有差异,我们已经有了Jython、JRuby、Scala、Groovy等语言,Clojure是最新的成员。这类语言还有很多,而且它们其实在不同程度上推动了业界接受多语言系统,你在其中使用这些新语言对于软件工程开发来讲完全是可以接受的。而Groovy在诞生之初遭遇到了来自保守Java阵营的巨大阻力,“哦,我们不能使用Groovy,我们会等Java支持闭包,这要不了多长时间,应该在Java 1.6.2中,这要不了几个月”。
采用新语言曾经有很大阻力。但是现在有很多特性,这些语言每种都有不同的特性,根据问题域的不同,人们不再像过去一样惧怕使用新语言。当我有一种需求,而正好这些语言中有满足那个需求的时候,我会放心大胆的去用它。在框架中你也能够看到类似的情况,像我之前提到过的Grails、Griffon和Gradle,它们对多语言都提供了不错的支持。如果你想用Scala写一个控制器,尽管去做。如果你一定要用Clojure的持久化数据结构库,在Groovy代码中用就是了。在多语言的世界中,这样的例子还有很多。
我猜你暗示的是像invokedynamic以及其他发生在JVM层面的事情。我们正打算开始在JVM上的某些语言中调查它们造成的差异,包括JRuby未来发布的某些版本,JVM上的某种Perl,以及其他大量使用这种设施的其他语言。Groovy只是刚刚开始利用这些设施,它对我们有些帮助,但由于我们实现的方式,帮助并不是很大。但随着这些工具变得成熟以及在人们的平台上可用,你会看到越来越多的类似东西出现,Groovy同样也会对它们加以利用。
它会让有些我们目前要花大力气才能完成的事情变得非常简单,大大降低我们的工作量,某些情况下还能有很好的性能改善,但还有很多其他东西没有得到简化,仍需我们自己去完成。这不要紧,好消息是JVM本身正在进化。
3. 在你提到的这些在JVM上运行的语言中,Groovy的语法看起来最像Java。你是否认为Groovy开始发挥一种效应,充当了JVM上其他语言的引路人?
我想可能吧。比起JVM上的其他语言,Groovy的自身定位是成为其中最接近Java的语言。它想尽可能地向Java靠拢。这既有优点,也有缺点。我认为其中的一个优点是Java程序员天生就是Groovy程序员,通过剪切和粘贴,99.9%的代码都能做到开箱即用。这时,愿意的话,你可以慢慢加入Groovy的习惯用法。我认为一旦你开始使用它去做无法用Java完成的事情,你就会说:“嘿!瞧,转向Groovy,我马上就有了闭包、领域特定语言和并发等支持。不必在想用它的时候苦等Java 8或9。”
一旦你说:“哇,瞧瞧敞开在我面前的世界”,接下来你马上会跳起来喊:“哦,我还有Scala、Clojure和JRuby等其他选择”。所以,在某种程度上,它也确实打开了通向其他事物的大门,而且它就属于JVM上那些被接受的其他语言,我认为Groovy已经帮忙铺平了道路,但它肯定不是正在改变人们对其他语言看法的唯一事物。
4. Groovy最适合哪类问题、挑战和解决方案?
要轻率地回答这类问题,并不难。在遇到过的绝大多数场景中,我都可以看到用Groovy解决大多数问题的方法。因此,回答Groovy在什么情况下不适合,对我来说会更容易一些,可能这是一个非常小的清单。我觉得不同语言都有优缺点,像Scala或Clojure,它们非常适合解决并发或并行问题,假如你要完成大量该方面的工作,用Scala或Clojure可能会更容易一些。在某种程度上,它们会指导你以一种简单的方式去开发代码。反观Groovy,它则不是单纯地只解决并发或并行问题。
它给你提供了一组选择,你可以开始使用Groovy并且采用一些Java特色的遗留实践,它们在利用并行和并发时并不像你想象的那样好,Groovy并不会阻止你去使用那些实践。但另一方面,如果你熟悉用Java解决并发需求,你可以拿它试试看,并将它带到Groovy里,仍然以一种非常相似的方式工作。若是愿意,使用像GPars这样的库,会给你提供一种非常好的并发DSL,简化了完成这类任务。接下来,如果你想在Groovy中开始使用某种更现代的并行习惯用法,可以使用Actor、数据流(Dataflow),GPars给你提供了这样的选择。
软件事务性内存(Software Transactional Memory)是未包含在当前GPars发布包中的另一种方法,但未来可能会加上。这样,你已经得到了一组选择。我猜,假如你正在拿它跟Scala或Clojure对比,在并发方面,后二者能提供的选择非常有限,而Groovy则打算提供更多选择。从某种意义来讲,这也增加了开发者的负担,因为他们需要知道选择使用哪种方案。因此,你可能想要得到类似“何时该使用Actor?”的这类指导,它并不是假设你在任何时候都想使用Actor,而是给你提供了一组选择。有时Actor不错,有时则数据流更合适,而有时你不得不去了解更低层次的东西,使用某些Java提供的原语等等,这些情况它都能满足。
因此,对于Groovy适合哪些场景这个问题,在面对客户时,我往往会去了解所处环境,再决定要用的语言和工具集,这取决于开发人员的技能、他们的背景,他们是否是Java的程序员,是否曾做过函数编程,他们之前是否了解其他的语言,他们习惯使用哪种工具,他们是否只使用特定的IDE等等。根据这些情况,我有时确实能看到用一些好方法可以让Groovy帮你的大忙。而其他时侯,我可能会说尽管我觉得Groovy可以提供非常大的帮助,但我们最好还是坚持用Java或是可能要看看JVM上的其他选择。
我想这部分回答了你的问题。Groovy非常通用,能应用到许多场景。它的缺点是它没有被剪裁成只做并发或是只做并行,单纯地追求速度不是构建它的目的,所以有时用Groovy完成某些工作可能要比你平常用像Java这样的静态语言要慢很多。现在,只要聪明的话,你可以给Groovy加速,通常这不是大问题。我还没有遇到过一个客户认为Groovy的速度是个问题。我总有办法可以让局面变得Groovy带来的生产力提高所获得的收益远胜于任何性能改进所获得收益。
Groovy的适用范围相当广,但根据环境,没有最好只有更好。
5. 你提到了跟Clojure和Scala有关的并发思想。Groovy如何帮助你写出并发代码?
对于这个问题,有许多答案散落各处。如果你指的是在使用Groovy本身和使用GPars库(它是时下的并发/并行库,我猜它或许是Groovy世界里最受欢迎一个库了)之间编写并发代码的所有不同方式,我认为它俩给你划定了一个完整的选择范围。要是想以并发方式执行任务,我觉得解决办法有很多种。若想在并发世界里共享状态,这会是个问题。有些事情,Groovy确实把它简化了;有些库,也以并发的方式构建到了Java中,你可以接收一个集合,然后使它成为不可改变的,对于某类问题,这种做法让解决某类问题变得简单。
其他类型的问题可能用像Actor这样的机制最合适,当你在进行消息传递时,GPars将给你提供这方面的解决方案。另一种非常好的解决问题的风格叫做“数据流”,回想我读博士的时候,它真是个很热门的概念。但后来它消失了,我们发现虽然概念的确很不错,但很难实际解决问题。现在,它重新又回来了,GPars库提供了非常好的数据流解决方案,以及大量在传统Java或C#中可以看到的命令式编程范式。即那种“如何做,而不是去做什么”的类型。相反,像逻辑编程或数据流编程则非常关注于做什么而不是如何做。
因此,我想把这两方面的好处都结合起来,而不是固守一种方法。用数据流处理某类问题是一种非常有趣且不错的方式;它消除了很多关于竞争条件、死锁、活锁相关的问题,以及一些你可能要面对的其他问题。部分这些问题可以用数据流完全消除,就算是不能完全消除的,在你死锁时,它们也都是静态可检测的。举个例子,如果我有个数据流表达式说:“把X变成Y”,另一个说:“把Y变成X”;很明显,这是循环依赖,它可能永远无法解决,所以死锁发生了。
情况是,我可以用静态分析检测出存在死锁;如果你在用数据流解决问题,那么你可以写单元测试或使用静态分析工具,我可以预先告诉会发生的事情。是的,那段代码有问题,但还有更难的事情,你当然可以派生出代码原则,并试图证明那里不存在死锁或者没有问题,那非常难而且会花大量的工作。相反,数据流风格的语言是可以修复这种问题的。而且我认为,在整个数据流领域,还有另一类非常相似的语言,它被称为通信顺序进程。(Communicating Sequential Processes)。
CSP同样是多年前由Tony Hoare提出的表示进程间如何进行交互的很好方式,GPars里包含了一个CSP实现。在某些方面它可能有点象Actor或数据流,但它有一些自己的特点,是某类问题的理想解决办法,而这类问题对于程序员来讲同样是很大的负担,“我是否必须去证明……”,我已经开发出了自己的线程编程模型,已经得到了自己的锁,我如何证明已经以一种一致的方式把所有变量都设置好了?每次我自己运行我的程序都跑得不错,可一旦将它放在高负荷的环境里,它就不再工作了;“怎么回事?”。对于某类问题,像CSP这样的工具可以让我以这样的方式设计我的系统:在其中,系统帮我准备不会失败的东西。
所以,在Groovy和GPars库之间有一整套选择可供使用,而后者更是可以让我完成很多我想用并发和并行解决的事情。例如,Groovy内置了@Immutable转换,它可以让集合变成不可修改,能够使用元编程拦截任何想修改集合的行为。使用元编程,我可以知道是否有人想修改某个东西,即便我正在继承的Java库,其设计者并未专门为并发考虑,没有把类设计成不可变的,但是我还得要用它,因为它已在系统中使用多年,而且设计者也不想改变系统;只要愿意,借助一点点元编程,我就可以创建一个小小的拦截器。
它非常像Aspect或其他类似的东西,其中我可以看到任何改变代码内部结构的尝试,并阻止它的发生。这非常有用,作为一种穷人跻身并发世界的方式,但是给你带来了一些遗留类。因此,取决于特殊的需求,真的存在着很多选择。这是个非常有趣的领域,接下来会有更多新鲜的东西。软件事务性内存是另一类解决特殊问题的工具,它是一种解决你打算如何保存“状态信息”的理想方法。‘禁止使用状态’说起来不错,但取决于你所处的抽象层次,状态可能是一种表示和实现特殊系统的绝佳方式。
像软件事务性内存这类机制可以让你这样表示系统:以一种可以让你随着时间演变状态,并且及时得到系统在某一时间点的一致性快照的方式组合状态的想法。GPars目前尚未实现这个功能;Java、Clojure的有些库给我们提供了该方面的优秀解决方案,未来我们有可能在GPars中也看到类似的功能。若真是这样,那就太好了。
6. 作为软件开发者,我们已经能够很好地推理出顺序程序的工作方式,像先做A再做B最后做C,但是对于并行计算的一个挑战就是,软件开发者很难找出该系统中正在发生什么,以及搞清楚其真正的工作方式。对于并行编程,我们怎样才能有一种更直观的方式让我们这些开发者可以根据我们的代码有效推理出实际真正发生的事情?怎样做才能使之更直观?
我想说你提到的这些困难目前还没有好的解决之道。我记得以前在做多线程编程的时候,当时还是用C++,你会去写程序,它基本可以工作,但会出现非常严重的事情。你把它丢到调试器(它当然对当前的各线程一无所知)里,全部线程突然都停止工作了,所有都是一起发生,而且是突然的:“它几乎可以工作,除了一种情况”到“根本就无法运行”,因为早期的调试器甚至不知道多线程,而只是中断它,你基本上是把所有的东西都丢掉了。从那时算起,我们已经走了很长的一段路,调试器现在已经足够聪明知道多线程之类的东西了。
多线程思想可能不是最好的起点,即使在早期,我们也在寻找替代方案。因此,如果我将任何事物都以数据流语言或者是逻辑编程语言、某些第四代语言来表示,它们都是去找出什么是解决这一问题域的最佳方式的尝试,因为坦白的讲,这是非常让人头疼的问题域。而且,我认为我们仍然还在非常早的阶段,即便我们已经在10年、20年前已经进行了相同的讨论,我仍然认为实际解决问题还为时过早。
同时,在并行编程模式方面,我们还没有一本书像GoF的书【《设计模式》】那样可以让我们说:“对于这类问题,有一种很明显的解决办法,这非常直观。”。因此,我们有点像是仍然在探索我们所有的方案,在某种程度上,GPars正在做这件事;它给你提供了一组选择。在Groovy中,我可以在这个集合中完成foreach,做点事。GPars则表示,我提供了一种方式可以让你以并行的方式对每个元素进行操作,有时它可以非常优雅的解决我的问题,其他时候这该死的家伙则完全崩溃了,因为我想以并行方式做的东西没有被设计成线程安全的。因此,某类问题真的可以非常简单的解决,它完全就像我的顺序程序一样。
有时,则有一些跟这完全不同的其他类型的问题。我们已经实际碰到很多遗留问题,在深入GUI编程时,所有事情都必须发生。在GUI内部,肯定有个线程具有特殊的权限,你要确保你切换到正确的线程上去工作。所以,要想让事情变简单,我们还有很长的路要走,我认为最佳方式就是开始尝试寻找各种不同解决方案可以真正很好发挥作用的环境。这是我目前欣赏GPars框架的地方,因为它给我提供的解决某类问题的Actor解决方案被证明是一种真正有效解决问题的方法。
但是从我目前遇到的很多问题来看,依我经验,它实际甚至不是具备明显优势的问题最佳解决方案。有20-25%的算法可以在使用Actor时工作得很好。其他有些问题可以非常好的用我前面提到的并行each方法解决。其他有些问题则最好使用数据流、通信顺序过程、软件事务性内存,因此,根据环境,我是否需要事务行为,我是否确实有程序运行在多进程环境、跨多台机器中,实际情况是什么?根据这个上下文,我们仍然在寻找解决问题的最好办法。
因此,在我看来最好就是去寻找问题的类型,尝试以不同的方法去解决它,然后发现在这个环境中这个方法工作得非常出色,就尝试以类似GoF的书那样去捕获知识。这是我认为现阶段我们可以做的最好的一件事。假若一、两年后还有类似的采访,我们到时就能说:“现在我们知道这类问题可以用软件事务性内存做得很好,这个最好用Actor/那个最好用数据流。”。这类东西将成为公共知识和公共实践,但目前不是。我想,要真的能这样,那会是非常让人激动的时刻。
7. 你已经提到在并行和并发开发领域我们仍然还处在摸索阶段,但目前我们正处于了解机器CPU内核有多少的过程中,它看起来呈快速增长趋势。如果我们没有给开发者提供直观好用的并行和并发结构,这何时会成为一个真正的问题?
我想很多人都在说:“看看摩尔定律,看看处理器,假如明年这个时候我们还没有把这个问题搞定,我们就完蛋了”。就在几周前的JavaOne会议上,我还在给一个处理器有些慢的人说,当20-30年前人们在解决某个特殊问题时,系统中可以找到的最快计算机是他们打印机里的处理器,他们就真的是把计算发送到了打印机的postscript处理器上,让它计算完答案再传回给他们的计算机。所以,这不是个新问题,这完全是如何做到物尽其用。因此,我不同意有些人说事情发展太快,我们都没法应付了。
但我们确实知道这是无法避免的,我们必须解决问题。我们可以依靠Apple已经用了很久的老方法来解决这个问题,即我们不允许并发,即便硬件完全有能力进行多线程计算,他们说:“我们将在处理这类事情的方法上强制某些约束”。他们打算给用户施加的那点影响,大大简化了我们的开发。随着时间的流逝,这些约束将会被撤除,我们将提供更好的方式给用户展示信息,允许所有事情以并行方式进行。这是一个存在已久的老问题,即便是摩尔定律使其成为我们确实需要立刻开始着手解决的问题,但我认为它不是我们所知的IT世界的终点。
我们将找到问题的解决办法,我们获得的大量知识将帮助我们,但我认为它将缓慢、稳定的成长。我想我们将看到的是仍然是像目前在iPhone上的那样的折中方案,我们将得到有限的工作用计算环境。看看Java EE容器,它给程序员提供了一种简化模型。即便我们已经有了一个并发的应用服务器,我们仍假装我们写的每段代码都运行在单线程中,我们在幕后解决如何让所有事情以并发方式工作的问题。因此,我们可以做很多事情来让我们自己不是总直接处理并发,但我们必须提前工作,同时也处理并发。
因此,我们将看到解决这类问题的各种解决方案,随着我们演变我们的编程语言、工具和并行模式,我们也将有能力处理事物的并行一面。
8. 非常感谢
谢谢。