您的位置:首页 > 编程语言

重构.改善既有代码的设计

2015-01-02 18:40 232 查看
1.首先为《重构-改善既有代码的设计》这本书做个广告
《重构-改善既有代码的设计》这是一部经典之作,相信很多人都听过或看过,本书清晰地揭示了重构的过程,解释了重构的原理和最佳实践方式,并给出了何时以及何地应该开始挖掘代码以求改善。书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。本书提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。

读完《重构-改善既有代码的设计》,有了些许感想,先与大家分享一下。当我们已经对设计模式倒背如流时,却往往发现在实际代码编写中有生搬硬套的感觉。设计模式是前人经验的总结,直接拿来用合不合适呢?这让我想起了大学一位老师告诉我们的一条学习的道路“知识,理论,智慧”。设计模式是很一种优雅的“智慧”,但对于我们初学者来说还仅仅是留存于文字的“知识”。把“知识”融合到自己的开发中,在不断探索和总结中形成自己“理论”,再应用到实际中,那么这才是是真正属于我们自己的“智慧”。重构恰恰是由“知识”到“理论”的必经之路,而书中的各种重构方法无疑是这条路上清晰的路标。

“代码的坏味道”大家一看都不会陌生,绝对是在我们的编程中如影随形的,现在把相应的重构方法和设计模式总结出来,方便自己参考查阅,也希望对大家有所帮助。

友情提示:下面所列出的不是公式,不是别的重构方法不能用,也不是非要重构到相应的设计模式,因为不论是重构还是应用设计模式,一切的目的都是为了软件构架的“优雅”,而不是炫耀技术。另外,两位作者在描述重构步骤的时候,都不断重复着“编译并通过测试”的步骤,这无疑是在强调测试的重要性,和重构的递进性,切不可一措而蹴。

2.从几个列子来让大家对重构有个更深刻的认识

重构,绝对是写程序过程中最重要的事之一。在写程序之前我们不可能事先了解所有的需求,设计肯定会有考虑不周的地方,而且随着项目需求的修改,也有可能原来的设计已经被改得面目全非了。更何况,我们很少有机会从头到尾完成一个项目,基本上都是接手别人的代码,即使这个项目是从头参与的,也有可能接手其他组员的代码。我们都有过这样的经验,看到别人的代码时感觉就像屎一样,有一种强烈的想重写的冲动,但一定要压制住这种冲动,你完全重写,可能比原来的好一点,但浪费时间不说,还有可能引入原来不存在的Bug,而且,你不一定比原来设计得好,也许原来的设计考虑到了一些你没考虑到的情况。我们写的代码,终有一天也会被别人接手,很可能到时别人会有和我们现在一样的冲动。所以,我们要做的是重构,从小范围的重构开始。

重构不只可以改善既有的设计,还可以帮助我们理解原来很难理解的流程。比如一个复杂的条件表达式,我们可能需要很久才能看明白这个表达式的作用,还可能看了好久终于看明白了,过了没多长时间又忘了,现在还要从头看,如果我们把这个表达式运用Extract Method抽象出来,并起一个易于理解的名字,如果函数名字起得好,下次当我们再看到这段代码时,不用看逻辑我们就知道这个函数是做什么的。如果对这个函数内所有难于理解的地方我们做了适当的重构,把每个细小的逻辑抽象成一个小函数并起一个容易理解的名字,当我们看代码时就有可能像看注释一样,不用再像以前一样通过看代码的实现来猜测这段代码到底是做什么的,好的代码胜过注释,毕竟注释还是有可能更新不及时的。

《重构,改善既有代码的设计》看这本书时会发现,书中讲的都是一些很简单的东西,而且很多东西就是我们平时在做的,只是作者把它们总结了起来。比如说Rename Field,就是对不易理解其作用的字段起一个易于理解的名字,这个肯定我们都做过,但是更多时候,我们是对这种字段视而不见而已,比如曾经花了很久没搞明白代码的字段"IP"是什么的缩写,最后发现竟然是“INPUT”。看过这本书的收获是,让重构融于整个写代码的过程中,让重构不再作为一项独立的任务,花费大量的时间,而是在写代码的过程中随时随地的进行,一个函数不容易理解,重构;添加新功能时很不方便,重构;



注意:

1.重构的误区,比如说看到switch就重构,代码超过50行就必须修改等。当然,不是说这些重构手法无效,而是这些手法提醒我们,当看到这样的情况时需要认真考虑一下,当前的情况是否需要重构,如果确定不需要,就不要改了。

2.测试机制和小步重构。重构前,先检查自己是否有一套可靠的测试机制。这些测试必须有自我检验的能力,毕竟重构可能破坏掉一些东西,我们要靠测试帮助我们发现这些问题,不要因为测试无法捕捉所有bug就不写测试, 因为测试的确可以捕捉到大多数bug。然后就是重构一定要一小步一小步的重构,确保没一小步不留下bug,测试通过再继续进行,否则你会功亏一篑。

3.书中全是一些很简单的手法,我相信,我们肯定都用过其中的大部分重构手法,只是没有察觉。这本书只是对其进行了一个总结,并让我们意识到重构这项技能,并让重构融入我们整个写程序的过程中,让重构无处不在。

重构



备注:

代码的坏味道一般重构方法使用模式重构
重复代码提炼方法

提取类

方法上移

替换算法

链构造方法
构造Template Method

以Composite取代一/多之分

引入Null Object

用Adapter统一接口

用Fatory Method引入多态创建
过长方法提取方法

组合方法

以查询取代临时变量

引入参数对象

保持对象完整
转移聚集操作到Vistor

以Strategy取代条件逻辑

以Command取代条件调度程序

转移聚集操作到Collecting Parameter
过长参数列以方法取代参数

引入参数对象

保持对象完整
 
条件逻辑过度复杂分解条件式

合并条件式

合并重复的条件片段

移除控制标记

以卫语句取代嵌套条件式

以多态取代条件式

引入断言
以Strategy取代条件逻辑

转移装饰功能到Decorator

以State取代状态改变条件语句

引入Null Object
分支语句提取方法

转移方法

以子类取代类型代码

以多态取代条件式

已明确方法取代参数
以State/Strategy取代类型代码

引入Null Object

以Command替换条件调度程序

转移聚集操作到Visitor
基本类型迷恋
程序代码过于依赖基本类型(int,string,double,array等低层次语言要素)
以对象取代数据值

以类型取代类型代码

以子类取代类型代码

提取类

引入参数对象

以对象取代数组
以State取代状态改变条件语句

以Strategy取代条件逻辑

以Composite取代隐含树

以Interpreter取代隐式语言

转移装饰功能到Decorator

用Builder封装Composite
数据泥团

在类的字段和参数列中,总是一起出现的数据
提取类

引入参数对象

保持对象完整
 
令人迷惑的临时字段提取类引入Null Object
组合爆炸
许多段代码使用不同种类或数量的数据或对象做同样的事情(例如使用特定条件和数据库查询)
 以Interpreter取代隐式语言
过大类提取类

提取子类

提取接口

复制被监视数据
以Command取代条件调度程序

以State取代状态改变条件语句

以Interpreter取代隐式语言
冗赘类

不再做很多工作或没有用的类
折叠继承关系

内联Singleton
 
不恰当的暴露
在客户代码中不应看到类的字段和方法,却是公开可见的
封装字段

封装群集

移除设置方法

隐藏方法
用Factory封装类
发散式变化

类经常因为不同的原因在不同方向上发生变化,显然是违反了单一职责原则
提取类 
霰弹式修改

如果遇到变化,必须在不同的类中作出相应的修改
转移方法

转移字段

内联类
将创建知识搬移到Factory
依恋情结
方法对于某个类的兴趣高过对自己所处的宿主类
转移方法

提取方法
引入Strategy

引入Visitor
平行继承体系
当为一个类增加一个子类时,也必须在另一个类中增加一个相应的子类
转移方法

转移字段
 
夸夸其谈未来性折叠继承关系

内联类

移除参数

移除方法
 
过度耦合的消息连
不断的向一个对象索求另一个对象
隐藏委托

提取方法

转移方法
使用抽象引入Chain Of Responsibility
中间转手人
类接口中有很多方法都委托给其他类
移除中间转手人

内联方法

以继承取代委托
 
狎昵关系
类之间彼此依赖于其private成员
转移方法

将双向关联改为单向

提取类

隐藏委托

以继承取代委托
 
异曲同工的类重命名方法

转移方法

提取超类
用Adapter统一接口
不完善的程序库类引入外加方法

引入本地扩展
用Adapter统一接口

用Facade封装类
纯稚的数据类
只拥有字段的数据类
封装字段

封装集合

移除设置方法

转移方法

隐藏方法
 
被拒绝的遗赠
继承父类时,子类想要选择继承的成员
以委托取代继承 
过多的注释
为糟糕的代码写大量的注释
使用一起重构方法,使方法本身达到自说明的效果,让注释显得多余 
怪异解决方案
在同一系统中使用不同的方式解决同一问题
替换算法用Adapter统一接口
总结

本书提供的重构是一种非常好的思想,对于大型的软件,轻易不敢去修改大框架。重构的思想给我们总结了非常多的经验,一小步一小步的重构,确保没一小步不留下bug,这个很重要。这本书写得非常的细腻而且深入,受益良多。下面粗略地概括一下对重构的理解,也整理一下之前很肤浅的概念。 

《重构》有一个很好的动机

也可以说是价值观,就是程序第一是写给人看的,而不是写给机器看的。 

根据这一价值观,其他多种利益纷至沓来,比如当程序有了良好的可读性和可理解性,程序中隐藏的Bug便很容易浮出水面,开发进度也更加顺畅,并且对于系统将来的结构变更和扩展,程序也更加具有灵活性。 

重构和优化

在之前的开发中,优化的意识要比现在(看完《重构》之后)强的多,如果遇到在一个循环中可以做多个事情的时候,决定把每件事情分开放到单独的循环中是要鼓起很大的勇气的,而现在便可以轻松的决定,因为清晰的代码在需要性能优化时有更宽的道路可以选择,并且往往这种决定不会造成真正的性能影响。

《重构》与《设计模式》的关系

在《设计模式》和《重构》中都有提出“设计模式为重构提供了目标”,在之前对这句话的理解总是朦朦胧胧,觉得有道理但又不是很深刻,现在觉得有两个词非常的关键:目标和目的。 设计模式为重构提供了目标,但不是目的。 

设计模式是经过证实的在一定场景下解决一般设计问题的解决方案的核心,通过设计模式我们很好得解决了某种问题,并且便于我们思考和交流,降低沟通之间的理解误差,此外同样重要的,设计模式增强了可复用性,便于将来扩展和维护。 

而重构是对程序内部结构的一种调整,其目的是在不改变“软件之可察行为”的前提下,提高其可理解性,降低其修改成本。 

所以如果我们把软件开发比作在大海中航行,设计模式就是遍布在大海中的航标,他可以引导我们驶向目的地——高可读性、可理解性、可扩展性、可维护性。所以设计模式是重构的目标(航标)而不是目的,设计模式可以帮助我们更好更快的抵达目的地(准确地说是无止境的),而避免触礁或偏离航向。

理论的知识必需少不了实践,否则对其领悟很肤浅。通过实践的磨炼,经验的积累,达到对设计理念的心领神会,对设计决策的信手拈来,以无招应有招,这才能更好的达到融会贯通。
工作中如何运用重构

当我们已经了解了重构的基础和分类,还实践了所有这些重构。同时,你已经很擅长测试了,所以你不再畏首畏尾。 于是你可能想:“我已经知道如何重构了。”不,还没有。前面列出的重构技术仅仅是一个起点,是你登堂入室之前的大门。如果没有这些技术,你根本无法对运行中的程序进行任何设计上的改动。有了这些技术,你仍然做不到,但起码可以开始尝试了。

为什么说它们仅仅是个开始?答案很简单:因为你还不知道何时应该使用它们、何时不应该使用、何时开始、何时停止、何时前进等待。使重构能够成功的,不是前面各自独立的技术,而是这种节奏。

当你真正懂得这一切时,你又是怎么知道的呢?当你开始冷静下来,你就会知道,自己已经真正掌握了。那时候你将有绝对的自信:不论别人留下的代码多么杂乱无章,你都可以将它变好,好到足以进行后续的开发。

不过,大多数时候,掌握的标志是:你可以自信地停止重构。在重构者的整场表演中,一开始你为自己选择一个大目标,例如“去掉一 堆不必要的子类”。然后你开始向着这个目标前进,每一步都走得小而坚定,每一步都有备份,保证能够回头。最终大功告成。

就在此时,意想不到的事情发生了:你再也无法前进一步。也许是因为时间太晚, 你太疲倦;也许是因为一开始你的判断就出错,实际上不可能去掉所有子类。也许是因为没有足够的测试来支持你。总而言之,你无法再自信满满地跨出下一步。你认为自己应该没把任何东西搞乱,但也无法确定。这是该停下来的时候了。如果代码已经比重构之前好,那么就把它集成到系统中,发布你的成果。如果代码并没有变好,就果断放弃这些无用的工作,回到起始点。然后,为自己学到一课而高兴,为这次重构没能成功而抱憾。那么,明天怎么办?

明天灵感总会来的。为了等待进行一项重构的后一半所需的灵感,你可能需要等上几个月。你可能会明白自己错在哪里, 也可能明白自己对在哪里,总之都能使你想清楚下一个步骤如何进行。然后,你就可以像最初一样自信地跨出这一步。也许你羞愧地想:“我太笨了,竟然这么久都没想到这一步。”大可不必,每个人都是这样的。这就是我要说的该前进的时候前进该休息的时候休息。

从感觉上来说,的确如此,因为这 是一种全新的编程方式。当你真正理解重构之后,系统的整个设计对你来说,就像源码文件中的字符那样可以随心所欲地操控。你可以直接感受到整个设计,可以清楚看到如何将设计变得更灵活,也可以看到如何修改它:这里修改一点,于是这样表现;那里修改一点,于是那样表现。

但是,从另一个角度来说,重构是一种可以学习的技术,你可以从本书读得并学习它的各个组成。然后,只要把这些技术集成在一起并使之完善,就可以从一个全新角度看待软件开发。

正如我所说,这是一种可以学习的技术。那么,应该如何学习呢?

1.随时挑一个目标。某个地方的代码开始发臭了,你就应该将问题解决掉。你应该朝目标前进,达成目标后就停止。你之所以重构,不是为了重构而重构,而是为了让你的系统更容易被人理解,为了防止程序变得散乱。

2.没把握就停下来。朝目标前进的过程中,可能会有这样的时候:你无法证明自己所做的一切能够保持程序原本的语义。此时你就应该停下来。如果代码已经改善了一些,就发布你的成果;如果没有,就撤销所有修改。

3.学习原路返回。重构的原则不好学,而且很容易遗失准头。就连我自己,也 经常忘记这些原则。我有时会连续做两、三项甚至四项重构,而没有每次执行测试用例(test cases)。当然那是因为我完全相信,即使没有测试的帮助,我也不会出 错。于是我就放手干了。突然,不幸的事情发生了,某个测试失败,我却无法找到究竟哪一次修改造成了这个问题。 

这时候你一定很愿意就地调试,试图从麻烦中脱身。毕竟,不管怎么说,一开始所有测试都能够正常运行,现在要让它们再次正常运行,会困难到哪里去?可是,你的重构己经失控了,如果继续向前走,你根本不可能知道如何夺回控制权。你应该回到最近一个没有出错的状态,然后逐一重复刚才做过的重构项,每次重构之后一定要运行所有测试。

当你出错的时候,使系统极大简化的一个方案也许已经近在咫尺,这时候要你停下来回到起点,是件最痛苦的事情。但是,现在,趁你头脑还清楚的时候,请想一想:如果你第一次重构用了一小时,重复它只需十分钟就够了,所以如果你退回原点,十分钟之内一定能够再次达到现在的进度。但如果你继续前进,调试所需时间也许是五秒种,也许是两小时。

当然,站着说话不腰疼,实际做起来困难得多。我个人曾经因为没有遵循这条建议,花了四个小时进行三次尝试。我失控、放弃、慢慢前进、再次失控、再重复……真是痛苦的四个小时。这不是有趣的事,所以你需要帮助。

4.二重奏。和别人一起重构,可以收到更好的效果。两人结伴,对于任何一种软件开发都有很多好处,对于重构也不例外。重构时,小心谨慎按部就班的态度是有好处的。如果两人结伴,你的搭档能够帮助你一步一步前进,你也能够帮助你的搭档。重构时,时刻留意远景目标是有好处的。如果两人结伴,你的搭档可能看到你没看到的东西,能想到你没想到的事情。重构时,明智结束是有好处的。如果你的搭档不知道你在干什么,那就意味你肯定也不知道自己在干什么,此时你就应该结束重构。最重要的是,重构时,拥有绝对自信是绝对有好处的。如果两人结伴,你的搭档能够给你温柔的鼓励,让你不致于灰心丧气。

与搭档协同工作的另一方面就是交谈。你必须讲出你所想做的事,这样你们两个才能朝着同一个方向努力。你得把你正在做的事情讲出来,这样你的搭档才有可能指出你的错误。你得把刚才做过的事情讲出来,这样下次遇到同样情况时你才能做得更好。所有这些交谈都有助于你更清楚了解如何让个别的重构项适应整个重构节奏。

以上就是学习的方法。现在即使你已经在你的重构目标(代码〕中工作了好几年,一丝一缕了然于胸,但只要发现其中不好的代码,以及消除它们的重构手法,你就有可能看到程序的另一种可能。你也许会想立刻挽起袖子,把你看到的所有问题都解决掉。不要这么莽撞。没有一位经理愿意听到他的开发成员说“我们要停工三个月来清理以前的代码”。而且开发人员本来也就不应该这样做。大规模的重构只会带来灾难。

你面前的代码也许看起来混乱极了,不要着急,一点一点慢慢地解决这些问题。当你想要添加新功能时,用上几分钟时间把代码整理一下。如果首先添加一些测试能使你对整理工作更有信心,那就那么做,它们会回报你的努力。如果在添加新代码之前进行重构,那么添加新代码的风险将大大降低。重构可以使你更好理解代码的作用和工作方式,这使得新功能的添加更容易。而且重构之后代码的质量也会大大提高,下次你再有机会处理它们的时候,肯定会对目前所做的重构感到非常满意。

重构时你总会发现某些代码并不正确。你绝对相信自己的判断,因此想马上把它们改正过来。但你千万要顶住诱惑,别急着那么做。重构时你的目标之一就是保持代码的功能完全不变,既不多也不少。对于那些需要修改的东西(比如程序功能等),列个清单把它们记录下来,需要添加或修改 的测试用例(test cases)、需要进行的其他重构、需要撰写的文档、需要画的图…… 都暂时记在卡上。这样就不会忘掉这些需要完成的工作。千万别让它们打乱你手上的工作。等你的重构完成之后,再去做这些要修改的事情。

备注:大型重构

1. Tease apart Inheritance 梳理并分解继承体系

某个继承体系同时承担两项责任 ,建立两个继承体系,并通过委托关系让其中一个可以调用另一个。



2. Convert Procedural design to Objects 将过程化设计转化为对象设计

你手上有一些传统过程化风格的代码 ,将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。



3. Separate Domain from from Presention 将领域和表述/显示分离

某些GUI类之中包含了领域逻辑 ,将领域逻辑分离出来,为它们建立独立的领域类。



4. Extract Hierarchy 提炼继承体系

你有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的 ,建立继承体系,以一个子类表示一种特殊情况。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息