重构据说是可以和设计模式相提并论的一项技术,前些天大致浏览完了《重构-改善既有代码的设计》,确实是颇有启发。下面说说我的一点体会和实践吧。
1.重构确实很有用,但是很难用得起来。
为什么很难用起来?因为要用重构,你就必须要写unit test代码。也就是NUnit+一堆测试代码。不然你敢重构写好的代码吗,你怎么证明、确信你的重构没有破坏原来的逻辑?不单要写test,而且你还得写尽量详细、尽量能覆盖所有分支的test。在国内还经常要加班加点做新功能、赶项目进度的环境下,有多少项目经理有这个远见、有这个魄力,花这么多时间让团队来写test?
所以通常我们的做法是,能不改动原来的代码就不改动,更不用说主动要去重构了。你改了代码,很可能会导致测试组的jj们得把整个模块跑一遍,然后下周被所有人暴打一顿。
2.重构很难用,但是还是一定要用,并且一定用得上。
那么怎么用呢,换一个角度来用:把重构的过程提到开发的阶段来做。
A.首先是用重构来提高自己的代码质量。意思就是,《重构》里提到了N多的代码的坏味道,也就是代码烂的各种表现,那我们在写代码的时候首先就是要尽量避免那些bad smells。从这个角度讲,《重构》其实是为我们提供了很不错的一份关于怎么提高代码质量的教材,虽然它是从反面教材来讲的。
B.将重构夹杂在开发阶段来做。其实本来重构界的弟兄们也是提倡这么搞的吧,也就是所谓的“两顶帽子”,一会加新功能,一会重构,小步进行。这个方法很实用,因为这样一方面我们逃避了重构本来必须的test代码的支持(至少后面还有快狠准的测试组jj可以把一下关),另一方面又确实在不断优化整理代码,令思路更清晰。
3.我的一点经验。
下面的说法适用于没有设计文档的开发过程中,通常我还是写了设计文档的。一开始的时候多想一下需求和可能的变化,然后从名词整理抽取出类,大致对每个类的职责有个想法。然后开始写代码。
以测试驱动的思路去写代码。从客户端写起,需要什么方法就添加到被调用的类里去。(而不是反过来,先写好业务逻辑层的所有“可能”的接口,再让客户端调用)。在编写的过程中,不断整理优化代码,如:
i.以小函数来组织逻辑。这个方法太长了,显然可以按照逻辑拆分成更小的函数,也就是Extract Method。这样逻辑更清晰,也更容易重用的机会,另外也更容易使用Move Method/Move Field等重构,有时也可以消除一部分重复的代码。(注:这是很易办到的事,而且效果也非常明显。只要你有这个概念)
ii.改命名。做得差不多后,自己做个代码审查。设想别人现在看你的代码,他能很快领会你的代码意图吗?你为什么要怎么写,你这么写是解决什么问题?如果他要改一个东西,他能很快定位到那一小块代码吗?这时你就会发现如果你把其中的若干方法、属性重新改一个更容易表达你的意图的名字,世界就清静多了。通常方法应该以你的意图来命名,也就是象“写一个简捷的注释一样来命名方法”。稍长一点没关系。仿造use case“动词+名词”的命名方法就挺好。另外你要注意命名的规范,前后必须取得统一。
iii.整理、调整类的设计。
这个方法放在那个类里是不是会更好?你设计这个类的方法的依据是什么?一般我们是先凭直觉经验,然后再用“专家模式”等类设计原则来验证斟酌,同时也要考虑“封装变化”等因数。这时就可能要用到Move Fields,Move Method等重构方法了。这时你就会发现将大方法拆成多个小函数的好处了。
...其他的重构方法因缺乏实践就无法提供更多说明了。不过我想就算只用上面的几个简单的重构,只要能掌握好它,也足以较大提高代码的可维护性了。代码必定可以变得更清晰、精巧、易重构,重用性更好。
最后一点题外话,Martin Fowler的风格我感觉挺有趣的,因为他总能青出于蓝胜于蓝,非常善于总结发挥,化艰深为实用,可以算是一个非常不错的技术传教士。象重构这个技术,本来是之前很多弟兄的心血研究了,但是一直没有人能够象BOB大叔这样大手笔地写出这么系统、实用的书来。
最近发现博客园的弟兄们写一些技术专题时也倾向于BOB大叔的技术写作风格,也就是结合实例,逐步给出方案,逐渐深入的风格,这真是个不错的习惯。基于时间精力我就无法给出例子了,希望以后可以试试。