重构改善既有代码的设计 – 原则篇
重构大到模式设计,也小到一个接口函数
何时重构
三次法则
- 第一次做某件事只管去做,第二次做类似的事情会产生反感,但还是可以做,第三次再做类似的事情就应该重构
添加功能时重构
- 代码的设计无法帮助我轻松的添加需要的功能特性。
修补错误时重构
- 调试代码修改Bug的过程中发现代码可读性不好,可以用重构来加深理解
坏代码的味道
- 重复代码(Duplicated Code)
- 在一个以上的地方发现相同的程序结构,可以肯定的重构,设法合而为一。
- 过长函数(Long Method)
- 如果函数内有大量的参数和临时变量,阻碍了代码阅读。总之一句话
程序越长,越难理解
当你感觉需要以注释来说明点什么的时候,就需要把需要说明的东西写进一个独立的函数
- 函数名称命名要能够体现函数# 重构改善既有代码的设计
重构大到模式设计,也小到一个接口函数
何时重构
三次法则
- 第一次做某件事只管去做,第二次做类似的事情会产生反感,但还是可以做,第三次再做类似的事情就应该重构
添加功能时重构
- 代码的设计无法帮助我轻松的添加需要的功能特性。
修补错误时重构
- 调试代码修改Bug的过程中发现代码可读性不好,可以用重构来加深理解
坏代码的味道
- 重复代码(Duplicated Code)
- 在一个以上的地方发现相同的程序结构,可以肯定的重构,设法合而为一。
- 过长函数(Long Method)
- 如果函数内有大量的参数和临时变量,阻碍了代码阅读。总之一句话**
程序越长,越难理解
** 当你感觉需要以注释来说明点什么的时候,就需要把需要说明的东西写进一个独立的函数
- 函数名称命名要能够体现函数 “做什么” 而不是 “怎么做”
- 过大的类 (Large)
- 单个类想要做太多的事情,内部出现太多的实例变量,一方面会有重复代码的问题,另一方面类会很混乱。
- 举个Ext的例子,Ext的Form元素,都有基类Field只提供基础函数getJsonValue和setJsonValue,子类TextField、TextArea、ComboBox都是基于这个组件来写的,假设Field组件想要把TextField和Combox组件的功能都实现,代码会显得很冗余
- 过长参数列 (Long Parameter List)
- 太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦你需要更多数据,就不得不修改它。可以把对象传递给函数,大多数修改都没有必要,因为你可能只需要在函数内增加一两条请求,就能得到更多数据。
- 发散式变化(Divergent Change)
- 如果某个类经常因为不同的原因在不同的方向上发生变化。比如在AF的数据表格展示身上,增加一种类型,不止一处需要对应修改,这个很容易造成改动引发。针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反映此变化。
- 霰弹式修改(Shotgun Surgery)
- 如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery。如果要修改的代码散落各处,你不但很难找到它们,也很容易遗忘某个重要的修改。
Divergent Change 是指一个类受多种变化的影响,而Shotgun Surgery则是指一种变化引发多个类做出相应修改。这两种情况下你都会希望整理代码,使外界变化与需要修改的类趋于一一对应。
- 依恋情结(Feature Envy)
- 函数对某个类的兴趣高过对自己所处类的兴趣。这种孺慕之情最通常的焦点便是数据。场景:某个函数为了计算某个值,从另外一个对象身上调用的几乎半打的取值函数。所以可以把它移到它该去的地方。
- 一个函数往往会用到几个类的功能,那么它应该被置于何处呢?**
原则是:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。最根本的原则是:将总是一起变化的东西放在一块儿。数据和引用这些数据的行为总是一起变化的。
- 数据泥团(Data Clumps)
- 可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些总是绑在一起的数据真应该拥有属于它们自己的对象。可以将它们提炼到一个独立对象中
- 基本类型偏执(Primitive Obsession)
- 大多数编程环境都有两种数据:结构类型允许你将数据组织成有意义的形式:基本类型则是构成结构类型的积木块。结构总是会带来一定的开销。对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。就比如喜欢传递很多参数,而不使用一个对象传参的形式
- Switch惊悚现身(Switch Statements)
- 面向对象程序的一个最明显特征就是:少用switch或case语句。从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散步于不同地方。如果要为它添加一个新的case子句,就必须找到所有switch语句并修改它们。switch语句常常根据类型码进行选择,你要的是“与该类型码”相关的函数或类。所以应该将switch语句提炼到一个独立函数中。
- 平行继承体系(Parallel InheritanceHierarchies)
- 当你为某个类增加一个子类,必须也为另一个类相应的增加一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,便是闻到了这种坏味道。消除这种重复性一般策略是:让一个继承体系的实例引用另一个继承体系的实力。
- 冗余类(Lazy Class)
- 如果一个类不值得其身价,它就应该消失。因为你所创建的每一个类,都得有人去理解它、维护它,这些工作都是要花钱的。比如一个组件里面需要获取一个Person的address、tel、name的时候,写了三个get函数,而不是直接返回Person的时候
- 夸夸其谈未来性(Speculative Generality)
- 如果某个抽象类没有太大的作用,可以直接去掉,再或者有些参数目前没用到,可以移除。
- 令人迷惑的暂时字段(Temporary Field)
- 有一种对象是这样的:其内某个实例变量仅为某种特定情况而设。这样的代码会让人很不理解,通常认为对象所在的字段都需要他所有的变量。比如某个算法,由于不希望传递多个参数,而使用一个对象的时候,但是这些字段只在使用算法的时候才需要使用,这个时候应该把这些特殊字段,抽离成一个独立的函数。
- 过度耦合的消息链(Message Chains)
- 如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象,这就是消息链。实际代码中,你可能看到的是一长串的getThing()或一长串临时变量。采取这种方法意味着客户代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端将不得不做出相应修改。这个场景在AF里面还挺多,跨组件调用函数,getFormWin().getJsonForm().getField(‘xxxx’)
- 中间人(Middle Man)
- 对象的基本特征之一就是封装----对外部世界隐藏其内部细节。封装往往伴随委托。比如你问主管是否有时间参加一个会议,他就把这个消息“委托”给他的记事簿,然后才能回答你。但是人们可能过度使用委托,你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。
- 狎昵关系(Inappropriate Intimacy)
- 两个类过于密切,花费太多时间去探究彼此的private成分。这是不好的行为,我们希望类严守轻轨。这个规范在checklist中有一条叫解耦来着, AF的ACL应用控制策略模块,有类似情况,表单弹窗的提交逻辑写在了Mgr中,导致后续表单复用的时候,需要实例化这个Mgr传入,组件之间没有解耦开来。
- 异曲同工的类(Alternative Classes with Different Interfaces)
- 如果两个函数做同一件事,却有着不同的名称,请根据据他们的用途重新命名,但这往往不。还可能需要反复的抽离公共函数出来。
- 不完整的库类 (Incomplete Libray Class)
- 举个例子,表格勾选组件,原来不支持单选模式和多选模式的切换,这个时候这个组件库又不能改的情况,可以继承勾选组件,在原来勾选的基础上再写一个组件,而不是去改原来的组件库的代码。
- 纯稚的数据类(Data Class)
- 所谓Data Class是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其他类过分细琐的操纵着。 没想到有啥坏处
- 过多的注释(Comments)
- Comments不是一种坏味道,事实上,它们还是一种香味呢。常常会看到这样的情况:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。这种情况的发生次数之多,实在令人吃惊。
当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
代码的味道之解决方法
代码味道 | 常用方式 |
---|---|
异曲同工的类 | 函数重命名,移动函数 |
过多的注释 | 函数抽离 |
纯稚的数据类 | 移动函数、封装字段或者集合 |
数据泥团 | 类抽离、引入对象参数、保持对象完整 |
发散式变化 | 函数抽离 |
重复代码 | 函数抽离、塑造模板函数、函数上移 |
依恋情节 | 移动函数、函数抽离 |
狎昵关系 | 移动函数、函数抽离、用委托替代继承、隐藏“委托关系” |
不完整的库类 | 引入外加函数、引入本地拓展、接口抽离 |
过大的类 | 抽离子类、用对象代替数据值 |
冗赘类 | 将类内联化、折叠继承关系 |
过长函数 | 函数抽离、用查询代替中间变量、分解条件表达式、用函数代替对象 |
过长参数列 | 用函数代替对象、引入对象参数、保持对象完整 |
过渡耦合消息链 | 隐藏“委托关系” |
中间人 | 移除中间人、以继承代替委托 |
平行继承关系 | 移动函数 |
基本类型偏执 | 用对象代替数据值、引入参数对象、用子类代替类型码 |
被拒绝的遗赠 | 用继承代替委托 |
霰弹式修改 | 移动函数、将类内联化 |
夸夸其谈未来性 | 折叠继承关系、移除参数、函数重命名 |
switch惊悚现身 | 以多态取代条件表单式、用类取代类型码、以明确函数取代参数 |
令人迷惑的字段 | 抽离类 |