代码重构的常见症状与重构方法

    最近在看《重构》一书,收获颇多。

    重构,是有迹可循的。某些模式的代码,向我们昭示着重构的可能,书中作者称之为“代码的坏味道”。

    一:重复的代码

    在程序中出现两次以上的程序结构,应该进行重构:

    1:在一个函数中出现重复的结构(如:多个if语句),就要考虑优化算法,使用更简洁、高效的写法。

    2:同一个类中出现两次以上相同结构的代码,则提取出来作为一个函数。

    3:两个互为兄弟的子类之间含相同代码,则先提取出来作为独立函数,然后上推到父类中。

    4:两个互为兄弟的子类之间含相似代码,则先把相同部分提取作为函数1,不同部分作为函数2,然后在函数3中调用函数1、函数2,并把相同签名的函数上推到父类。【这样,由于子类各自重写了函数1、2,那么子类的函数3产生的结果就不同。此法名为“塑造模版函数”】

    5:两个毫不相关的类中重复代码,则把重复代码提取到一个新的独立类中,然后在原来的类中通过新类进行调用。(例如:提取到工具类中)

    二:过长函数

    函数是功能的基本单元,一个函数一个尽量承担一个职责。如果一个函数中,做了多步工作,则应该进行重构:

    1:我们在编写函数时,如果需要用注释来说明某一块代码时,则应该优先考虑把这部分代码作为一个函数来定义,并且通过函数名来说明其用途;在重构长函数时,这也是特征之一 —— “函数中哪里需要用注释说明其用途,则尝试提取出来作为独立函数,用函数名表达其用途

    2:对已有长函数进行分解:以单一功能为指标,提取每一部分代码进独立函数,最后原函数只需通过一系列调用语句,引用被提取出去的函数即可。

    3:长函数中的临时变量:在原函数中,如有使用一些临时变量来接收某个函数调用结果的,则把这些临时变量直接用函数调用语句代替。

    4:过长函数参数列:过长函数参数列表是函数调用出错的主要原因,可以新建一个参数类,把参数作为类成员,而调用时只需传递一个参数类对象即可。

    5:有太多临时变量和参数不能替代或提取:使用函数对象法:新建一个新的类,在其中通过一个成员变量,引用原来的类;把原函数中用到的临时变量、参数,全部作为类成员字段;定义一个函数,通过使用成员字段,实现与原函数一样的功能;最后,将原函数改造为:新建函数功能类对象(this,原临时变量,参数 作为构造参数),调用功能函数,并把结果返回。

    6:条件表达式改造:用于if语句的判断表达式往往是造成代码可读性下降的原因之一,某些判断语句用到的数据需要通读上下文才能理解。可以将条件语句提取出来,作为一个独立函数,通过函数名表达其判断内容,而函数內根据判断语句返回true 或 false 即可。

    7:循环语句改造:循环语句块同样可以承担单一职责,因此可以提取出来作为独立函数,函数名表达其用途。

    三:过大的类

    类是面向对象而设计的,如果一个类中包含了太多与其本身无关的功能时,就要考虑重构:

    1:将与本类无关的变量、函数,提取出来作为一个新类;

    2:如果提取出来的变量、函数,适合作为一个子类,则使用提取子类法;

    

    四:过长参数列表

    1:如果参数是某个函数的调用结果,则直接使用函数调用语句作为参数;

    2:如果某几个参数是属于某一个类的字段,则使用该类的对象作为参数,以保持对象的完整性;

    3:剩下的杂乱无章、缺乏归属对象的参数,则为它们制造一个类,用以容纳这些参数,以参数类对象作为函数参数。

    五:发散式变化【一个类受多种变化影响】

    类的设计要有可扩展性,并且修改要容易进行。如果一个类需要引入不同变化时,对于每种变化,需要修改多个地方,则需要进行重构:

    以变化为基本单元,对于某一种变化,所引起的修改,提取到一个新的类中,使得每种变化都分别对应于一个类而进行。

    六:霰弹式变化【一个变化,影响多个类】

    如果有一种变化,需要在多个不同类中进行修改,则需要进行重构:

    根据这个变化所引起的修改,把它们全部提取到一个类中。

    七:放错位置

    如果一个类中,有函数对另一个类的内容调用颇多,则需要进行重构:

    1:如果是一个函数过多调用另一个类的数据,则把该函数“搬移”到被那个类去;

    2:如果是一个函数中部分代码过多调用另一个类中数据,则先把该部分代码提炼为独立函数,然后再搬移。

    八:零散数据

    如果有一些数据,常常在一起出现,例如:作为函数参数经常出现,则需要进行重构:

    把这些零散的数据提炼到一个新的类中,以对象为单位来组织、使用这些数据。

    九:替换基本数据类型

    对于某些小规模、少字段的信息,虽然可以用几个基本数据类型来表达出来,但是这些零散数据一旦分开使用,就让人摸不清用途。所以需要重构:

    1:用小对象组合零散数据:例如:带有数值与货币种类的money类、由start和end字段组成的range类等,类名清晰易懂。

    2:不影响类行为,只用于表示某内容的类型码替换:

    3:影响类行为的类型码替换: 

    

    十:switch语句块重构

    将switch语句提炼到独立函数,尽量用多态来取代case判断语句的基本数据类型,最后把该函数上推到父类中去。

    十一:平行继承

    如果有两个继承体系,体系一的子类用途与体系二的子类用途相似,则需要重构:

    在体系一中引用体系二子类实例,将原子类中的函数改写为调用体系二子类实例中函数。

    十二:冗余类

    如果有一些子类、独立类,没有承担明确的用途,那么就需要重构:

    1:如果是没明确职责的子类,则折叠继承体系——把子类内容合并到父类去,取消子类。

    2:如果是没明确职责的独立类,则将其内容合并到最频繁调用它的类中去。

    十三:过长的调用链

    如果存在一个类调用类2,类2调用类3...造成一长串调用关系,则需要重构:

    1:隐藏委托关系:把 类1对象.类2对象字段.getXX() 形式的代码,在类1中进行封装,定义  get类2XX()  函数,在函数中通过类2对象.getXX()调用,并把结果返回。

    

    十四:去掉中间人

    1:  过度委托(一类中超过一半方法需要靠委托类来调用其他类方法):则隐藏中间人,去掉委托类,让调用者直接与负责的对象打交道。

    2:如果只有少数函数需要委托类来调用其他类方法:则把这些函数放进调用端,直接用调用端.XX()调用即可。

    3:如果委托类还有其他行为,则使用“继承取代委托”,继承实际负责类作为子类,从而扩展原对象的行为,又可以调用原对象的方法。

    十五:访问私有

    如果两个类之间彼此过多访问private内容,则需要重构:

    1:把经常需要互相调用的内容提取到一个新的类中,在新类中光明正大地直接调用;

    2:子类可以独立成类:则用委托取代继承。将子类作为一个独立的类来定义,在其中使用一个原父类对象进行内容调用。

    十六:异曲同工的内容

    如果有功能相同的函数、代码,则将它们进行统一。

    十七:为原有类库添加函数

    如果需要在原有的类库基础上添加新函数,可以使用继承原类库的功能类,在子类中添加新函数,在程序中使用自定义的子类即可。

    十八:类中的字段封装

    类中的字段应该保持私密性:

    1:普通字段封装:将public改为private,并定义public修饰的setter/getter函数。

    2:集合字段封装:对于集合类型的字段,定义public修饰的remove/add函数,在函数中通过集合字段本身调用removeadd操作。

    3:只读字段:对于一些定义了之后就不再修改的字段,我们应该在类的构造函数中进行赋值,然后只提供getter函数,隐藏setter函数。

    

    十九:拒绝继承

    如果一个子类只需要父类中的少数内容,那就应该用委托取代继承,避免在子类中无谓地实现父类的接口。

    

    二十:注释过多

    注释可以增强代码可读性,但是注释也为我们指明了重构的方向。

    1:需要用注释来说明一个代码块的用途时,尝试将其提取为独立函数,用函数名来表达用途;

    2:需要注释来说明某种条件、状态时,使用断言。

    

原文地址:https://www.cnblogs.com/ygj0930/p/7803868.html