转自独立blog《Adrop的自留地

出来混,迟早要还的

我们欠下的那些技术

 

这段代码需要怎么重构?

if ((version < 380 &&from == 6) || (version < 370 &&from == 7) ||from == 1013)

        {

            if (tagNo == 1 && shareMsgId >= 0)

            {

                // …;

            }

        }

如何添加一个新的处理分支

bool GetUrlByMD5(const ST& head, ST2& request)

{

       根据md5cache中查询url

       查到 return true

       没查到获失败 return false

}

增加一种处理分支,根据配置支持从cache中通过MD5获取url,同时也支持从cache中获取的方式。如何更好的增加这个功能?

 

 

你认为重构是什么,列举一些你常用的重构方式?

 

为什么不想重构

程序能运行、没有bug、能满足需求就不错了,还额外花那么多时间重构,就那么点工资

天天加班忙着赶需求被产品和领导催,哪有时间重构

逻辑太复杂了,不好重构

逻辑太复杂了,不知道如何下手重构

这么复杂的逻辑,不敢乱动,重构出问题了谁负责

 

 

大型重构和日常重构

 

大型重构

日常重构无处不在

始终以写程序库的思想来写代码,时刻考虑被调用

 

什么时候不能重构

重构与设计

重构与敏捷

重构方法可能是有争议的

重构方法可能是完全相反的

 

 

What?

名词:

A change to made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

在不改变可见行为的前提下,为了让软件更易于理解和更方便修改而对其内部结构所做的改变。

 

动词:

To restructure software by applying a series of refactorings without changing its observable behavior.

在不改变软件可见行为的情况下,运用一系列重构方法,重组其结构。

 

Why?

为了让代码更优美,更赏心悦目

满足形式的完美主义和代码偏执倾向

 

重构能改善软件设计

重构使软件更容易理解

重构有助于找到bug

重构有助于提高编程速度

 

 

When?

任何有重复的时候重构
不爽的时候就重构

bad smell

       重复代码

       过长函数

       过大的类

       过长参数列表

       发散式变化-一个类受多种变化影响

       散弹式修改-一种变化引发多个类的修改

       依恋情节-过于依赖另一个类的多个方法

       数据泥团-多个类中一组字段频繁出现,一组方法参数在多个方法参数列表中频繁出现

       基本类型偏执-一组基本数据类型多处出现,可以提取为一个类

       swtich语句

       平行继承体系-散弹式修改的特例,增加一个子类,同时也需要增加另一个类的子类

       冗余类

       过度设计

       类的临时字段-只在某种特定情况下才有效的类的字段

       过度的链式函数调用

       中间人-类的函数过多的委托给其他类

       亲密关系-一个类对另一个类的private成员过于好奇,尤其是继承关系下子类对父类的过度依赖

       类似功能的类

       不够用的类库

       数据类-只有数据成员的类,如果有

       不完整的继承-只需要父类的少数方法

       注释-最好的注释是不要注释,自注释

      

 

添加功能时重构

修正bug时重构

评审代码时重构

 

重构分类和方式

 

重新组织函数

       提炼函数

       内联函数-消除不必要的函数

       内联临时变量-消除不必要的临时变量

       以查询取代临时变量

       引入解释性变量

clip_image002[8]

       分解临时变量

clip_image004[8]

       移除对参数的赋值-出参的值被修改掉,避免有出参

       以函数对象取代函数-一个大函数,提取为一个类,并分解为多个小函数

       替换算法-修改函数本体为另一个算法实现

在对象之间搬移特性

       搬移函数

       搬移字段

       提炼类

       将类内联化-消除不必要的类,合并入另一个类中

       隐藏委托关系-通过委托类调用另一个对象,在服务类上实现客户所需要的所有函数,以隐藏委托

       移除中间人-客户方直接调用受托类

       引入外加函数-类库不提供的方法,自己实现,不对调用方有任何依赖

       引入本地扩展-类库不提供的方法,封装在类中,继承或组合的方式

重新组织数据

自封装字段-引入gettersetter,字段设置为private,并且有简单逻辑

以对象取代数据值-简单数据值随着软件发展,需要和其他数据项和行为一起使用才有意义,taf接口中,始终用结构来作为入参和出参,而不用基本数据类型

将值对象改为引用对象-引用对象,是可变的,值对象是不可变的,内部值都是不可变化不可被设置的

将引用对象改为值对象

以对象替代为数组-有意义的数组,比如索引为0的元素表示xxx1标识xxx,用类来封装

复制被监视数据-MVCGUI中,Model和数据逻辑处理的分离

将单向关联改为双向关联

将双向关联改为单向关联

以字面常量取代魔法数-有意义名称的常量、宏替代

封装字段-加入简单逻辑

封装集合-类中有arraylist等集合,不提供对集合字段的直接访问,支持对集合元素的add/remove等方法,并且返回不可修改的集合副本

以数据类取代记录-记录可以认为是c中的struct,用类来代替

以类取代类型码-可枚举的常量,用类来代替,类是不能创建的,预先创建好设置为static,直接使用,好处是避免传入非法的值

以子类取代类型码-不同的类型有不同的行为,才考虑,否则考虑用类取代类型码来重构。

State/Stategy取代类型码-你有一个type code ,它会影响class 的行为,但你无法使用subclassing。将类型封装成一个类,做为构造函数的参数传入。

以字段取代子类-你的各个子类的惟一差别只在「返回常量数据」的函数身上。消除子类,增加类型码的字段,从构造函数传入,并且用工厂替代构造函数

简化条件表达式

 

分解条件语句

     if条件提取为一个方法

     then部分提取为一个方法

     else部分提取为一个方法

合并条件语句-检查条件相同,处理行为是一致的,考虑将条件合并或提取为单独的方法

合并的重复的条件片段-一组条件表达式的处理中都包含了相同的代码,提取这些相同代

码,根据情况搬离到条件表达式之前或之后

去除控制标志-条件表达式处理中有根据条件对某个控制标志赋值的情况

     break代替回控制标志

     return代替控制标志

用守卫语句代替嵌套条件语句-针对某个条件单独检查并立刻从函数中返回

clip_image006[8]

用多态代替条件语句-一个表达式中根据一个类型码的不同而有不同的行为

引入空对象-需要多次判断某个对象是否为空

clip_image008[8]

引入断言

简化函数调用

       函数改名

       添加参数

       将查询函数和修改函数分离

       令函数携带参数-若干函数做了类似的工作,但在函数本体中却包含了不同的值。

clip_image010[8]

       以明确函数取代参数-有一个函数,其内完全取决于参数值而采取不同反应

clip_image012[8]

       保持对象完整-你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。

clip_image014[8]

       以函数取代参数-对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数也可以(也有能力)调用前一个函数。

clip_image016[8]

       引入参数对象-以对象取代同时出现的多个参数

       移除设置函数-不需要被改变的值,无需设置函数,不该暴露的也不要暴露

隐藏函数-不该暴露的也不要暴露

以工厂函数取代构造函数

封装向下转型

以异常取代错误码

以测试取代异常

处理概括关系

       字段上移

       字段下移

       构造函数本体上移

       函数上移

       函数下移

       提炼子类

       提炼超类

       提炼接口

消除不必要的继承体系

       构造模板函数-Template Pattern

       以委托取代继承-某个subclass 只使用superclass 接口中的一部分,或是根本不需要继承而来的数据。

       以继承取代委托-在两个classes 之间使用委托关系(delegation),并经常为整个接口编写许多极简单的请托函数(delegating methods)。