动态语言企业应用优缺点浅析

阅读数:6505 2010 年 7 月 6 日 04:26

动态语言的兴起已经有些年头了。现在,人们早已不再去争论动态语言是否能够取代静态语言,因为这种争论毫无意义。越来越多的开发者开始在动态语言更为擅长的领域应用它们。比如, Django Ruby on Rails 等开发框架的盛行使得像 Python 和 Ruby 这样的动态语言可以在 Web 开发领域大放异彩,PHP 和 JavaScript 也早已在 Web 开发领域占有一席之地。

不过目前动态语言在企业开发中的应用还不够广泛,很多企业只是用它来做一些粘合系统的工作,并没有承担起主力开发语言的重任。尤其是在底层系统开发方面,动态语言远没有在 Web 开发方面那么风光。在运行时效率和虚拟机稳定性方面的不足,使得动态语言注定无法与编译型语言竞争,并取代它们在高性能领域的地位。然而,动态语言也有自己的优势所在。如何克服自己的劣势,将优势发扬光大,便是每一位动态语言开发者所面临的机遇和挑战。

我所在的团队用了近两年的时间,将一个电信领域的公司绝大部分的生产系统用动态语言(主要是 Python)重写。包括短 / 彩信消息网关、业务订阅服务、座席查询系统、销售支撑系统,乃至搜索引擎等多个核心系统,都在重写之列。重写的理由很多,一方面原有系统无论是从性能上,还是从应对需求变化的能力上,都已经不能满足业务发展的需要;另外一方面,动态语言的诸多优势,也是我们重写的动力。这里仅以开发这些系统时获得的经验,来谈谈动态语言在应用时的优缺点。

动态语言的优势

动态语言的优势有很多,归纳起来主要有以下几个方面:

1. 生产力。动态语言在开发效率方面有着无与伦比的优势,这也与动态语言“优化人的时间而不是机器的时间”这个理念相吻合。利用传统的静态语言要开发几周的功能和特性,使用动态语言也许几天甚至几个小时就可以实现。不仅如此,动态语言在开发原型系统和常用工具方面的开发效率也非常高,尤其值得一提的是原型系统。更快地让原型系统运转起来,不仅可以尽早验证一些假设,也能够更好地与迭代开发相结合,更及时地与需求方进行沟通,帮助需求方挖掘和了解自己真正的需求。开发效率可以说是动态语言最为吸引人的地方,这也被认为是将来开发语言的前进方向。这些年随着敏捷开发的盛行,越来越多的开发者意识到,原来动态语言的特性和敏捷开发的价值观也相当契合:缩短反馈时间,对变化的响应能力更强。所以许多动态语言团队选择了敏捷开发的实践来组织团队。我们在实际开发的过程中,以两周为一个迭代周期,基本上 1-2 个迭代就可以完成一个系统的开发和上线,而原型系统的开发通常能在 1-3 天内完成。

2. 代码量。曾有报道说,用 Ruby on Rails 写同样的项目,代码量大概只有 Java 的 1/10。且先不说这个说法是否有夸张的成分,但就实际来看,动态语言的确从代码量上来说,要比 Java/C/C++ 等传统静态编译型语言要少的多(当然语言的表达能力与动态静态关系并不大,静态函数式语言的表达能力也很强),可能几千行的项目就算得上是个大项目。例如,我们开发的系统中较为复杂的消息网关,生产代码行数大概在 7000 行左右;而订阅服务系统的生产代码行数不到 3000 行;借助于 Xapian 的 Python-bindings,我们的搜索引擎系统代码行数只有 800 行左右。代码量少的好处非常多:首先,这意味着将来需要花在维护上的代价更低;其次,因为代码少了,犯错误的机会也就少了,因此代码量少还意味着 BUG 的减少;再次,代码量的减少也有助于优化系统,有句话说的好,“There is no code faster than no code”。开源项目 Nebula Device 有一个设计哲学,就是“每一次Release 要比前一次代码量更少”,窃以为高论也。

3. 测试。因为动态语言很容易实现反射等动态特性(JUnit 也是等到 Java 支持了反射以后才出现的),因此测试也更为容易实现。Python 和 Ruby 的标准库中都带有 unittest 的框架,这几乎可以让你无成本地使用单元测试来加固代码。因为动态语言本身不具有编译过程,因此犯下某些低级错误的几率大大增加,也为重构带来了重重困难。没有单元测试的重构如同梦魇一般,动态语言尤甚。因此,在开发语言以动态语言为主的开源项目中,单元测试总是占有相当大的比重。还有建议称测试代码与生产代码的比率( Unit Test To Code Ratio )要达到 2:1 以上。另外,动态语言的测试环境更容易搭建,实现 Mock 也更为简单。

4. 原生数据结构。现在主流的动态语言多为脚本语言发展而来,而在这些语言中,集合、列表和词典这样的数据结构都是原生的,而静态语言的数据结构往往是通过程序类库来实现的。比如 Python 就提供了 set、tuple、list 和 dict 等原生数据结构,同时还提供了大量操作(如数组分片等),让这些数据结构使用起来非常方便。原生数据结构使得对数据的操控融入到了语言的语法当中,让程序更为易读,这也让基于代码的沟通更为顺畅。

5. 简单易学。动态语言的语法相对简单,学习成本看似也比较低。有人举例说,Python 和 Ruby 写个 Hello World 只需要一行即可,这是很多静态语言所达不到的(把多行代码写成一行的不算)。当然你可以认为这只不过是句玩笑话,不过单就语法而言,动态语言的学习门槛要比很多静态语言要低的多。可是,开发不仅仅只是语法而已。很多动态语言的初学者,能够用动态语言写一些简单的小程序小工具,却很难构建起庞大复杂的商业系统,究其原因,主要是还是因为系统设计和面向对象的功底欠缺所导致的。如何设计,如何抽象,如何重构,这些能力与语言无关,而是个人的修为。正如陆游所言,“功夫在诗外”,这些能力也不是一朝一夕、通过学学语言就能够轻易练就的。当然,动态语言的各种特性(如 Duck Typing)也使得在静态语言中不得不使用的设计模式可以很自然地表达,这些差异也增加了动态语言学习的隐性成本。

不足之处

任何事物都具有两面性,动态语言也不例外,虽然优势显而易见,动态语言的不足之处也有很多。这里列举一些我们在开发过程中所遇到的问题,以及一些初步的解决方案,来供大家参考。

1. 运行效率。运行效率低下使得动态语言饱受诟病。“跑得太慢”这顶帽子已经在动态语言的头上扣了许多年。甚至有 Benchmark 表明,在某些应用场景下,动态语言的运行效率和 C/C++、Java 等成熟的静态语言相比,相差数十倍甚至上百倍,这也为动态语言的普及埋下阴影。不少开发者因为运行效率的问题,纷纷表示 “对动态语言很失望”。其实我倒是觉得大可不必纠结在这个问题上,原因有两点。第一,很多动态语言的应用场景使得运行效率的重要程度大大降低。就拿 Ruby on Rails 来说,在 Web 开发这个应用场景里,数据库的响应时间无疑是最大时延,与之相比代码运行时间就微不足道了。而且通过 Cache 和优化,基本上可以消除代码运行效率低对项目的影响。又如我们的消息网关系统,最耗时的部分就是网络通信和文件 I/O,而这两部分动态语言和静态语言相比并无明显劣势,运行效率的问题可以完全忽略。第二,如果遇到很耗 CPU 或者很耗内存的运算,完全可以通过 C/C++ 实现的扩展来解决。无论是 Python 还是 Ruby,都支持采用 C/C++ 编写扩展。通过这些扩展,可以极大地提高运行效率,从而弥补动态语言在运行效率上的不足。

2. BUG 难于发现。动态语言由于没有构建的过程,因此很多错误只有等到运行时才会发现。而这些错误很可能是些低级错误,比如拼写错误、没有 import 相关的类库,或者括号不匹配等等。如果每次修复这样的 BUG 都要通过去测试环境中部署来验证的话,则会浪费了大量时间。因此动态语言往往需要充分的自动化测试套件,才能够确保代码基本可用。另外,使用动态语言的时候,一个良好的代码静态检查工具也是很有必要的。它不但可以纠正一些低级错误,而且还可以帮助你发现代码中的 Bad Smells,大大提高开发效率。对于 Python 来说, Pyflakes Pylint 都是不错的选择;而 Ruby 也有众多工具可供使用。测试充分的代码也更容易重构,在重构动态语言项目时要万分小心,因为动态语言极容易犯错,稍不留意就会引入新的BUG。保持小步前进的步伐,每次修改后都执行测试,最好再通过持续集成环境来帮助发现测试失败的情况,这样重构起来才能得心应手。

3. 专业人员少。不少使用动态语言的公司都会遭遇一个问题,那就是使用动态语言的资深开发人员很少,不但很难招聘到靠谱的员工,核心人员的离队也会对公司造成很大的损失。这是因为完全使用动态语言进行开发的公司少的可怜,只有极少数的开发者能够参与其中并获得相关的开发经验。绝大多数的动态语言使用者还处在爱好者阶段,跟着 Tutorials 写写 Demo,或者随手写个 Utils 等等。因为高水平的动态语言开发者的确是可遇不可求,因此寻找有经验的开发者也许要花上不少的时间和成本。当团队有了较为有经验的开发者以后,就需要通过内部培训、结对编程等手段,帮助公司里没有经验的开发者迅速积累经验,逐渐成为动态语言方面的靠谱人才。其实,对于动态语言的圈子,还有一个有趣的说法:因为学习动态语言的人往往都是在其他领域有了很深的积累后,在有余力的情况下才接触动态语言的,因此往往相对都比较靠谱,动态语言的圈子反而能够帮助雇主们甄选出一批高素质的开发者。

4. 不够成熟。动态语言的发展历史虽然不比静态语言差到哪里(比如 Ruby 和 Java 就同为 1995 年始创),然而由于其较为小众,因此无论是虚拟机的实现上,语言本身的机制上,还是相关的配套工具上都算不得十分成熟。例如,Ruby 虽然以其优美灵活的语法为人所称道,但也因为其虚拟机效率低下和内存泄露问题所为人诟病,使用 Ruby on Rails 的网站往往需要加配监控程序,一旦发现某个 VM 内存超标立刻重启;Python 的虚拟机虽然还算稳定,但长久以来一直受 GIL( Global Interpreter Lock )问题所困扰,完全无法发挥多核的优势,这在家用 PC 都早已多核的今天的确是个不小的问题(事实上 Ruby 也存在 GIL 问题)。不过,虽然官方实现不够成熟,现在已经有很多逐渐成熟的其他选择可供使用。比如 JRuby 就充分利用了 Java 成熟的虚拟机和 Ruby 优良的语法特性,还可以允许开发者使用 Java 背后庞大的类库。通过 multiprocessing Stackless Python ,甚至手工将任务切成多份,分发给多个进程运行,都可以规避掉 GIL 的问题,更充分地利用系统性能。当然,随着时间的推移,动态语言的实现将会越来越成熟,不但 MRI 逐渐完善, MagLev Rubinius 等一系列优秀的 Ruby 虚拟机也开始登上舞台;Python 3000 甚至打破了向后兼容性,试图将 Python 以前的设计错误全面改写。回头去看 Java 等一批成熟开发语言的发展路线,有谁没有经历过不成熟的青春期呢?

小结

通过实践我们发现,动态语言既不是什么洪水猛兽,也不是什么奇巧玩物,它们已经逐渐成长为称手的兵器,帮助开发者们快速完成项目,进而达成商业目标。使用动态语言,已经让我们切切实实感受到了它的开发效率为我们所带来的好处。在商业机会瞬息万变的今天,谁能以最快的速度实现自己的想法,谁能尽快应对市场带来的变化,谁就能离成功更进一步。

诚然,动态语言目前还存在很多问题。但瑕不掩瑜,如果在使用时可以意识到这些问题,并善加处理的话,动态语言也可以成为复杂商业系统的主角,在企业开发中占据自己的地位。而且随着开源社区的努力,很多问题正逐一被解决。我们有理由相信,在不远的未来,动态语言一定会有一片更为广阔的天空。

感谢田乐(Tin)和赖翥翔(Jason Lai)对本文提出了大量的反馈意见,感谢霍泰稳为本文找到如此贴切的标题。


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

评论

发布