【重构-改善代码】笔记

长代码提炼

1.长函数切开,并把较小块的代码移至更合适的class内,降低代码重复量。
2.去除临时变量
3.重构可能引起的问题,性能问题
4.运用多态取代与价格相关的条件逻辑(switch)-- 配合策略模式1.

重构原则

两顶帽子:添加新功能 、 重构

  • 添加新功能:不应该修改既有代码,只添加新功能
  • 重构:不添加新功能,只管改进程序结构

重构改进软件设计
改进设计的一个重要方向:消除重复代码(Duplicate code)
重构让代码更容易理解
重构助你找到bug
重构提高编程速度

重构原则

  • 三次法则:第三次做就应该重构
  • 添加功能时一并重构
  • 修补错误时一并重构
  • 复审代码时一并重构

重构的难题

  • 数据库
  • 修改接口
  • 难以通过重构手法完成的设计改动
  • 何时不该重构(重构不如重写的时候;项目临近期限)

重构与设计

预先设计节省回头工

重构与性能

编写快速软件

  1. 时间预算法 time budgeting:通常只应用于性能要求极高的实时系统
  2. 持续关切法 constant attention:设法保持系统的高性能
  3. 良好的分解方式建造自己的程序,不对性能投入任何关切,直至进入性能优化阶段

代码的坏味道

重复代码(Duplicated code)

  • 同一个class内的两个函数含有相同表达式 – 提炼重复代码
  • 两个毫不相关的class 出现重复代码,提炼到独立class

过长函数(Long method)

每当感觉需要用注释说明点什么,就把需要说明的东西写进独立的函数
条件式与循环常常也是提炼的信号

过大类(Large class)

过长参数列(Long Parameter List)

发散式变化(Divergent Change)

如果某个class经常因为不同的原因在不同方向上变化,拆分更好

霰弹式修改(Shotgun Surgery)

遇到变化在许多不同classes中修改,把需要修改的代码放到一个class 中

  • Divergent Change 指一个class受多种变化的影响
  • Shotgun Surgery指一种变化引发多个classes相应修改,

依恋情节(Feature Envy)

数据泥团(Data Clumps)

删掉众多数据中的一笔,其他数据是否因此失去意义,如果不再有意义,应该为他们产生一个新对象

基本型别偏执(Primitive Obsession)

  • 结构型:允许将数据组织成有意义形式
  • 基本型:构成结构型的积木块

swith惊悚现身(Switch Statement)

多态解决
示例:

//原代码
public boolean isNeedKnife(Object param) {
    if (param is Apple) {
      return false;
    } else if (param is Banana ) {
      return false;
    } else if (param is Watermelon) {
      return true;
    }
  }


//优化
public abstract class Fruits {
  public boolean isNeedKnife() {
    return false;
  }
}

public class Watermelon extends Fruits{

  @Override
  public boolean isNeedKnife() {
    return true;
  }
}

shop.fruits.isNeedKnife()

平行继承体系(Parallel Inheritance Hierarchies)

每当为一个类增加一个subclass , 必须也为另一个class 相应增加一个subclass
消除策略:让一个继承体系的实体指涉(refer to)另一个继承体系的实体

冗赘类(Lazy class)

夸夸其谈未来性(Speulative Generality)

令人迷惑的暂时值域(Temporary Field)

过度耦合的消息链(Message Chain)

中间转手人(Middle Man)

某个class接口有一半的函数都委托给其他class - 过度运用

狎昵关系(Inappropriate Intimacy)

拆分

异曲同工的类(Alternative Classes with Different Interfaces)

重命名
移动方法知道两者协议一致
如果必须重复,提取super类

不完美的程序库类(Incomplete Library Class)

纯稚的数据类(Data Class)

定义:拥有一些值域,以及用于访问(读写)这些值域的函数
访问权限

被拒绝的捐赠(Refused Bequest)

subclasses应该继承superclass的函数和数据, 但有些他们不想继承

过多的注释(Comments)

不好的代码,多余的注释

构筑测试体系

重构的前提,可靠的测试环境

自我测试代码

每一个class 都应该有一个测试函数,并用它来测试自己的这个class

JUnit测试框架

重构名录

重构名录格式

包含5部分

  • 名称
  • 概要
  • 动机
  • 做法
  • 范例

寻找引用点

重新组织你的函数

Extract Method

有一段代码可以被组织独立出来 - 很好的命名

  • 动机:过长的函数独立出来
  • 做法:创造新函数,命名

Inline Method

  • 动机:你手上有一群组织不甚合理的函数,可以将他们都inline 到一个大型函数中,再从中提炼出组织合理的小型函数
  • 作法:①检查函数,确定他不具多态性;②找到函数被调用点;③将这个函数的所有被调用点都替换为函数本体(代码);④编译测试;⑤删除该函数的定义

Inline Temp

有一个临时变量,只被一个简单表达式赋值一次 ,而他妨碍了其他重构手法

//原
double basePrice = anOrder.basePrice();
return (basePrice > 1000)

//重构
return (anOrder.basePrice() > 1000)

Replace Temp with Query

程序以一个临时变量保存某个表达式的运算结果
将这个表达式提炼到一个独立的函数

//原
double getPrice(){
	int basePrice = quantity * itemPrice;
	double discountFactor;
	if (basePrice > 1000){
		discountFactor = 0.95;
	}else {
		discountFactor = 0.98;
	}
	return basePrice * discountFactor;
}
//重构
double getPrice(){
	return basePrice() * discountFactor();
}
private double discountFactor(){
	if (basePrice() > 1000) {
		return 0.95;
	}else {
		return 0.98;
	}
}
private int basePrice(){
	return quanlity * itemPrice;
}

Introduce Explaining Variable

将复杂的表达式(或者其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

条件逻辑中,如果有个特别有价值,提炼出来
示例:

double price(){
	return quanlity *  itemPrice - Math.max(0,quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
}

//重构
double price(){
	final double basePrice = quantity * itemPrice;
	return basePrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100.0);
}
//。。。。。
//全部提炼重构结果
double price(){
	final double basePrice = quantity * itemPrice;
	final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05;
	final double shipping = Math.min(basePrice * 0.01, 100.0);
	return basePrice - quantityDiscount + shipping;
}
//全部提炼出方法
double price(){
	return basePrice() - quantityDiscount() + shipping();
}
private double quantityDiscount(){
	return Math.max(0, quantity - 500) * itemPrice * 0.05;
}
private double shipping(){
	return Math.min(basePrice * 0.01, 100.0);
}
private double basePrice(){
	return quantity * itemPrice;
}

Split Temporary Variable(剖解临时变量)

有某个临时变量被赋值超过一次,既不是循环变量,也不是一个集用临时变量,针对每一次赋值,创造一个独立的,对应的临时变量。

Remove Assignments to Parameters

对一个参数进行赋值动作
以一个临时变量取代该参数位置

int discount(int inputVal, int quantity, int yearToDate){
	if(intputVal > 50) inputVal-= 2;
	if(quantity > 100) inputVal-= 1;
	if(yearToDate > 10000) inputVal-= 4;
	return inputVal;
}
//重构
int discount(int inputVal, int quantity, int yearToDate){
	int result = inputVal;
	if(intputVal > 50) result -= 2;
	if(quantity > 100) result -= 1;
	if(yearToDate > 10000) result -= 4;
	return result ;
}

Replace Method with Method Object

有个大型函数,其中对局部变量的使用,无法提取方法
将这个函数放进一个单独对象中,如此一来局部变量成了对象内的值域,然后可以在同一个对象中将这个大型函数分解为数个小型函数。

Substitute Algorithm(替换你的方法)

把某个算法替换为另一个更清晰的算法
将函数本体替换为另一个算法

示例

String foundPerson(String[] people){
	for(int i = 0; i < people.length; i++){
		if (people[i].equals("Don")){
			return "Don";
		}
		if (people[i].equals("Join")){
			return "Join";
		}
		if (people[i].equals("Kent")){
			return "Kent";
		}
	}
}

//重构
String foundPerson(String[] people){
	List candidates = Arrays.asList(new String[]{"Don", "John", "Kent"});

	for(int i = 0; i < people.length; i++){
		if (int i = 0; i < people.length; i++){
			return people[i];
		}
	}
	return "";
}

在对象之间搬移特性

class 往往会因为承担过多的责任而变得臃肿不堪,将一部分责任分离出去,

Move Method搬移函数

程序中,有个函数与其所驻class 之外的另一个class 进行更多的交流,调用后者或被后者调用。
在该函数最常引用的class 中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。

  • 动机:如果一个class有太多行为,或如果一个class与另一个class有太多合作而形成高度耦合,就会搬移函数。

Move Field搬移值域

程序中,某个filed被其所驻class 之外的另一个class 更多地用到
在target class 中建立一个new field,修改 source filed 的所有用户,令他们改用new field。

Extract Class 提炼类

某个class 做了应该由两个classes 做的事
建立一个新的class 将相关的值域和函数从旧的class 搬移到新的class
也是改善并发程序的常用技术,可以为提炼后的两个class分别加锁
也存在危险,面临事务问题

Inline Class 将类内联化

某个class 没有做太多事情
将class所有特性搬移到另一个class 中,然后移除原class

Hide Delegate隐藏委托关系

客户直接调用其server object的delegate class
在serve端建立客户所需的所有函数,用以隐藏委托关系(工作原理)

Remove Middle Man(移除中间人)

某个class做了过多的简单委托动作
让客户直接调用delegate(委托类)

Introduce Foreign Method引入外加函数

所使用的server class 需要一个额外函数,但你无法修改这个class
在client class 中建立一个函数,并以一个server class 实体作为第一引数

Introduce Local Extension引入本地扩展

使用的server class 需要一些额外函数,但你无法修改这个class
建立一个新的class 使它包含这些额外函数,让这个扩展品成为source class 的subclass 或wrapper

重新组织数据

自封装值域

直接访问一个值域,但与值域之间的耦合关系逐渐变得笨拙
为这个值域建立取值/设值函数(getting/setting methods),并且只以这些函数来访问值域
示例

class IntRange{
	private int low, high;

	boolean includes(int arg){
		return arg >= low && arg <= high;
	}
	void grow(int factor){
		high = high * factor;
	}
	IntRange(int low, int high) {
		low = low;
		high = high;
	}
}
//封装
class IntRange{

	boolean includes(int arg){
		return arg >= getLow() && arg <= getHigh();
	}
	
	void grow(int factor){
		setHigh(getHigh() * factor);
	}
	
	private int low, high;

	int  getLow(){
		return low;
	}
	
	int  getHigh(){
		return high;
	}
	
	void setLow(int arg){
		low = arg;
	}
	void setHigh(int arg){
		high = arg
	}
}

Repalce Data Value with Object 以对象取代数据值

有一笔数据项(data item)需要额外的数据和行为。
将这笔数据项变成一个对象

将实值对象改为引用对象

有一个class,衍生出许多相等实体,希望将他们替换为单对象。
将这个实值对象变为一个引用对象

将引用对象改为实值对象

有一个引用对象,很小且不可变,而且不易管理
将他变为一个value object实值对象

以对象取代数组

有一个数组,其中的元素各自代表不同的东西
以对象替换数组,对于数组中的每个元素,以一个值域表示之。

复制被监视数据

有一些domain data置身于GUI控件中,而domain method 需要访问之
将该笔数据拷贝到一个domain object 中,建立一个observe 模式,用以对domain object 内的重复数据进行同步控制。

将单向关联改为双向

两个classes都需要使用对方特性,但其间只有一条单向连接
添加一个反向指针,并使修改函数能够同时更新两条连接

将双向关联改为单向

两个classes之间有双向关联,但其中一个class如今不再需要另一个class的特性
去除不必要的关联

以符号常量/字面常量取代魔法数

有一个字面数值,带有特别含义
创造一个常量,根据其意义为他命名,并将上述的字面数值替换为这个常量

封装值域

你的class中存在一个public 值域
将他声明为private 并提供相应的访问函数

封装群集

有个函数返回一个群集
让这个函数返回该群集的一个只读映件,并在这个class中提供添加/移除群集元素函数

以数据类取代记录

需要面对传统编程环境中record structure(记录结构)
为该记录创建一个哑数据对象

以类取代型别码

class中有一个数值型别码,但它并不影响class的行为
以一个新的class替换该数值型别码

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值