从命令行操作的实践来谈重构的修炼
阅读提示:重构是一个“永恒的话题”,只要开发在持续,那么重构就会一直伴随着我们。但是,“重构”本身并不是一个很容易做到的事情。
这半年多来,各大技术站点都在热炒SOA、AJAX、敏捷,却鲜有文章谈论“重构”。事实上,目前国内软件行业的还是非常匮乏经验丰富的构架师和高级研发人员,而“重构”则是这两类技术人员必须修炼的基本功课。
但是,在国内搞开发,能够实践“重构”的机会并不是很多。很多人扎根进项目繁琐的需求变更中,而无暇顾及“回头整理整理思绪,整理整理代码”。特别是在“平台泛滥”的项目实施中,大量的实施人员都在考虑“如何在现有的平台和框架下实现功能”,很少考虑“代码结构是否有改善的空间”。
俺这几年大部分时间都是跟产品研发打交道,所以幸运的属于那一小部分“比较容易有机会实践重构”的人群。当然,比起在TW公司的那些哥们来说,比他们还也有些不幸,毕竟能够向TW那样“非常崇尚敏捷”的公司不多。
比上不足,比下有余,自然有一番自己的体会。前段时间又玩了一把“重构”,是重构一个命令行处理类的,把一些心得说一说。
单纯的修改代码与重构是不一样的
单纯的修改代码,与重构是有很大差别的。可能你对某个Component并不很熟悉,但是通过Debug和Track,还是能够比较容易的“Edit Code”或者“Fix Bug”。但是这跟重构的差距还是很远。
举个例子:来到CDC这边快一年了,经历了两个“maintenance version”的开发和目前正在进行的“major version”的研发。之前的两个“maintenance version”只是Fix Bug,而且并没有多少参考资料,基本上只能依靠自己的Debug和Track手段来分析、跟踪、定位Bug,然后修正它。那段时间我们都在嘲弄自己“阅读代码基本靠猜,修改代码基本靠蒙”。可想而知,那时候,在旧有的代码中定位Bug和修改Bug尚且是一个非常困难的事情,根本没有“重构”的可能性。——但是我们还是很成功的修改很多Bug,这也意味着修改了很多代码。对我们来说,唯一的收获就是对旧有的代码结构有所了解了,间接的吸收了国外那些senior architecture的设计思想。
但是,在进行两个“maintenance version”的开发的过程中,我们没有任何的“重构”行为。
重构的基本前提:理解原有的设计意图
重构的基本前提就是你必须对“旧有的代码结构比较熟悉”。当然,如果能够了解最初的设计思路,就更容易去重构现有的代码结构了。但很多情况下,我们只能通过阅读代码来“推测”原作者的设计意图。
比如最近正在重构的一个命令行类。这个命令行类用于维护Application的Upload、Deploy等等十几个复杂的操作。
大致的类方法结构如下(真实的类比这更为复杂):
(主函数) | main(String[]) |
(处理任务函数) | task(String[]) |
(执行任务) | :.action() |
(执行某个任务) | :.deploy(Application, XiNode) |
(执行某个任务) | ········ |
(退出) | :.exit(String, int, int) |
(初始化) | :.init() |
(解析参数) | :.parse(String[]) |
(打印帮助) | :.usage() |
(打印某个任务帮助) | :.usageDeploy() |
(打印某个任务帮助) | ········ |
(验证输入参数) | :.validate() |
很明显,这个旧有的代码类带有这非常明显的“面向过程”的思维模式,当然,这是很多“命令行”类的通病。在最初构建命令行类的时候,任务比较少,代码行数和方法也就比较少,复杂的设计可能反而影响代码的构建效率,这样“流水式”的设计可能更容易实现和维护。
为了更容易说明问题,画了一张简易的“思维图”来诠释原始作者的设计意图(这个设计意图仅是依据阅读代码而分析出来)。
如果你能够理解旧有代码中作者的基本设计意图,那么你就基本上可以开发重构了。当然,可能会存在某些细小的地方,在你阅读代码的时候遗漏掉(毕竟没有完整的设计文档),但是这对“重构”的影响不是很大,把握注关键的一些设计意图,其他的可以在重构过程中慢慢体会和学习。