对于部分很老的业务,软件也许已经经过几个、几十个、甚至上百个人的修改,面目全非。
于是,bug越来越多,越来越难维护,新的需求越来越难实现,软件的构架对新的需求渐渐的失去支持能力。这就是这个软件系统的生命走到尽头的时候。
代码的味道是高水平程序员对“好程序“的一种感觉,他们具备一种能力,即使不涉及程序代码的具体实现就能看出你的设计是否合理。
如果代码有“异味“,那么你需要进行Refactorying.
1. Duplicated Code(重复的代码)
- 症状:
– 重复的表达式
– 不同算法做相同的事
– 类似代码
- 措施:
– 一个类中包含两段重复代码时,使用提炼方法置于一处
– 两个相同层次的类含有重复代码时,提炼方法,方法上移
– 两个无关类中出现重复代码时,提炼方法至其中一个类或其他类中供调用
臭味行列中首当其冲的就是Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。
2. Long Method(长函数)
- 症状:
– 存在大量的代码行
– 注释
– 条件和循环
- 措施:
– 每当需要注释时,写一个方法来代替注释
– 绝大多数场合下,采取提炼方法技术来缩短函数
– 方法带有很多参数和临时变量时,采取查询替换临时变量和链式调用替换临时变量来消除临时变量;采用引入参数对象和保留完整对象技术瘦身参数列表;重型武器:使用方法对象替换方法
– 使用分解条件语句处理条件表达式;使用集合闭包方法替换循环
拥有[短函数](short methods)的对象会活得比较好、比较长。
3. Large Class(过大类)
- 症状:
– 出现太多实例变量
– 包含太多代码
- 措施:
– 提炼类把一些变量打包
– 可以作为子类时,提炼子类
– 无法作为委托时,提炼模块
4. Long Parameter List(过长参数列)
- 症状:
– 参数列表过长
– 参数列表变化频繁
- 措施:
– 传递对象,使方法自己获取它需要的参数
– 参数来自同一对象或是对象本身,保留整个对象为参数
– 参数来自不同对象,引入参数对象或者引入命名参数
刚开始学习编程的时候,老师教我们:把函数所需的所有东西都以参数传递进去。
5. Divergent Change(发散式变化)
- 症状:
– 一个类因为不同的理由以不同的方式修改
- 措施:
– 将两类变化涉及的方法拆为两个对象,每个对象只因为同一类变化而变化
– 将某种特定原因造成的所有变化提炼为一个新类,新类处理这一类变化
6. Shotgun Surgery(霰弹式修改)
- 症状:
– 发生一次改变时,需要修改多个类的多个地方
- 措施:
– 使用移动字段和移动方法将所有修改集中到单个类中。若现有类无好的候选者,创建;通常可以使用内联化类将整组行为整合到一起。
7. Feature Envy(依恋情结)
- 症状:
– 一个方法调用其他类的方法比较多
– 一个方法调用到多个类的功能
- 措施:
– 移动方法至另一适当的对象中
– 看哪个类含有最多的数据就把方法和那些数据放在一起
8. Data Clumps(数据泥团)
- 症状:
– 两个类中相同的实例变量
– 许多方法签名中相同的参数
- 措施:
– 对于实例变量,使用提炼类将其变成一个对象
– 对于方法签名,引入参数对象或保留完整对象来瘦身
9. Primitive Obsession(基本型别偏执)
- 症状:
– 使用了基本类型(fixnum、string、array)
- 措施:
– 使用对象替换数据值
– 使用多态替换类型码
– 使用模块扩展替换类型码
– 使用状态或策略模式替换类型码
– 使用对象替换数组
10. Switch Statements(switch惊悚现身)
- 症状:
– 代码使用了case语句
- 措施:
– 用多态替换case语句
- 提炼方法,先把case语句提炼出来;
- 移动方法至需要多态行为的类;
- 选择技术:使用多态替换类型码、模块扩展替换类型码、状态或策略模式替换类型码
– 若分支很少,只在一个方法里起作用,使用显示方法替换参数或引入Null对象来替换case语句
面向对象程序的一个最明显特征就是:少用switch(或case)语句。从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同的地点。如果要为它添加一个新的case子句,你必须找到所有switch语句并修改它们。面向的多态概念可为此带来优雅的解决办法。
11. Parallel Inheritance Hierarchies(平等继承体系)
- 症状:
– 为某个类增加子类时,必须为另一个类增加子类
– 某个继承体系类名前缀和另一个继承体系类名前缀相同
- 措施:
– 使用移动字段和移动方法
12. Lazy Class(冗赘类)
- 症状
– 类并没有做什么工作
- 措施:
– 削减层次
– 内联化类或内联化模块
13. Speculative Generality(夸夸其谈未来性)
- 症状:
– 类或模块没太大用处
– 方法、代码分支,或整个类的用户只有测试用例
- 措施:
– 类或模块没太大用处,使用削减层次
– 不必要的委托,通过内联化类来移除
– 带有未使用参数的方法,使用移除参数
14. Temporary Field(令人迷惑的暂时值域)
- 症状:
– 某个实例变量仅为某种情况而设置
– 某些实例变量仅为某个函数的复杂算法少传参数而设置
- 措施:
– 提炼类,封装这些变量和需要他们的方法为方法对象
– 引入null对象为变量无效时创建额外组件,消除条件代码
15. Message Chains(过度耦合的消息链)
- 症状:
– 一长串的 get_This 或临时变量
- 措施:
– 使用隐藏委托技术
16. Middle Man(中间人)
- 症状:
– 一个类的接口中有很多方法在委托另一个类
- 措施:
– 这种方法数目很多时,使用移除中间人技术,直接和干活的对象打交道
– 这种方法的数目不多时,内联化方法到调用者中
– 这种方法包含额外的行为时,使用层次替换委托,将实际对象变成一个模块
17. Inappropriate Intimacy(过分亲密)
- 症状
– 一个类访问了另一个类的私有部分。
- 措施
– 使用移动方法和移动字段可以降低亲密程度
– 将双向关联改成单向
– 使用提炼类将公共部分提炼到安全之处
– 子类知道太多父类不想让他们知道的东西,使用委托替换继承让他们离家
18. Alternative Classes with Different Interfaces(异曲同工的类)
- 症状:
– 完成相同工作,但却使用了不同的方法名
- 措施:
– 使用重命名方法
– 使用移动方法将行为移到类中
– 因此出现冗余代码时,使用提炼模块或引入继承来修正
19. Incomplete Library Class(不完美的程序库类)
- 症状:
– 类库中函数构造的不够好,想扩展时可能很棘手
- 措施:
– 使用移动方法将需要的行为直接移到类库中去
20. Data Class(幼稚的数据类)
- 症状:
– 类仅由属性构成,纯粹用来持有数据,过分细致的依赖于其他类
- 措施:
– 对任何不应被修改的实例变量采用移除设值方法
– 运用封装集合技术封装集合实例变量
– 尝试移动方法将相关行为移到数据类中,对一些设值方法和取值方法使用隐藏方法
21. Refused Bequest(被拒绝的遗赠)
- 症状
– 子类不需要或仅需少部分父类的方法和数据
- 措施
– 创建兄弟子类,使用方法下移,将所有不用的方法移到兄弟类中(不推荐)
– 若子类冲用了行为却不想支持父类里的公共方法时,应该使用委托替换继承技术
22. Comments(过多的注释)
- 症状:
– 存在注释
- 措施:
– 提炼方法替代注释
– 提炼之后还需注释,则重命名方法
– 若需注明系统所需状态规则,则引入断言
在系统开发中我就发现,很多人写的代码有大片大片的注释,乱七八糟的格式,看起来就像稻草堆一样。注释掉的代码就要删除,即使将来需要还原还可以从版本控制工具中找到。提交的代码一定要干净。
- 狂热的元编程
- 症状:
– 元编程技术产生难懂的代码
- 措施:
– 使用动态方法定义替换动态接收器去除元编程
– 不去除时,使用隔离动态接收器来分隔疑虑
24. 脱节的API
- 症状:
– 为了灵活性而建立的细致、脱节的API,外加很多配置选项
- 措施:
– 同样的配置选项不断被重复使用时,引入网关用简化的方式和API交互
– 引入表达式构造器可同时作用于内部和外部API
25. 不断重复的样板文件
- 症状:
– 重复的样板文件
- 措施:
– 提炼方法
– 太普遍的方法,引入类标注