项目/代码重构

由于最近一段时间一直在公司做项目优化,从中确实体会到代码优化的必要性,同时,也看到许多代码需要重构的必要性。结合网上的重构文章,精简的记录一下(程序员都很忙,而且大多时候只是为了解决棘手的问题,不适合看长篇大论)关于代码重构

大多出自该书:《重构-改善既有代码的设计》(此作者就是最早提出微服务的大牛——Martin Fowler)

重构不仅仅是代码整理,它还提供了一种高效且受控的代码整理技术。

(一)重构前言

1、何谓重构
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

2、为何重构
改进软件设计、帮助找到Bug、提高编程速度。

3、何时重构
任何情况下我都反对专门拨出时间进行重构。重构本来就不是一件应该特别拨出时间做的事情,重构应该随时随地的进行。

当然,如果代码非常混乱,重构的代价太大,不如直接重新写!

(二)哪些代码需要重构——坏代码

新版增加:神秘命令(Myterious Name)、全局数据(Global Data)、循环语句(Loop)等。

1、重复代码
如果多个地方看到相同的程序结构,那么可以肯定:设法将它们合二为一,程序会变得更好 。
两个互为兄弟的子类内含有相同的表达式:提炼出相同代码,将它推入超类内;
两个毫不相干的类中出现:将重复的代码提炼到一个独立的类中。

2、过长的函数/方法
很显然,一个函数太长,易读性和逻辑性会变差。
所以:
(1)每当感觉需要以注释说明的时候,就需要把要说明的东西写进一个独立函数,并以其用途命名。
(2)确定提炼哪一段代码的方法:寻找注释,注释通常能指出代码用途和实现手法之间的语义距离
(3)条件语句和循环常常也是提炼的信号

3、过大的类
单个类不要做太多的事情,否则其内往往就会出现太多实例变量。
可以将几个变量一起提炼至新类内。提炼时应该选择类内彼此相关的变量,将它们放在一起。通常如果类内的数个变量有着相同的前缀或字尾,这就意味有机会把它们提炼到某个组件内。

4、过长参数列
太长的参数列难以管理,太多的参数会造成前后不一致、不容易使用,而且一旦你需要更多数据,就不得不修改它。如果将对象传递给函数,大多数修改都将没有必要。

5、发散式变化
所谓发散式变化是指一个类因为多个因素的变化要改来改去,对于这种类就需要将这个类拆分开。拆分成一个类只受一个因素的影响(最好是一个因素)。

6、散(霾)弹式修改
简单的说就是,一种变化引发多个类相应修改的情况。这种情况主要是非常容易少改相应的类。
这种情况可以把所有需要的代码放进同一个类。如果眼下没有合适的类可以安置这些代码,就创造一个。

7、依恋情结
对象编程的思想是:将数据和对数据的操作行为包装在一起。有时候一个函数往往会用到几个类的功能,那么它究竟该被置于何处呢?处理原则通常为:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。

8、数据泥团
如果在很多地方看到相同的三四项数据一起出现。那么这些总是绑在一起出现的数据应该拥有属于他们自己的对象。
找到这些数据以字段形式出现的地方,将它们提炼到一个独立的对象中。这么做的直接好处是可以将很多参数列缩短简化函数调用。

9、基本类型偏执
这个翻译其实并不准确,只是字面翻译而已,原文用的是:Primitive Obsession,不用太纠结这个说法。
这个情况主要是由于刚开始创建类时,字段比较少,而随着特性的不断增加,基本数据类型的字段就会越来越多。将它们有组织地结合起来,可以更方便的管理这些数据。
(1)如果你有大量的基本数据类型字段,就有可能将其中部分存在逻辑联系的字段组织起来,形成一个类。更进一步的是,将与这些数据有关联的方法也一并移入类中。为了实现这个目标,可以尝试以类取代类型码(Replace Type Code with Class) 。——提取关联的基本属性为单独的类
(2)如果基本数据类型字段的值是用于方法的参数,可以使用引入参数对象(Introduce Parameter Object) 或保持对象完整(Preserve Whole Object) 。——传参数时,尽量使用对象作为参数
(3)如果想要替换的数据值是类型码,而它并不影响行为,则可以运用以类取代类型码(Replace Type Code with Class) 将它替换掉。如果你有与类型码相关的条件表达式,可运用以子类取代类型码(Replace Type Code with Subclass) 或以状态/策略模式取代类型码(Replace Type Code with State/Strategy) 加以处理。——通过多种方式用对象替换可以替换的代码
(4)如果你发现自己正从数组中挑选数据,可运用以对象取代数组(Replace Array with Object) 。——从数组中过滤数据时,就用对象代替数组

10、switch惊悚现身——不要出现switch——重复的Switch
简单的说就是:少用switch语句,而应该考虑以多态来替换它。从本质上说,switch语句的问题在于重复。
但是,如果只是在单一函数中有些选择实例,且并不想改动它们或者说固定不变的情况,那么多态就有点杀鸡用牛刀了,没必要。

11、平行集成体系
存在这么一种情况:每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。(其实,平行继承体系就是散弹式修改的特殊情况)
那么,消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。

12、冗余类——项目中不要出现多余的类
某个类中的功能没有用的话,就及时删除掉或者将有用的极少部分代码合并到父类中,并删掉该类。

13、夸夸其谈未来性——过度设计
如果某个抽象类其实没有太大作用,可以将超类和子类合为一体。将不必要的委托转移到另一个类中,并消除原先的类。如果函数的某些参数未被用上,那么就将参数移走。如果函数名称带有多余的抽象意味,就应该对它重命名,让它现实一些。

14、令人迷惑的暂时字段——临时字段
某个实例变量仅为某种特定的情况而设。这样的代码在一般情况下,让人不容易理解,看不出该变量的用途。
可以将这种实例变量单独创建一个类,并注明作用。

15、过度耦合消息链 —— 过长的消息链
如果请求一个对象时,这个对象需要请求另一个对象,然后再请求别的对象………这就是消息链。采用这种方式,意味着客户代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应的修改。
这时候我们可以隐藏“委托关系”,并在服务类上建立客户所需要的所有函数。你可以在消息链的不同位置进行这种重构手法。理论上是可以重构消息链上的任何一个对象,但是这样做往往会把一系列对象都变成“中间人”。通常更好的选择是:先观察消息链最终得到的对象是用来干什么的,再看看能否通过抽取方法把使用该对象的代码提炼到一个独立函数中,然后再将这个函数推入消息链。

16、中间人
对象的基本特征之一就是封装——对外部世界隐藏其内部细节。封装往往伴随着委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。
这时候就应该移除中间人,直接和真正的负责人打交道。如果这样“不干实事”的函数只有少数几个,可以将它们放进调用端。如果中间人还有其它行为,可以把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。

17、狎暱关系 —— 内幕交易(Insider Trading)
类与类之间过分紧密的关系必须拆散——可以引入第三方类或者利用委托。

18、异曲同工的类
两个函数做同一件事,却有着不同的签名。但这往往不够,可以反复将某些行为移入类中,直到两者的协议一致为止。
当然,如果你必须移动大量代码才可以完成这个工作,那还不如直接构建一个父类。

19、不完美的库类 —— 最新版中去掉了这个
很多第三方库提供的接口经常不能恰如其分得满足我们的需求,这时候就需要对第三方接口做一层转换,或者给它添加一定的行为。

20、纯稚的数据类——这个坏代码被翻译的确实很业余
Data Class,为啥说是纯稚呢?它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其它类过分细琐地操控着。Data Class就像小孩子,作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任。所以叫纯稚
这些类早期可能拥有public字段,果真如此就应该在别人注意到它们之前将它们封装起来。如果这些类内含容器类的字段,就应该检查它们是不是得到了恰当的封装;如果没有,就把它们封装起来。对于那些不该被其它类修改的字段,就应该去掉该字段的所有设值函数。

21、被拒绝的遗赠——Refused Request,这个翻译也很业余
一般来讲,子类应该继承超类的一切,但如果它们不想或者不需要继承,又该怎么办呢?按照传统说法,这就意味着继承体系的设计错误。你需要为这个子类新建一个兄弟类,然后让父类只包括两个子类共享的部分。
一般而言,这就足够了,但是如果子类不愿意支持超类提供的接口,则说明不能使用继承处理,应该使用委托。

22、过多的注释
注释当然是必须的,但是不要因为代码的混乱而加注释,遇到这种情况时,请先重构代码后再加必要的注释。

在实际项目开发中,我们不可能一条一条的比对是否会犯以上提到的问题,以上的这些问题还是需要根据实际情况而定。当然,并不是说以上这些问题不重要,而是基于这些经常出现的问题,培养我们良好的编码习惯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章