重构笔记-异味

重构笔记-异味

额外方法Extract Method–>方法迁移Move Method

不要在别人对象的属性基础上运用switch语句或条件逻辑(可在自己对象的属性基础上使用),应运用多态

查询方法和插入方法分开

重构原则

重构名词定义:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本

重构动词定义:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构

重构与添加新功能:

添加新功能时,不应该修改即有代码,只管添加新功能
重构时不能再添加功能,只管改进程序结构
清楚知道自己何时是添加新功能,何时是重构

重构时机:

  • 添加功能时重构
  • 修补错误时重构
  • 复审代码review时重构

重构修改接口时:

  1. 尽量让旧接口调用新接口;当修改方法名时,留下旧方法,调用新方法;通过注解Deprecation修饰旧接口
  2. 一种特别的接口修改:在throws字句中增加一个异常
原接口
public void test() throws SQLException {
...
}
新接口增加异常
public void test() throws SQLException, IOException {
...
}
此时客户端代码不做相应修改 即捕捉非受检异常则无法通过编译
一种解决方式,捕捉非受检异常
public void test() throws SQLException {
	try {
	...
	} catch (IOException e) {
	...
	}
}
推荐方式:定义异常基类并抛出
public class CustomException extends Exception {
...
}
public void test() throws CustomException {
...
}

关注性能的三种编写软件方法:

  • 时间预算法
  • 持续关注法
  • 利用90%统计数据,即大部分时间消耗在一小半代码身上

间接层和重构:

  • 允许逻辑共享:一个子函数在两个不同的地点被调用,或超类中的某个函数被所有子类共享
  • 分开解释意图和实现:定义每个类和函数的名字解释自己的意图
  • 隔离变化:将行为和委托对象和接收对象解耦
  • 封装条件逻辑:通过多态处理表达条件逻辑

tip:

1.不要过早发布接口,请修改你的代码所有权政策policy,使重构更顺畅
2.不要公开过多的接口
3.重构会增加间接层
4.若代码复杂无法正常运作,最好的方式是重写,重构的前提是原代码可以稳定工作

问题代码

代码异味,体现在一个类中实例变量的数量,一个函数内代码的行数,重复代码,过长参数列,过大的类,过度耦合等

Deplicated Code重复代码

当同一个类的两个方法含有相同的表达式:采用Extract Method提炼重复代码,然后两个方法一同调用

当两个互为兄弟的子类内含有相同表达式:首先对两个类分别使用Extract Method110,然后再对被提炼出来的代码使用Pull Up Method332,将它推入超类。若代码间类似而非完全相同,则110提出共性,然后利用Form Template Method345获得一个Template Method设计模式(模板设计模式),super调用共性。若有些方法以不同的算法做相同的事,可以选择其中较清晰的一个,并使用Substitute Algorithm139将其他方法的算法替换掉

当两个毫不相关的类出现Duplicated Code:此时考虑对其中一个使用Extra Class149,将重复代码提炼到一个独立类中(间接类),然后在另一个类内使用这个新类。

	此时确定方法和类的关系,是建立独立类放置提取代码还是,该代码从属一个类,另一个类调用该类
	public class A {
		public void deplicated() {
			...
		}
	}
	public class B {
		private A a = new A();
		public void test() {
			a.deplicated();
		}
	}
	OR
	public class A {
    	private C c = new C();
		public void test() {
			c.deplicated();
		}
	}
    public class B {
    	private C c = new C();
		public void test() {
			c.deplicated();
		}
	}
	public class C {
		public void deplicated() {
			...
		}
	}

Long Method过长方法

拥有短函数的对象修改次数更少,存活时间更长。各种短函数中会有层层委托,充分利用了间接层的优势

一个函数的代码行数不要超过80行
处理函数时:

积极分解函数,遵循:需要注释代码时,将被注释代码写进一个独立函数中,并以其用途而非实现手法命名

一般将函数变小只需使用Extract Method

当函数内有大量的参数和临时变量,此时使用Extract Method会将参数和临时变量当做参数传递过来,导致可读性下降。此时使用Replace Temp with Query120消除临时元素,Introduce Parameter Object295(引入参数对象)和Preserve Whole Object288(保存对象完成a.test(param()))则可以将过长的参数列表变得更简洁

若如此做之后仍有太多临时变量和参数,则使用Replace Method with Method Object135(以函数对象取代函数) 利用函数对象执行函数

public class OutterClass {
	
	public int compute() {
		...
		return innerClass.test();
	}
	
	class InnerClass {
		int a;
		int b;
		int c;
		int test() {
			return a + b + c;
		}
	}
}

提炼代码的技巧:寻找注释,一个注释的下面一般指明代码用途和实现手法之间的语义距离,将注释下的代码替换成一个函数并在注释的基础上给函数命名

对于条件表达式,使用Decompose Conditional238处理
对于循序,将循环和其内的代码提炼到一个独立函数中

Large Class过大的类

一个类做太多事情就会出现太多实例变量

可以运用Extract Class149将几个变量提炼至新类内。提炼时应该选择类内彼此相关的变量,将它们放在一起,若这个组件适合作为一个子类,使用Extract Subclass149

一个类若拥有太多代码,Extract Method

一个类有太多代码也可以使用 Extract Class149和Extract Subclass330;技巧:先确定客户端如何使用它们,然后运用Extract Interface341为每一种使用方式提炼出一个借口

Long Parameter List过长参数列

若向已有的对象发出一条请求就可以取代一个参数,那么应该使用Replace Parameter with Method292.已有对象可能是函数所属类内的一个字段,也可能是另一个参数。还可以使用Preserve Whole Object288将来自同一个对象的一堆数据收集起来,并以该对象替换它们。若某些数据却反合理的对象归属,使用Introduce Parameter Object295为其制造出一个参数对象

注意:若你不希望造成被调用对象与较大对象间的某种依赖关系。此时将数据从对象中拆解出来单独作为参数,合情合理。

Divergent Change发散式变化

跳出系统的耦合,修改无需修改其他点

若某个类经常因为不同的原因在不同的方向上发生变化,符合Divergent Change,应找出某特定原因而造成的所有变化,然后运用Extract Class149 将它们提炼到另一个类中

Shotgun Surgery 霰弹式修改

类似Divergent Change,但相反。若遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的异味就是Shotgun Surgery

此时应用Move Method142 和 Move Field146 把所有需要修改的代码放进一个类中,通常运用Inline Class 154 把一系列相关行为放进同一个类。但会造成少量Divergent Change

Divergent Change是指:一个类受多种变化的影响

Shotgun Surgery则是指:一种变化引发多个类相应修改

Feature Envy 依恋情结

一个函数调用过多另一个对象的取值函数。使用Move Method将该函数移到它该去的地方

当函数中只有部分过于依赖其他对象的函数,则使用Extract Method提炼独立函数,再使用Move Method

Move Method判断依据:哪个类拥有最多被此函数使用的数据

Strategy模式和Vistor模式破坏了依恋情结,增加间接层解决Divergent Change

Data Clumps 数据泥团

数据项如两个类中相同的字段,许多函数签名中相同的参数,这些总是一起出现的数据应该存在一个独立对象中 Extract Class,然后通过Introduce Parameter Object或Preserve Whole Object为函数签名减重。如此使参数列缩短

数据泥团的味道:当删掉众多数据项中的一项时,其他数据是否因而失去意义,若是则表明应该为这些数据项产生一个新对象

Primitive Obsession 基本类型偏执

结构数据 :对象,带来额外空间开销
基本类型:int等,没有额外空间开销,不具备函数

使用Replace Data Value with Object175将原本单独存在的数据值替换为对象

若想要替换的数据值是类型码,而它不影响行为,则可以使用Replace Type Code with Class218

若有与类型码相关的条件表达式,则使用Replace Type Code with Subclass或Replace Type Code with State/Strategy

若有一组总被放在一起的字段可使用Extract Class

若在参数列中看到基本类型数据,可用Introduce Parameter Object

若发现自己正从数组中挑选数据,可运用Replace Array with Object186

Switch Statements

少用switch或case语句,利用多态模糊替代

使用Extract Method将switch语句提炼到一个独立函数或类,再以Move Method将它搬移到需要多态性的那个类里,此时决定是否使用Replace Type Code with Subclass或Replace Type Code with State/Strategy,完成继承结构后,运用Replace Conditional with Polymorphism

若只是在单一函数中有些选择事例,且不想改动 ,使用Replace Parameter with Explicit Methods285,若选择条件包含null,试试Introduce Null Object

Parallel Inheritance Hierarchies 平行继承体系

Parallel Inheritance Hierarchies 是Shotgun Surgery的特殊情况,该情况下,每当为某个类增加一个子类,必须也为另一类相应增加一个子类,消除这种重复性的一般策略:让一个继承体系的实例引用另一个继承体系的实例。若再接再厉运用Move Method142 和Move Field 146,就可以将引用端的继承体系消弭于无形

Lazy Class冗赘类

若某些子类没有太多工作,依赖,试试Collapse Hierarchy344.对于几乎没用的组件,应该以Inline Class154解决

Speculative Generality 夸夸其谈未来性

当为了扩展性并试图以各种钩子和特殊情况来处理一些非必要的事情时出现

若某个抽象类没有太大作用,运用Collapse Hierarchy,不必要的委托运用Inline Class154,若函数的某些参数未被用上,可对它实施Remove Parameter277,若函数名称带有多余的抽象意味,应该运用Rename Method273

Temporary Field 令人迷惑的暂时字段

当某个实例变量仅为某种特定情况而设,使用Extract Class149,继而将所有和这个变量相关的代码放进这个类,辅助运用Introduce Null Object当变量不合法时创建一个null对象

若类中有一个复杂算法,需要好几个变量,往往导致异味Temporary Field的出现,此时可以利用Extract Class把这些变量和其相关函数提炼到一个独立类中。提炼后的新对象将是一个函数对象

Message Chains 过度耦合的消息链

一个对象委托另一个对象,另一个对象再继而委托其他对象,职责链模式,但是若链太长则需要重构

此时使用Hide Delegate157,或者更好是能否以Extract Method将使用该对象的代码提炼到一个独立函数中,再运用Move Method把这个函数推入消息链

Middle Man 中间人

对象基本特征是封装,对外隐藏内部细节。封装往往伴随委托

面对过度委托使用Remove Middle Man160,也可以使用Inline Method把它们放进调用链,若这些Middle Man还有其他行为,可以运用Replace Delegation with Inheritance355把它变成实责对象的子类

Inappropriate Intimacy 狎昵关系

两个类过于亲密,彼此private成员难以理解,此时使用Move Method和Move Field拆散过分狎昵的类。

可以试试是否可以运用Change Bidirectional Association to Unidirectional200让其中一个类对另一个斩断关系。或者Extract Class将两者共同点提炼到一个安全地方,再或者尝试运用Hide Delegate157让另一个类传递

当需要将一个与父类耦合严重的子类独立出去,使用Replace Inheritance with Delegation352使其离开继承体系

Alternative Classes with Different Interfaces 异曲同工的类

若两个函数做同一件事情,却有着不同的签名,此时运用Rename Method根据其用途重新命名,再反复使用Move Method将某些行为移入类,直到两者的协议一致为止。若必须重复而赘余地移入代码才能完成这些,或许可运用Extract Superclass减轻异味

Incomplete Library Class 不完美的库类

若想修改库类的一两个函数,使用Introduce Foreign Method162;若向添加一大堆额外行为,运用Introduce Local Extension164

Data Class 纯稚的数据类

所谓Data Class:它们拥有一些字段,以及用于访问读写这些字段的函数,除此之外一无长物

使用Encapsulate Field206或Encapsulate Collection208进行封装。对于不该被其他类修改的字段,运用Rename Setting Method300

Refused Bequest 被拒绝的遗赠

子类不想或不需要继承一些函数和数据,

传统方式:
此时需要为这个子类新建一个兄弟类,再运用Push Down Method328和PushDown Field329 把所有用不到的函数推给这个兄弟类,如此,超类只持有所有子类共享的东西:所有超类都应该抽象的

重构:
运用Replace Inheritance with Delegation352

Comments 过多的注释

多用方法和命名替换注释

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值