一.什么是重构
名词:就是不改变系统外部行为的条件下修改程序内部代码结构,使代码易于理解和容易维护。
动词:在不改变系统外部行为的前提下,使用一系列重构手段,调整代码结构。
所以重构的目的是使代码易于理解和维护,重构可能影响系统性能(可能造成更多的查询和内存资源),
二.为什么重构
1.当你写代码的日子过的非常难过的时候,当你维护一个经过几十手程序猿写的非常烂非常烂代码的时候,他对你的生活造成了困扰
* 2、使你写的代码更容易理解,在平常开发中阅读别人代码的时候也可以运用重构手段并且根据自己的理解调整代码,代码就会越趋简洁,你就会看到设计层的东西,如果没有重构达不到这个层次,你可能只是看了代码理解了代码,然后“哦”的一声就完事了,以后你肯定会忘了这段代码的行为。重构应该随时随地进行,无须专门抽出时间进行重构,当添加新功能特别困难的时候、修改代码BUG非常苦恼的时候、代码审查的时候。*
三.重构好处
重构衍生软件设计,重构过程中会领悟和学习到设计层的东西,从而引发模式思考,重构中走向设计模式
重构使代码更容易理解,从而帮助找到bug
重构提高编程速度,从而使添加功能更容易
四.嗅出代码“坏”味道:
坏味道 | 说明 | 重构手段 |
---|---|---|
魔法变量 | 有些方法和变量的命名令人摸不着头脑 | 重新给它命名吧! |
重复的代码 | 在一个以上的地方看见相同的程序结构,那么合而为一,是比较好的选择 | 提炼函数、提炼类、塑造模版函数 |
过长的函数 | 拥有短函数的对象会活的比较好、比较长 | 每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立的函数中。尽可能起个好的方法名取代注释 |
过大类 | 如果想利用单一类做太多事情,其内往往就会出现太多的成员变量和成员对象,还有各种依赖关联关系很复杂 | 提取完成同一任务的相关变量到一个新的类、考虑把责任委托给其他类 。。。。。。。 |
过长的参数列表 | 太长的参数列表难以理解,太多参数会造成前后不一致、不易使用,而且你需要更多数据时,就不得不修改它。 | 以参数对象取代参数列表 |
散弹试修改 | 天女散花的逻辑,改某个需求的时候,要改很多类 | 将各个修改点集中起来,抽象成一个新类 |
依恋情节 | 函数对某个类的兴趣高过对自己所处类的兴趣,就会产生对这个类的依恋情节,造成紧耦合 | 判断哪个类拥有最多被此函数使用的数据,然后将这个函数和那些数据摆在一起。原则:将总是变化的东西放在一块。 |
数据泥团 | 有些数据项,喜欢成群结队地待在一块(基情)。那就把它们绑起来放在一个新的类里面 | 缩短参数列表 简化函数调用 |
switch惊悚现身 | 面向对象程序的一个最明显特征就是:少用switch语句。从本质上说,switch语句的问题在于重复 | 看到switch你就应该考虑使用多态来替换它 |
冗余类 | 你所创建的每一个类,都得有人去理解它、维护它,但一个类没有存在的必要时候,就让这个类庄严扑义吧! | 如果一个类的所得不值其身价,它就应该消失 |
五。重构第一步。如何写好方法。
- 短小
方法尽可能短小 - 只做一件事
“单一职责原则”(SRP)要求每一个类型只负责一件事情。我们将此概念扩展到方法上,就变成了:一个方法只做一件事。 - 提炼方法
当方法做了很多事情,方法太长等 - 查询取代临时变量
//重构前
double basePrice = count * price;
if(basePrice > 5000)
return basePrice * 0.95;
else
return basePrice * 0.98;
//重构后
if(basePrice() > 5000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
double basePrice(){
return count * price;
}
- 引入解释性变量
//重构前
if((platform.toUpperCase().indexOf("mat.zjtcn.sso") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0)
{
//do something
}
//重构后
final boolean isMatSys = platform.toUpperCase().indexOf("mat.zjtcn.sso") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResize = resize > 0;
if(isMatSys&& isIEBrowser && wasInitialized() && wasResize){
//do something
}
- 内联方法
//重构前
public Class A{
public void play(){
B.newInstance().start();
}
public void play2(){
B.newInstance().start();
}
public void play3(){
B.newInstance().start();
}
}
//重构后
public Class A{
public void start(){
B.newInstance().start();
}
public void play(){
start();
}
public void play2(){
start();
}
public void play3(){
start();
}
}
- 每个方法同一个抽象层级,以下是spring源码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
- 去除if嵌套
//重构前
public void login(String userName,String passWord){
if(request.getSession("user")!=null){
if(userName!=null&&passWord!=null){
shiroLogin(userName,passWord);
}
}
}
//第一步重构
public void login(String userName,String passWord){
if(request.getSession("user")==null)return ;
if(userName==null||passWord==null) return ;
shiroLogin(userName,passWord);
}
//第二步重构
public void login(String userName,String passWord){
if(!validate(userName, passWord))return ;
shiroLogin(userName,passWord);
}
public boolean validate(String userName,String passWord){
if(request.getSession("user")==null)return false;
if(userName==null||passWord==null) return false;
return true;
}
5.1重构列表
1.重新组织函数
1.1Extract Method(提炼函数)
1.2Inline Method(内联函数)
1.3Inline Temp(内联临时变量)
1.4Replace Temp with Query(以查询取代临时变量)
1.5Introduce Explaining Variable(引入解析性变量)
1.6Split Temporary Variable(分解临时变量)
1.7Remove Assignments to Parameters(移除对参数的赋值)
1.8Replace Method with Method Object(以函数对象取代函数)
1.9Substitute Algorithm(替换算法)
2.在对象之间搬移特性
2.1 Move Method(搬移函数)
2.2 Move Field(搬移字段)
2.3 Extract Class(提炼类)
2.4 Inline Class(将类内联化)
2.5 Hide Delegate(隐藏“委托关系”)
2.6 Remove Middle Man(移除中间人)
2.7 Introduce Foreign Method(引入外加函数)
2.8 Introduce Local Extension(引入本地拓展)
3.重新组织数据
3.1 Self Encapsulate Field(自封装字段)
3.2 Replace Data Value with Object(以对象取代数据值)
3.3 Change Value to Reference(将值对象改为引用对象)
3.4 Change Reference to Value(将引用对象改为值对象)
3.5 Replace Array with Object(以对象取代数组)
3.6 Duplicate Observed Data(复制“被监视数据”)
3.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
3.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
3.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)
3.10 Encapsulate Field(封装字段)
3.11 Encapsulate Collection(封装集合)
3.12 Replace Record with Data Class(以数据类取代记录)
3.13 Replace Type Code with Class(以类取代类型码)
3.14 Replace Type Code with Subclasses(以子类取代类型码)
3.15 Replace Type Code with State/Strategy(以state/Strategy取代类型码)
3.16 Replace Subclass with Fields(以字段取代子类)
4.简化条件表达式
4.1 Decompose Conditional(分解条件表达式)
4.2 Consolidate Conditional Expression(合并条件表达式)
4.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
4.4 Remove Control Flag(移除控制标记)
4.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
4.6 Replace Conditional with Polymorphism(以多态取代条件表达式)
4.7 Introduce Null Object(引入Null对象)
4.8 Introduce Assertion(引入断言)
5.简化函数调用
5.1 Rename Method(函数重命名)
5.2 Add Parameter(添加参数)
5.3 Remove Parameter(移除参数)
5.4 Separate Query from Modifier(将查询函数)
5.5 Parameterize Method(令函数携带参数)
5.6 Replace Parameter with Explicit Method(以明确函数取代参数)
5.7 Preserve Whole Object(保持对象完整)
5.8 Replace Parameter with Methods(以函数取代参数)
5.9 Introduce Parameter Object(引入参数对象)
5.10 Remove Setting Method(移除设值函数)
5.11 Hide Method(隐藏函数)
5.12 Replace Constructor with Factory Method(以工厂函数取代构造函数)
5.13 Encapsulate Downcast(封装向下转型)
5.14 Replace Error Code with Exception(以异常取代错误码)
5.15 Replace Exception with Test(以测试取代异常)
6.处理概括关系
6.1 Pull Up Field(字段上移)
6.2 Pull Up Method(函数上移)
6.3 Pull Up Constructor Body(构造函数本体上移)
6.4 Push Down Method(函数下移)
6.5 Push Down Field(字段下移)
6.6 Extract Subclass(提炼子类)
6.7 Extract Superclass(提炼超类)
6.8 Extract Interface(提炼接口)
6.9 Collapse Hierarchy(折叠继承体系)
6.10 Form Tem Plate Method(塑造模板函数)
6.11 Replace Inheritance with Delegation(以委托取代继承)
6.12 Replace Delegation with Inheritance(以继承取代委托)
六.重构第二步
测试代码。提倡小步重构小步测试,当然有足够信心下可以不进行测试,大部分情况下都是小步重构,这样几乎都是没有bug的,而且重构可以帮助找到bug、提高编程速度。因为在增加功能或者复审代码时候对代码进行了重构和理解,随着代码日趋简洁,我们会发现以前一些看不到的设计层面的东西
七.论注释
曾经我对“一份好的代码里注释至少要占到一半的份量”这样话深信不疑,我也不厌其烦的给每一个函数都加上javadoc,对此,我深感自豪;而对于别人写代码不加注释的“坏习惯”,我深表遗憾。如今,我已经懊恼不已,并且我已经开始对我的代码进行审核,再次优化。我已经开始遵守“别给糟糕的代码加注释–重新写吧”这条准则。
也许你是一个习惯好的人,会对代码进行不断的优化改进,然而你经常会把注释忽略掉,就如同下面这样:
/**
* 集合竞价.
*
* @param orderFromClient
* @return
*/
private Message callAuction(SelfOrder orderFromClient, long b, JadeInfo jadeInfo)
方法参数已经改变了,然而javadoc的注释中依然只有一个参数。
好注释
对意图进行解释,说明了使用compare的意图
/*
* 按照用户id对资金变化量进行排序,从而保证在多线程同时更新资金字段时,不发生死锁
*/
@Override
public int compareTo(VarialMoneyUser o) {
return this.getUid().compareTo(o.getUid());
}
TODO: + 说明:
如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明。
FIXME: + 说明:
如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。
XXX: + 说明:
如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明
有的时候,我们需要做一些事情,而我们暂时没有做,那么就可以使用TODO,这样IDE就会管理到这些TODO,当然还有FIXME标记,表明我们有些问题暂时不知道怎么优化、改善。
误导性注释
// 12点后设置isreload为true,重新加载配置,
private void updateReloadStatusTrue(
多余的注释
/**
* @Description: 获取指定日期的报告
*/
public Object queryReportByDate(String time);
/**
* @Title: addQReport
* @Description: 添加报告
* @param report
* @return
*/
public int addQReport(Object report);
/**
* @Title: getLastQuotationReport
* @Description: 获取指定商品上一次的行情报告
* @param map
* @return
*/
@SuppressWarnings("rawtypes")
public QuotationDailyReport getLastQuotationReport(Map map);
eclipse 重构工具
例子讲解
参考文献
《代码整洁之道》
《重构改善既有代码》