Refactoring notes 20120105

来源:互联网 发布:dijkstra算法描述 编辑:程序博客网 时间:2024/05/16 05:19

《重构:改善既有代码的设计》阅读随笔

之前很长时间就听说了重构这个词。有一次baidu面试官还问过我,大致回答就是通过现有的代码组织出一个更好的结构,使以后的开发思路更清晰容易。然后问我重写和重构,当时没听说过重写,以为是override,就把c#里面的override说了一遍,后来才发现丢人了。哈哈。



书是师兄推荐给我的,前一阵发现图书馆里面还真有这本书就借来了,好不容易有了时间开始静下来读着一本书。


说实话这本书不用说国内,就是一些经典的国外技术书籍,个人认为它都要高出一个档次,我的依据是,作者是写作风格就如一个朋友和你促膝而谈一样,循序渐进的讨论问题。阅读快感非常不错。一般看技术书,看到一半之后,就感觉很煎熬,但是这本书总给我更多的好奇心去想赶紧知道接下来要说些什么的感觉。


没有上来就讲历史,国内外现状之类的无用之事,第一章用一个小例子来让我明白了,原来我以前也一直在或多或少的用重构,只是不会总结罢了。就像没学设计模式之前,很多人也不自觉的用过工厂模式一样。

不过第一章里面讲述的大多数方法都没记住,只是明白了大概思路。而且需要注意的是,重构的代价需要你去衡量。比如项目的尾声是不适合进行重构的。成本使然。

第二章说了什么是重构。说白了就是不改变软件既有功能和行为的前提下,调整其内部的结构,从而降低以后的修改成本。这跟性能优化也有所不同,性能优化也是让行为和功能不变,但是会对代码进行一些之后我们无法理解的改变,我感觉从一种角度来看是和重构截然相反的事物。重构是让结构清晰,使人容易理解。而优化则是将代码变得不那么容易理解,却使得机器更快的运行。

再就是两顶帽子互换的想法,让我印象很深刻,一顶是重构,一顶是添加功能(也就是修改行为吧?)。重构时不能添加新功能,把结构捋顺,也不用添加任何不必要的测试。然后结构清晰之后,就可以更好的添加新的行为和功能。待到添加完之后,结构有可能变得又不清晰了,那么再进行重构。如此反复进行。可能开始还会感觉重构是不必要的。但是书里说,代码的混乱是慢慢积累的。例如当添加到100个功能之后,如果之前没有重构,可能会面目全非。但是两顶帽子互相交替,那么即使100个功能行为,也会变得游刃有余,因为结构清晰。(个人也感觉有时候当类增加到十个之后,就有想吐的感觉了,估计是因为我结构不清晰的原因。)  



重构的好处是使得软件的结构更加容易理解,而且容易找到bug。提高编程速度。这几点很好理解。说白了就是防止以后规模逐渐变大之后代码会变得腐败的一种方法。

第三章的一个概念就是“代码的坏味道”(bad smell?),包括:

代码重复(如两个函数有一个相同的表达式,可以extract method 然后两个函数分别调用这个mehod)

函数过长(如果一个函数过长,就要考虑分解成几个小的方法,然后按顺序调用,还有其他的方法如replace temp with query和introduce parameter Object等,现在没读到,以后再说o(∩_∩)o ,怎么寻找需要提炼的代码呢?书中给了一个方法就是,如果一段代码前有注释,就表明需要提炼了,再给一个恰当的命名。switch和循环也是经常需要提炼出来的部分)

类太大(一个类应该专注做一件事情)

参数过长(参数太长了 读代码不容易理解,注意力经常在分辨参数的用途上,干扰了对函数执行动作的注意力。可运用replace parameter with  method等)

switch

只有数据而没有其他行为的数据容器类

注释太多

以上这几种情况 还有其他几种(暂时不明白,也就没写),是经常遇到的需要重构的情景,额。。。我想还是凭经验吧。


第四章是个人问题,没搞过java  也不知道JUnit,没怎么读。不过其中的“类中有自己的测试代码”这种思想让我开了眼界了,自己土鳖吧,呵呵。



重要的一点是:对于重构的这些方法,我们应该衡量是否真的需要那么去做,有些时候的代价是巨大的,但是带来的好处却不多,比如一个很短的函数,我们一眼就可以看清楚他的用途,就没有必要去对他进行重构。书里面一些简单的例子有些确实不适合费那么大力气重构,但是作者是为了展示重构的方法,所以没有苛求。

第6章  重新组织函数 

Extract Method:看到一段代码做的是一类工作,比如打印输出,那么可以提取出来,通过参数进行调用。 另外给提取出来的函数命名一个贴切的名字是一个好习惯。如打印输出可以命名为print_output等。见P110.需要注意局部变量的问题,包括无局部变量,有局部变量但不改变和对局部变量改变这三种情况。重点在对局部变量的修改上,如果只在提炼的函数内部使用,则可以尽情修改,但是如果在新函数的外部还要使用这个局部变量,则需要在提炼的函数结尾将修改过的局部变量返回。


Inline Method:当函数的内部的语句和这个函数的名字一样清楚的时候,多一个函数调用显然是坑爹的行为,可以简化之。间接性可能会对层次的理解有帮助,但是有些是不必要的间接层次,就需要简化。另外一个用处就是当函数分配的不合理的时候,可以把一大堆函数全都内联到一个大函数中,然后进行重新的分配。变成一个更合理的函数(先inline再extract method)。需要注意的是:如果子类继承了这个函数,那就别改变他,因为子类无法复写一个不存在的函数。

Inline Temp:当有temp=foo( );return temp;的时候,直接写成return foo( ); 这个方法一般配合别的方法如replace temp with query使用。一种单独使用的情况就是例子中的函数返回值,这种方法没什么危害。但是如果这种方法有时候妨碍其他重构工作,就给它inline掉。

Replace Temp with Query:将一个表达式提炼到一个新的函数中,在所有对表达式调用的地方换成新提炼函数。(新函数相当于是一个query动作,因为temp只被赋值一次,所以直接调用query的结果是一样的。感觉很像extract method啊~~)在进行extract method之前,局部变量尝尝难以被提炼,用query替换temp就可以在提炼的method中调用query了。


Split Temp Var:一个临时变量只应该代表一个意义,否则会让读者出现疑惑。应将有疑惑的temp分解成两个具体意义的变量。

Replace Method with Method Object:用函数对象取代函数。当一个大型的函数里面局部变量的使用使得你没办法Extract Method时,可以将这个函数放在一个单独的对象中,这样这些局部函数就成为对象内的字段,对象内部的函数都可以调用它们。然后就可以用Extract Method将这个大函数分解为几个小的函数的组合了。需要注意的是,新的函数对象需要维护一个源对象的引用。然后通过源对象去调用里面的函数。这样就可以轻松的对新对象里面的函数进行extract method了。

第七章 在对象之间搬移特性

Move Method:如果一个类中的某一个函数与另一个类进行的交流要多余和本类的交流,那么在另外一个类中建立一个类似行为的新函数。将原有的那个函数只是简单的当成一个中间函数。或者可以直接移除。注意在继承体系中移除需要小心。

Move Field:同Move Method一样,如果一个字段被另一个对象调用的频率多余被本类对象调用的频率高很多,那么就考虑将字段移动到另一个类中,并修改所有的原来的字段的用户。

Extract Class:某个类做了本应该由两个类做的事情(说白了就是单一指责吧),可以建立一个新类,将字段和函数搬移到新类之中(通过Move field和Move Method)。在旧类中维护一个新类的实例(在不确定新类是否需要旧类实例之前,不要在新类中添加旧类的引用)。最后决定是否公开新类。

Inline Class:和Extract Class目的正好相反。一个类没有必要承担一个责任的时候,就将它移动到另一个类中合并为一个类。注意:其他地方是否有引用源类的地方,需要修改为引用目标类。

Hide Delegate:委托大概意思是指客户端通过服务端的方法,间接的去调用其他对象实际的行为,万一委托关系发生变化,客户端也就需要随着发生变化。若将服务端放置一个简单的委托函数,将这个委托关系隐藏起来,那么客户端只需要知道这个委托函数即可,当将来委托关系发生变化的时候,客户端不需要改变,只针对服务端进行调整即可。


Remove Middle Man:和Hide Delegate的作用相反,修改方式也类似于一个逆过程。(你可以选择保留还是删除原来的那个委托。)


[未完待续]

原创粉丝点击