转自独立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)
{
根据md5从cache中查询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时重构
评审代码时重构
重构分类和方式
重新组织函数
提炼函数
内联函数-消除不必要的函数
内联临时变量-消除不必要的临时变量
以查询取代临时变量
引入解释性变量
分解临时变量
移除对参数的赋值-出参的值被修改掉,避免有出参
以函数对象取代函数-一个大函数,提取为一个类,并分解为多个小函数
替换算法-修改函数本体为另一个算法实现
在对象之间搬移特性
搬移函数
搬移字段
提炼类
将类内联化-消除不必要的类,合并入另一个类中
隐藏委托关系-通过委托类调用另一个对象,在服务类上实现客户所需要的所有函数,以隐藏委托
移除中间人-客户方直接调用受托类
引入外加函数-类库不提供的方法,自己实现,不对调用方有任何依赖
引入本地扩展-类库不提供的方法,封装在类中,继承或组合的方式
重新组织数据
自封装字段-引入getter、setter,字段设置为private,并且有简单逻辑
以对象取代数据值-简单数据值随着软件发展,需要和其他数据项和行为一起使用才有意义,taf接口中,始终用结构来作为入参和出参,而不用基本数据类型
将值对象改为引用对象-引用对象,是可变的,值对象是不可变的,内部值都是不可变化不可被设置的
将引用对象改为值对象
以对象替代为数组-有意义的数组,比如索引为0的元素表示xxx,1标识xxx,用类来封装
复制被监视数据-MVC或GUI中,Model和数据逻辑处理的分离
将单向关联改为双向关联
将双向关联改为单向关联
以字面常量取代魔法数-有意义名称的常量、宏替代
封装字段-加入简单逻辑
封装集合-类中有array、list等集合,不提供对集合字段的直接访问,支持对集合元素的add/remove等方法,并且返回不可修改的集合副本
以数据类取代记录-记录可以认为是c中的struct,用类来代替
以类取代类型码-可枚举的常量,用类来代替,类是不能创建的,预先创建好设置为static,直接使用,好处是避免传入非法的值
以子类取代类型码-不同的类型有不同的行为,才考虑,否则考虑用类取代类型码来重构。
以State/Stategy取代类型码-你有一个type code ,它会影响class 的行为,但你无法使用subclassing。将类型封装成一个类,做为构造函数的参数传入。
以字段取代子类-你的各个子类的惟一差别只在「返回常量数据」的函数身上。消除子类,增加类型码的字段,从构造函数传入,并且用工厂替代构造函数
简化条件表达式
分解条件语句
● if条件提取为一个方法
● then部分提取为一个方法
● else部分提取为一个方法
合并条件语句-检查条件相同,处理行为是一致的,考虑将条件合并或提取为单独的方法
合并的重复的条件片段-一组条件表达式的处理中都包含了相同的代码,提取这些相同代
码,根据情况搬离到条件表达式之前或之后
去除控制标志-条件表达式处理中有根据条件对某个控制标志赋值的情况
● break代替回控制标志
● return代替控制标志
用守卫语句代替嵌套条件语句-针对某个条件单独检查并立刻从函数中返回
用多态代替条件语句-一个表达式中根据一个类型码的不同而有不同的行为
引入空对象-需要多次判断某个对象是否为空
引入断言
简化函数调用
函数改名
添加参数
将查询函数和修改函数分离
令函数携带参数-若干函数做了类似的工作,但在函数本体中却包含了不同的值。
以明确函数取代参数-有一个函数,其内完全取决于参数值而采取不同反应
保持对象完整-你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
以函数取代参数-对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数也可以(也有能力)调用前一个函数。
引入参数对象-以对象取代同时出现的多个参数
移除设置函数-不需要被改变的值,无需设置函数,不该暴露的也不要暴露
隐藏函数-不该暴露的也不要暴露
以工厂函数取代构造函数
封装向下转型
以异常取代错误码
以测试取代异常
处理概括关系
字段上移
字段下移
构造函数本体上移
函数上移
函数下移
提炼子类
提炼超类
提炼接口
消除不必要的继承体系
构造模板函数-Template Pattern
以委托取代继承-某个subclass 只使用superclass 接口中的一部分,或是根本不需要继承而来的数据。
以继承取代委托-在两个classes 之间使用委托关系(delegation),并经常为整个接口编写许多极简单的请托函数(delegating methods)。