2007年03月19日

上一篇文章里,我们提到了重构是现代开发的一项利器,因为它同时满足现代开发的核心思想:正交性(做一件事情不会影响到另外一件)、小步快跑(人的大脑在处理多头绪的事情时效率不高,多头绪的事情要分解成大脑最擅长处理的单头绪)、实用优于优雅(不过多考虑将来的事情,用最简单明了的方法解决当前的工作)。

在我的实践中,小步快跑式的快速叠代开发,效果很是令人满意。在2006年11月至2007年1月间,项目已经内部发布了24次,一直稳定地在生长,直到年关前,我决定进行一次大的重构为止。

让我简单回忆一下,当时为什么决定打破常规,做一次大型的重构。

最开始,我只是想进行一些优化工作,一方面项目的代码有些乱了,另一方面系统有一些运行瓶颈。我想在过年前做一些完善,发布一个让我自己舒服的版本,好带着好心情过年。谁知随着重构的深入,我竟如陷落泥潭一般,苦苦挣扎着过了一个相当沮丧的年,直到三月中旬才真正完成这次重构。这其中到底发生了什么呢?

首先,为了优化代码,我决定把手写的sql代码,尽量用rails的ActiveRecord的方式重写一下(手写的sql代码是过早优化的明显证据),这样可以令代码更好懂,更好改,为后续开发打好基础。看起来毫无疑问,我应该先把model association中的join table升级为join model,前者已经过时,而且我的确需要利用join model添加我需要的额外关系属性。

嗯,终于需要改动数据表的结构了,由于一直用migration来改变数据结构,项目的数据库一直在平滑改进,用户数据没有破化过。但是一些看起来杂乱的migration文件应该重构,应该把它们归置整理,将数据库从头到尾好好整理下–反正还没有正式发布,自己的数据也可以用导出导入功能安全的备份,就这么弄。

对了,要趁这整理数据库的机会,把所有数据表的主键改回系统缺省的整数,用uuid字符串做主键越看越是个错误。

动手。升级到join model看起来还顺利,只是很多测试通不过了。老方式虽然落后,但rails框架准备了较完善的支持代码,使得model间的associations方便易用。而join model虽好,却是个新事物,还在不断的发展中,不得不手工处理一些常见的功能。

经过调查,决定将rails升级到最新版,这样join model就好处理些。

有点中流换马的感觉,测试还没有完全通过,所以不应该签入代码库。但升级到新版rails前,不把已经做好的很多改动保存好,心里也是没底。不管了,签入代码库,反正开发人员就我一个。(后来这样不负责任的签入还有好几次)。

一大堆跟rails1.2.2相关的警告,我的代码中有些过时用法,更正。还有些错误,排查。运行测试,老错误很多还在那里,但升级是完成了,按理说也是要签入吧?唉。

研究剩下的错误,发现那是个大话题,并非我想象中的那么容易。扎到hasmanythrough.com,找答案。发现需要把rails的model好好研究下,可是,年关将至。。。

还有一点时间,把uuid改了吧。一改才发现,这后面藏着的也不是善碴儿,但已经上了贼船,硬着头皮弄吧。还好,过年前这个东西总算解决了。

过年,不爽。还是得过年,休息,远离电脑,不爽。。。年终于过完了!

来吧,我王老五重打旗鼓另开张。可是,大脑用各种理由推诿我的要求,我发现连做到电脑跟前都是件事情了。传说中的“节后综合症”静悄悄地登场了。

强制,没有状态,给自己放假。再强制,还是没状态,再放假。反正soho一族就这点好处了。

总算进入正轨了,咬牙骂娘诅咒,好歹解决了所有问题。可是这么长时间,写一个自己的宠物项目都可以完工了。这就是重构吗?

其实这次重构开始不久我就意识到问题所在了,可是已经走不了回头路,只能硬着头皮坚持下去。大型重构应该是极力避免的,如果不能,也必须将大型重构分解成小型重构,再分解成相互不影响的小块。互不影响的一个简单检验标准是:重构了这个小块,全部原有测试依然通过,毫无遗漏。每次一个重构小块通过了测试,签入代码库,签入是小里程碑的标志。当然了,小里程碑是“小步快跑”思想的捍卫者。

大型重构的问题也是中古年代开发的问题:所有的头绪纠缠在一起,它们互为牵制,为项目的顺利进行设置重重障碍。头绪增多时,大脑处理它们的能力并非是线形下降,而是成指数下降,如果这种情况发生了,项目死亡的风险急剧加大。因为在这种极低的效率下,开发人员的士气受到影响,心智受到摧残,表现为注意力下降,精力分散,拖延任务,推卸责任。

这正是为什么许多项目总是在百分之九十处停滞,并且永不能完成的原因。在前文提到的开发思想的帮助下,这个问题得到了相当程度的解决。我上面犯下的大错,就是对没有很好的遵守正交性和小步快跑原则的惩罚。

让我们来假想一下怎样按照正常的现代开发方法,更好的进行上述重构。如前所述,我的目地是进行一些优化工作。这应该是一系列子任务,将这些子任务写出来,力图使它们看起来相对独立。

考虑到rails是个正在成长的开发框架,应该首先把它升级到最新版,以避免无谓的重复劳动。测试,签入、庆祝。

一项项重构子任务,如主键uuid替换为整数的那个任务。测试,签入、庆祝。

一项项重构子任务,如发现正在处理的问题超出预期的麻烦,则要看情况决定处理方法。这个问题可能可以继续细分成更小的子任务;或者,纯粹是因为问题难解决,这时可以硬攻,也可以暂时绕过,等所有其他重构完成后再集中火力攻克。对大型重构来说,这么做值得鼓励,因为它没有打乱正常的重构步骤,没有破坏可预期性。测试、签入、庆祝。或者测试、恢复原版本、庆祝(看你怎么想了)。

重构过程中一定要避免加入新功能,有时候那是一种诱惑,但必须要抵制。有了好的想法,记下来,继续重构。等重构结束后再来研究那些新功能,新点子。

总之,大型重构是很危险的。如果不能很好的将其细分成正交性强的小块,它将面临老式开发带来的收尾难题。不要害怕琐碎的重构步骤会降低你的工作效率。一个花了一年时间才完成项目的开发者,是没有能够完成项目的开发者效率的 无穷倍

2007年03月16日

我从来都特别羡慕那些效率奇高的人。我总是搞不懂那些家伙是怎样在完成许多伟大事情的同时,还能到处参加社会活动、出书、沉迷于自己的特别喜好。这种人中做的极端的,会令我不自觉地妒火中烧,关于这点,我以后会专门写个帖子讨论。

在过去相当长一段时间内,我的工作效率是比较低的。作为一个开发者,我越来越对微软的那一整套东西心怀怨念。几年前为了改善我的开发效率,我下了很多功夫去研究当时比较流行的一些开发新概念,比如设计模式、面向对象编程的原则、范型编程、测试驱动、敏捷开发等。每一种技术都有其道理,但我后来发现,这些技术都是一些更高阶思想原则的派生物,如果不理解那些基本的思想,就不能很好的应用由它们派生的技术。

在我对此有了模糊的印象时,两本书和一个开发框架适时地出现了:The Pragmatic Programmer, Getting Real和Rails

这两本书的作者带给我了核弹式的冲击,特别是The Pragmatic Programmer,令我这个对unix没有太多概念,几乎完全成长在微软操作系统和开发环境下的程序员倍感惭愧。

软件开发是个很复杂的事情,如果你没有必要的而且正确的指导原则,不同程度的开发失败几乎是必然的事情。人们一直都在搜寻那些“正确”的指导原则,经过了这许多年的持续努力,一些当前最流行的技术思想看起来离“正确”越来越近了,她们中最核心的是:正交性和“小步快跑”。

敏捷开发主要源于“小步快跑”的思想,即快速迭代。敏捷开发和各种现代开发方法都严重依赖的单元测试,则主要源于正交性思想,即完善的隔离开发中的各个模块,确保随着时间的推移,各个模块始终互不影响。重构则源于这两个思想:重构特别重视正交性,她鼓励你在每一步后通过测试确保刚才的改动没有破坏原有的系统;同时重构也极端鼓励小步快跑的方式,以至于人们经常会感觉到她有点书生气,甚至迂腐。但本质上重构是利用每一步都可以掌握的微小重复,换取项目自始至终的可掌控性,而对于稍有规模的项目,可掌控性通常比其他任何东西都重要的多。结对编程鼓励人们小步快跑,同时使用单元测试保证项目不会跑到沟里。

由于这是个很大的话题,一遍小文章不可能涵盖清楚,必要的后续展开是少不了的。下一篇文章将讲述我的一段滴血的重构教训

如果你想更深入的了解正交性的思想,看The Pragmatic Programmer;如果你想更深入的了解小步快跑,看Getting Real;研究rails框架和她的开发哲学,你将加深对这两个原则的理解,并了解到其他一些核心思想,如隐藏在“习惯优于配置”之后的“实用优于优雅”。