序言:
断断续续又是一周,想想为什么写这些笔记,其实也是自己偷懒的表现。懒得过来一个月后再去翻这本书发现又是一本新书重头看到尾,偷懒到随时翻看笔记就可以回忆起某些重点就足以;另外一个原因也是为了激励自己,每每看完一本书总得留下点什么,觉得有用或者无用,也是对自己的一个监督。很多时候,通往成功的路,往往在0到1之间放弃了坚持,只有跨越了01坎,往往就可以发现新大陆。
《重构-改善既有代码的设计》
一眼看到这本书的书的时候,给我的感觉就是:重构,一定是非常大的一个项目,或者至少是代码量很多的工程才会用到重构这些项技术,很多时候是不需要的。当自己实际深入的钻入书本,体会作者的良苦用心时,才枉然大悟。原来重构镶嵌在我们开发工程中的每个空隙,一不小心我们就涉及到。所以书中的对重构的定义是:重构是不改变软件可观察行为的前提下改善其内部结构。另外的一种说法是:对软件内部结构的一种调整,目的是不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
因为书中是以java为例子,大部分偏向于面向对象。我对c++、Java也是略微的了解,对python更加熟悉,所以书中提及类的封装,接口的优化与开发的概念还是可以理解到作者的良苦用心。其实对于我现在常用到的方法做个总结,目前的工作还基本上是以C为主。
其实在重构之前,为什么要重构的问题,书中也明确的提及,比如第三章-代码的坏味道。标题就总结的很好,比如说:重复代码、过长函数、过大的类、过长的参数、发散式变化、散弹式变化、依恋情节、数据泥潭、基本类型偏执、Switch惊悚现身、平行继承体系、冗赘类、夸夸其谈未来性、令人迷惑的暂时字段、过度耦合的消息链、中间人、狎昵关系、异曲同工的类、不完美的库类、纯稚的数据类、被拒绝的遗赠、过多的注释。仅仅是这些标题,马上就可以结合实际工作中遇到的的这些编码雷区。只有知道什么是雷区,才能在实际的编程中避免。有一位有经验的工程师曾说过:新手程序员为有经验的程序员对比,为什么有经验的程序员可以编写稳定的框架,不是因为没有bug,而是在实际开发中,有经验的程序员能很明确知道哪些是坑,然后跳过或者费很大劲去绕过;但是新手程序员可能就会扎根进去,结果却不得而知。所以在实际的开发中,知道哪些是雷区,哪些是坑,事先绕过,才能减少抓虫子的时间。
但是如果已经入雷区入坑了怎么办?也许重构是一种方法,但是对于一个不是很了解程序的老板来说,很多只是关心结果,不可能说我不实现新功能了,我要花一个星期去重构代码。如果一个系统真正的遇到瓶颈了,真的需要重构,到那种地步的话,就需要顾问已经专业的有经验的带头人去进行重构整个系统,就好比一个sdk的升级板。在上一东家的工作中,很庆幸我能参与SDK平台开发。在开发之前,就一个旧版本的SDK,但是为了添加新功能,方便二次开发等因素,但是核心功能不变的原则,使用python开发。虽然接口已经变,但是方向是明确的,以至于我在职的时候,还进行了下一版本的开发,虽然还没有上线,我就被安排到测试组兼任leader,也是之前博文所写的。回归正题,就因为之前没有沉淀,现在看到这些熟悉的方法,就是曾相识,所以,如何进行重构,我就书中提及的方法中,总结一些我将来工作会用到的方法:
-
Extract Method(提炼函数)
这个方法几乎都适用,目标很明确。每个函数应该有它单一的功能,不要杂糅着各种各样的功能,相互依赖的变量关系在里面。
-
Replace Temp With Query(以查询取代临时变量)
我们都希望写出一个简洁的代码,并且不却功能的函数。那么变量会在函数中经常发现。这个方法的前提是,如果该函数中为了存储调用函数的返回值,并且只是为了做个安全的判断,那么这样的情况就可以省略掉该变量,而直接使用查询函数代替变量;但是如果该变量被使用的次数为2次以上,那么就可以暂时的预留吧。
-
Remove Assignments to Parameters(移除对参数的赋值)
对参数的赋值是比较危险的一个操作,如果传入的是个指针,如果在函数中将这个地址用作其他临时变量赋值,那么这就入坑了。所以我们需要引入一个局部变量来存这个参数。也就是说:在按值传递的情况下,对参数的任务修改,都不会对调用造成任何影响。
-
Rename Method(函数改名)
函数的名称应该准确表达它的用途。给函数命名的一个好方法:首先考虑应该给这个函数写上一个怎样的注释,然后想办法将注释变成函数名称。
我们常常无法第一时间就给函数起一个很好的名称。这时候我们就可能会这样将就着,但是,这是通向混乱之路。这在前面的书籍《程序员修炼之道》中也有提及过。如果看到一个函数名称不能很好的表达它的用途,应该马上加以修改。记住,我们的代码是为人写的,其次才是为计算机写的。
-
Remove Parameter(移除参数)
程序员可能经常添加参数,却常常不愿意去掉它们。他们打的如意算盘是:无论如何,多余的参数不会引起问题,而且以后还可能用上它,并且如果修改可能还会有意外的虫子进来。
这也是通往混乱之路。参数代表着函数所需的信息,不同的参数值有着不同的意义。函数调用者必须为每一个参数操心该传什么东西进去。如果不去掉多余的参数,就是让你的每一个用户或者代码维护者多费一份心。是很不划算,更何况“去除参数”是非常简单的的一项重构,只要保证测试的完整与准确性,就可放心的做。
-
Separate Query from Modifier(将查询函数与修改函数分离)
也就是说,某个函数即返回对象值,也修改了状态,这样的函数就需要去重构,就应该将两个功能函数分开实现。
-
Parameterize Method(令函数携带参数)
若干函数做了类似的工作,但是函数本体中却包含了不同的值。这样的情况下,我们就可以考虑使用这样的方法,其实就是消除重复代码。
我只是大概总结了一些常用的方法,有些很简单,但是却很实用。其实在阅读的过程中或者在看标题的时候,会发现,咦,有些说法是不是有些矛盾了,这里说需要去除参数,那里说需要添加参数。当我完全看完的时候,才理解其中的奥秘。重构,不是我们所想的那么简单,也不是我们不能高攀的那么困难。仅仅知道这些方法是不够的,也不是强拖拽的一定将某个方法用上或者把全部方法用个遍,其实不是。我们知道这些方法,知道哪些是代码的坏味道,才会在实际的编程中刻意的规避它,不让自己入坑。虽然都说编程就是不断挖坑不断填坑的过程,自己挖的还好,如果是别人挖的,那可真的有可能深不见底。所以,你入或不入,坑就在那里,不来不去;而我们手持秘籍,遇坑则避,乃智人也!
-
测试
所以,最后,再谈谈测试。在阅读了前面的几本书后,比如《软件测试的艺术》、《程序员的思维修炼》、《程序员的修炼之道》都会提及测试,可见测试在整个系统中乃至开发过程中的地位之重。就重构而言,为什么使用测试,为什么要保证测试。书中的理由也是让人很信服的。以下是书中原话,还是很想将作者的心声记录下来,因为他说的就是我们的心声:
如果认真观察程序员把最多的时间耗在哪里,你就会发现,编写代码其实只是占非常小的一部分。有些时间用来决定下一步干什么,另一些时间花在设计上,最多时间则是用来调试。我敢肯定每一位读者都还记得自己花在调试上的数个小时无数次的通宵达旦。修复错误通常是比较快的,但是错误却是一场噩梦。当你修好一个错误,总是会有另一个错误出现,而且肯定要很久后才会注意到它。那是你又花上大把时间去寻找它。
“自测试代码”--类应该包含它们自己的测试代码。作者是这样理解的:每个类都该有一个测试函数,并以它来测试自己这个类。
所以作者就开始在自己开发的代码中添加测试函数,并且可以运行。后面发现更加有趣的事情,就是这样测试函数可以单独运行,再配合一些脚本及控制台,就可以做出自动化测试效果--确保所有测试都完全自动化,让它们检查自己的测试结果。
当然,说服别人也这样做并不容易。编写测试程序,意味着要写很多额外的代码。除非你确切体会到这种方法对编程速度的提升,否则自我测试就显不出它的意义。很多人根本没学过如何编写测试程序,甚至根本没有考虑过测试,对于编写自我测试代码也很不利。如果需要手动运行测试,那更是令人烦闷;但如果可以自动运行,编写测试代码就真的很有趣。
其实现在想要编写测试代码,很多语言就有第三方库的支持。具体原理都差不多,模拟对象,模拟变量,预测结果。在《软件测试的艺术中有详细的讲解》。
那么,我们为什么在说重构的时候要使用测试,而且还是比较完善的测试体系。用途就在于:如果在重构过程中,自认为已经重构完成,但是如何去检验功能是否还在,那么这时候就需要在重构过程中经常运行测试,只要有一项测试不通过,那么就是说您的重构的函数可能缺胳膊短腿了,需要注意了,需要去修复它。只有这样步步为营,才能将重构安全的进行下去,否则就是灾难性的毁灭,那么经理绝对不会开心的。