第一部分:重构
第二部分:设计原则与代码所有权
第三部分:进化型设计
第四部分:灵活性与复杂性
第五部分:测试驱动开发
第六部分:性能与过程调优
可维护性与效率
比尔:我在丹佛机场的红地毯俱乐部(Red Carpet Club)[1]中常常碰到名人。今年夏天我碰到了 Calista Flockhart (卡莉斯塔·弗洛克哈特)[2], 而去年我碰到了你。我是个追星族,但是由于害怕哈里森·福特,没敢跟 Calista 搭讪。不过,你和我倒是坐下来喝了杯啤酒。记得当时你曾对我说过,应该以程序员能读懂的字符格式来序列化对象,而不是以二进制代码格式。当我提到字符格式 要比二进制码格式慢时,你说,从效率的角度来看,二进制代码格式使得软件更加难以维护。那么,能否请你谈谈关于序列化方式的具体案例?一般地说,你如何在 可维护性与效率之间寻找平衡点?
马丁:效率永远是第一位的,前提是你能正确理解它。很多时候问题在于,人们以为做某些事情是为了效率着想,但他们却从不使用性能分析器(profiler)。如果你出于效率的考虑而做某件事,但却不使用性能分析器,那么你所宣称的根本就不着调。
序列化所牵扯的问题要多一些。使用二进制代码做序列化的问题之一就是你无法去查看结果。当你需要存储序列化的对象时,这个问题就更加突 出。Java 的一个典型问题就是如果你改变了一个类,那么就无法读取以前所序列化的对象。类似的,如果一个客户端和一台服务器正通过序列化的对象进行通讯,假如一端的 数据结构进行了更新而另一端没有,那么整个通讯就彻底失效了。
有一个小窍门可以让你绕开这个问题。不要序列化对象本身,而是把数据从对象中提取出来,放到一个字典里,然后再序列化那个字典。这么做会使你能够应对一些变化。
比尔:但是,字典是“不明确的”。我们之前刚刚说起过这点。
马丁:的确,字典不是“明确的”。不过,如果你往类里添加一个字段,并把这个多出来的值放到字典里 的话,不会有什么问题。因此,这是一个比较强壮的机制。XML 一般也比较强壮,因为你可以对你所不了解的数据视而不见。二进制序列化的主要问题就是它的脆弱性。在我的书《企业应用架构模式》中,更多地提到了序列化的 方式。例如,在数据库中传输和存储数据时,就需要考虑介于字符和二进制之间的序列化格式。
编写可性能调优的软件
比尔:你在《重构》一书中写道:“编写能快速运行的软件的秘诀就在于先编写可性能调优的软件,然后对执行速度进行调优。” 我该怎样编写可性能调优的软件呢?
马丁:只要做到软件结构合理就可以。
比尔:怎么讲?
马丁:因为结构合理的软件可以更容易地进行改动。
比尔:也就是说,这时候软件要更容易地进行改动,不是为了添加新功能,而是为了提升已有功能的性能。
马丁:结构合理的软件能够更容易地进行调优。你应该首先专心于让软件结构合理、设计清晰,然后,在性能调优器的引导下,完成性能优化过程。
优化
马丁:还有一件事需要牢记:性能优化与版本和具体的实现是密切相关的。当你拿到 Java 的一个新版本时,一定要把以前所做的优化都撤消,然后重走一遍优化过程,以确保那些优化手段仍然奏效。通常你会发现,你为上一个版本的虚拟机 (virtual machine)或优化型编译器(optimizing compiler)所做的性能优化往往使当前的版本变慢,也即,之前的优化手段如今往往起到适得其反的作用。
比尔:要记住以前为了提升性能都做了哪些改动可不是件容易的事情。
马丁:你必须这么做——先撤销,再重新应用。我知道这不容易。这就要求你对优化过程中所做的每个改动都要有详细的记录。要知道,旧的优化所造成的一些微不足道的性能损失,在新的版本下有时候可能会变得非常显著。
Craig Larman (拉曼 C [3]) 曾经讲过一个故事,我到现在都还很喜欢这个故事。Craig 有一次在 JavaOne 的大会上做性能优化的讲座。他提到了两个广为人知的技术:对象池(object pooling)和线程池(thread pooling)。对象池就是重用已有的对象,而不是每次都创建新的对象。线程池的原理基本类似。讲座结束后,有两个人来到 Craig 跟前。这两个人都是设计高性能虚拟机的。其中一个虚拟机是 Hotspot,另一个好像是 JRocket。一个人告诉 Craig,线程池的效果不错,但对象池则使得虚拟机的运行变慢;而另一个人告诉 Craig 的恰恰相反。
所以,你有可能在一种虚拟机上优化了性能,但拿到另一种虚拟机上,却减慢了其运行速度。对此,你要特别小心。对象池就是一个很好的例子。 很多人热衷于对象池,但起码有一半的情况下,人们并不去测量对象池的效果到底是好是坏。在 Java 的早期日子里,对象池非常重要,因为垃圾回收(garbage collection)功能还不是很完善。但在垃圾回收技术更新换代之后,对象池的效果就大大降低了,因为那些生存周期很短的对象可以被低成本地回收。只 有那些生存周期很长的对象,才适合使用对象池技术,因为对它们进行垃圾回收的成本很高。
从这里可以看出,规则也是在不断变化的。这就是为什么要对性能调优很仔细的原因所在。不要妄想根据源代码就能预测机器会做什么。当你与虚拟机或优化型编译器打交道时,性能调优是唯一的手段,因为编译器和虚拟机所做的事情,远远超出你的想象。记住,不要预测,要实测。
模式的意义
比尔:你在《设计是否已死》这篇文章中表示:“对我来说,模式仍然是至关重要的,”尤其是考虑到极限编程的话。那么今天,模式处于一个什么样的地位呢?
马丁:模式给了我们瞄准的目标。我还会做一些预先设计。模式就是用来干这个的。模式还给了我们以重 构的目标。我们得以知道我们改进的方向。了解模式还有助于我们找到设计美感,因为模式至少都是一些好的设计。你可以从它们身上学到很多。因此,我仍然认为 模式起着重要的作用。其实,很多积极推动极限编程运动的人,本身就活跃在模式社区里。这两个社区在很大程度上是重叠的。
敏捷宣言
比尔:最后一个问题,敏捷宣言到底是怎么一回事?
马丁:哦,那是一群投入到敏捷软件方法领域的人,聚在一起,彼此印证心得。这个领域包括极限编 程,scrum,crystal,修错驱动开发(fix-driven development),和动态系统开发方法(DSDM)。我们意识到我们之间有很多共同之处,因此决定把我们所认同的写下来。我们把这份宣言看作是一 种象征——在这面大旗下汇聚了支持此类方法的人们。在那次聚会中,我们决定使用“敏捷方法”这个词。这份宣言可以作为敏捷软件开发的一个定义。借助这份宣 言,我们共同呼吁,软件业的主流应该朝着这个方向前进。
比尔:让我们来看看四条原则中的一条:“个人与交互重于过程和工具”。这是什么意思?
马丁:这条原则大意是说,与其借重过程和工具来加强对软件开发的管理,不如更多地关注于团队及其成员,关注于每个个体以及他们之间在个人层面上的交互。
比尔:你是说提升他们的技能么?
马丁:要远比这个丰富得多。它包括了提升技能;它还包括要竭尽全力使程序员们身心愉悦,从而得以留 住人才;它还意味着更认真地对待个性冲突,注重人与人的相处,而不是试图找出某个完美的软件开发过程,然后要求大家都来遵守这个过程。我对这条原则的理解 是,应该是团队选择适合其的软件开发过程,而不是让团队来适应指定的开发过程。
尽管在那次聚会上,我们中的许多人都津津乐道于自己所采用的开发过程,并且我们当中的几个人还是软件工具销售商,但我们一致同意,对于一个项目的成功来说,软件开发过程和工具只是次要的因素,最主要的因素还是团队,是团队中的成员,是他们人性化的合作与努力。