重构是对软件内部结构的一种调整,目的是在不改变外部行为的前提下,提高其可理解性(可读性),降低其修改成本。
重构是严谨、有序地对完成的代码进行整理从而减少出错的一种方法(小步前进)。
重构的实质是对完成代码的设计进行改进。
我的理解:重构初级对方法的抽象,重构高级对类的抽象,对场景的抽象。
程序员对代码所做的为了满足短期利益代码改动,或再没有完全清楚增个架构下的改动,都很容易使代码失去它的清晰结构,偏离需求或设计。而这些改动的积累很容易使代码偏离它原先设计的初衷而变得不可立即和无法维护。
重构则帮助重新组织代码,重新清晰的体现结构和进一步改进设计。
容易理解的代码可以很容易的维护和做进一步的开发。即使对写这些代码的程序员本身,容易理解代码也可以帮助容易地做修改。
程序代码也是文档。而代码首先是写给人看的,后才是给计算机看的。
重构是一个codereview和反馈的过程。在另一个时段重新审视自己或别人代码,可以更容易的发现问题和加深对代码的理解。
重构是一个良好的软件开发习惯。
重构对设计和代码的改进,都可以有效的提高开发速度。
好的设计和代码质量实体提高开发速度的关键。在一个有缺陷的设计和混乱代码基础上的开发,即使表面上进度较快,但本质是延后对设计缺陷的发现和对错误的修改,也就是延后了开发风险,最终要在开发的后期付出更多的时间和代价。
为了增加一个新的功能,程序员需要首先读懂现有的代码。
为了修复一个Bug,程序员需要读懂现有的代码。
永远不要做Last-Minute-Change。推迟重构,但不可以忽略。
一个Task的计划是3天,如果为了重构,需要更多的时间( 2天或更多)。推迟重构。可以把这个重构作为一个新的Task。
读懂代码(包括测试例子代码)
重构
运行所有的Unit Tests
读懂代码
应用重构工具进行重构(如Eclipse)
1)安全第一,尤其是关键部分,应先做出一demo,各环节正常测试运行后无缝对接。
周五和下班前提交更要小心,更改后的代码一定要及时放cvs,并在提交时注明修改的地方或原因,告同组的项目组员。
2)重构要先有接口测试,重构后必须保证通过接口测试,因为现在的系统是一个正常运行的系统,如果把未测试通过的代码放服务器,势必会给公司带来损失。
所以要求:小步进行,意思是每做改动,均要测试和可回朔。
3)重构完成后,向服务器提交代码时,需采用更保险的方法,将原文件备份为以***.class.20060809.jeff的文件,不能简单的覆盖,否则那是很危险的。
4)遇正常工作任务时先做正常任务,完成后继续重构,不能以重构为借口推工作。
5)这次总结的方式方法,经验形成文档,要求以后在工作中是随时做的,当增加功能时,修改bug时或复审代码时都应该想到是否将原有的代码重构,以提高系统的可复用性和可扩展性。
1)名字重构,修改原有不合理的名字。
2)包,结构重构,重整原有的结构,合并和细分。
3)方法体重构,长方法抽取,方法公用。
4)在正确的类里计算值,不要在别的类里计算和写逻辑有本类的东西。
5)配置文件的重整
6)编码效率(系统性能)的重构:
a) 循环内不许声明变量
b) 尽可能不要在循环内做判断
7)非有用文件的及时删除
8)log的整理
9)文档的整理
10)异常的处理
11)参考java开发技术规范
l 重复的代码(DuplicatedCode)
l 过长的函数(LongMethod)
l 过大类(LargeClass)
l 过长的参数列(LongParameter List)
l 发散式变化(DivergentChange)
l 霰弹式修改(ShotgunSurgery)
l 依恋情结(FeatureEnvy)
l 数据泥团(DataClumps)
l 基本型别偏执(PrimitiveObsession)
l Switch语句(Swtich Statements)
l 平行继承体系(ParallelInheritance Hierarchies)
l 冗赘类(LazyClass)
l 夸夸其谈未来性(SpeculativeGenerality)
l 令人迷惑的暂时值域(TemporaryField)
l 过度遇合的消息链(MessageChains)
l 中间转手人(MiddleMan)
l 狎昵关系(InappropriateIntimacy)
l 异曲同工的类(AlternativeClasses with Different Interfaces)
l 不完善的程序库类(IncompleteLibrary Class)
l 纯粹的数据类(DataClass)
l 被拒绝的遗赠(RefusedBequest)
l 过多的注释(Comments)
重复代码是最常见的异味,往往是由于Copy& Paste 造成的。
重构方法:
u 重复代码在同一个类中的不同方法中,则直接提炼为一个方法
u 如果重复代码在两个互为兄弟的子类中,则将重复的代码提到父类中
u 如果代码类似,则将相同部分构成单独函数,或者用 Template Method 设计模式
u 重复代码出现在不相干的类中,则将代码提炼成函数或者放在独立的类中
是面向结构程序开发带来的 “后遗症”,过长的函数降低可读性。
重构方法:
u 将独立的功能提炼成新函数
3. 过大类(Large Class) ☆☆☆☆
过大的类使得责任不清晰。
重构方法
u 将过大类的功能拆分成多个功能单一的小类
u 拆分功能的时候要保证每个方法内调用的是同一层级的方法。
4. 过长的参数列(Long ParameterList) ☆☆☆☆
过长的参数列难以理解,而且容易传错参数。
重构方法:
u 将参数列表用参数对象替换
5. 发散式变化(DivergentChange) ☆☆☆
一个类由于不同的原因而被修改。
重构方法:
u 将类拆分成多个,每个类只因为一种变化而修改
6. 霰弹式修改(ShotgunSurgery) ☆☆☆☆
与发散式变化相反,遇到变化时需要修改许多不同的类。
重构方法:
u 将类似的功能放到一个类中
7. 依恋情结(Feature Envy) ☆☆☆
函数对某个类的兴趣高过对自己所处的类,通常是为了取其他类中的数据。
重构方法:
u 将函数部分功能移到它感兴趣的类中
8. 数据泥团(Data Clumps) ☆☆☆
在多个地方看到相同的数据项。例如:
多个类中相同的变量,多个函数中相同的参数列表,并且这些数据总是一起出现。
重构方法:
u 将这些数据项放到独立的类中
9. 分支语句(SwtichStatements) ☆☆☆☆
大量的分支、条件语句导致过长的函数,并且可读性差。
重构方法:
u 应将它变成子类或者使用 State和 Strategy模式
10. 过度耦合的消息链(MessageChains) ☆☆☆
一个对象请求另一个对象,后者又请求另外的对象,然后继续。。。。,形成耦合的消息链。
重构方法:
u 公布委托对象供调用
11. 过多的注释(Comments) ☆☆
代码有着长长的注释,但注释之所以多是因为代码很糟糕。
重构方法:
u 先重构代码,再写上必要的注释
12. 夸夸其谈未来性(SpeculativeGenerality) ☆☆☆
现在用不到,觉得未来可以用到的代码,要警惕。
重构方法:
u 将用不上的代码去掉
13. 纯粹的数据类(Data Class) ☆☆☆
将数据类中数据以Public方式公布,没对数据访问进行保护。
重构方法:
u 将数据封装起来,提供Get/Set方法
坏味道 |
特征 |
情况及处理方式 |
目标 |
|
重复代码 |
1.重复的表达式 |
同一个类的两个函数有相同表达式 |
重复代码提取为方法 |
相同表达式只在一个类的一个方法出现,供其他方法调用 |
2.不同算法做相同的事 |
兄弟类含有相同表达式 |
重复代码提取为方法 |
||
3.类似代码 |
提升方法到父类 |
|||
|
不相干类含有相同代码 |
提取为独立类供调用 |
||
过长函数 |
1.代码前面有注释 |
|
提取方法 |
每个方法只做一件事,方法要定义完善、命名准确 |
2.条件表达式 |
||||
3.循环 |
||||
过大的类 |
1.一个类中有太多实例变量 |
部分字段之间相关性高 |
相关的字段和方法提取为类 |
每个类负责一组具有内在的相互关联的任务 |
2.一个类中有太多代码 |
某些字段和方法只被某些实例用到 |
这些字段和方法移到子类中 |
||
过长参数列 |
1.参数列过长 |
方法可以通过其他方式获取该参数 |
让参数接受者自行获取该参数 |
只需要传给函数足够的、让其可以从中获取自己需要的东西就行了 |
2.参数列变化频繁 |
同一对象的若干属性作为参数 |
在不使依赖恶化的情况下,使用整个对象作为参数 |
||
|
被调用函数使用了另一个对象的很多属性 |
将方法移动到该对象中 |
||
|
某些数据缺乏归属对象 |
首先创建对象 |
||
发散式变化 |
一个类受多种变化的影响 |
类经常因为不同的原因在不同的方向上发生变化 |
将特定原因造成的所有变化提取为一个新类 |
针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化 |
散弹式修改 |
一种变化引发多个类的修改 |
某种变化需要在许多不同的类中做出小修改 |
把所有需要修改的代码放进同一个类中 |
针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化 |
依恋情结 |
一个函数使用其他类属性比使用自身类属性还要多 |
某个函数从另一个对象调用了几乎半打的取值函数 |
将依恋代码提取为单独方法,移动到另一对象 |
将数据和对数据的操作行为包装在一起 |
数据泥团 |
同时使用的相关数据并未以类的方式组织 |
|
先将字段提取为类,再缩减函数签名中的参数 |
总是绑在一起的数据应该拥有属于它们自己的对象 |
1.两个类中相同的字段 |
||||
2.许多函数中相同的参数 |
||||
基本类型偏执 |
过多使用基本类型 |
总是被放在一起的基本类型字段 |
提取类 |
将单独存在的数据值转换为对象 |
参数列中有基本类型 |
提取参数对象 |
|||
数组中容纳了不同的对象,需要从数组中挑选数据 |
用对象取代数组 |
|||
基本数据是类型码 |
使用类替换类型码 |
|||
带条件表达式的类型码 |
使用继承类替换类型码 |
|||
Switch语句 |
相同的switch、case语句散布于不同地方 |
根据类型码进行选择的switch |
使用多态替代switch |
避免到处做相同的修改 |
单一函数中有switch |
使用显式的方法取代参数 |
|||
平行继承体系 |
1.为某个类增加子类时,必须为另一个类增加子类 |
|
一个继承体系中的实例引用另一个继承体系中的实例,然后迁移成员 |
避免到处做相同的修改 |
2.某个继承体系类名前缀和另一个继承体系类名前缀相同 |
||||
冗赘类 |
类无所事事 |
父类和子类无太大差别 |
将它们合为一体 |
|
某个类没有做太多事情 |
将这个类所有成员移到另一个类中,删除它 |
|||
夸夸其谈未来性 |
|
某个抽象类没有太大作用 |
将父子类合并 |
|
不必要的委托 |
将这个类所有成员移到另一个类中,删除它 |
|||
函数的某些参数未用上 |
移除参数 |
|||
函数名称带有多余的抽象意味 |
重命名函数名 |
|||
函数只被测试方法调用 |
连同测试代码一并删除 |
|||
令人迷惑的暂时字段 |
1.某个实例字段仅为某种情况而设 |
|
提取单独的类,封装相关代码 |
|
2.某些实例字段仅为某个函数的复杂算法少传参数而设 |
||||
过度耦合的消息链 |
一长串的getThis或临时变量 |
客户类通过一个委托类来取得另一个对象 |
隐藏委托 |
消除耦合 |
中间人 |
某个类接口有大量的函数都委托给其他类,过度使用委托 |
有一半的函数 |
移除中间人 |
|
少数几个函数 |
直接调用 |
|||
中间人还有其他行为 |
让委托类继承受托类 |
|||
狎昵关系 |
某个类需要了解另一个类的私有成员 |
子类过分了解超类 |
将继承改为委托,把子类从继承体系移出 |
封装 |
类之间双向关联 |
去掉不必要的关联 |
|||
类之间有共同点 |
提取新类 |
|||
异曲同工的类 |
两个函数做同一件事,但是签名不同 |
|
合并 |
|
不完美的类库 |
类库函数构造的不够好,又不能修改它们 |
想修改一两个函数 |
在调用类增加函数 |
|
想添加一大堆额外行为 |
使用子类或包装类 |
|||
幼稚的数据类 |
某个类除了字段,就是字段访问器、设置器 |
|
1.用访问器取代public字段 |
封装 |
2.恰当封装集合 |
||||
3.移除不需要的设置器 |
||||
4.搬移对访问器、设置器调用方法到此类 |
||||
5.隐藏访问器、设置器 |
||||
被拒绝的馈赠 |
派生类仅使用了基类很少一部分成员函数 |
子类拒绝继承超类接口 |
使用委托替代继承 |
|
过多的注释 |
一段代码有着长长的注释 |
|
消除各种坏味道 |
|
if ( flag == 1 ){ return true; } else{ return false; }
|
return flag == 1;
|
if ( "Male".equals(gender) ) { return "Mr."; } else{ return "Mrs."; }
|
return "Male".equals(gender) ? "Mr." : "Mrs.";
|
for (int i = 0; i < 12; i++) { this.getDays(i); //skip... }
|
int YEAR_MONTHS = 12; for (int month = 0; month < Month.YEAR_MONTHS; month++) { this.getDays(month); //skip... }
|
public boolean isStartAfter(Date date) { Calendar calendar = BusinessCalendar.getCalendar(); calendar.setTime(date); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE);
return ( (hour || ( (hour==fromHour) && (minute<=fromMinute) ) ); }
public boolean includes(Date date) { Calendar calendar = BusinessCalendar.getCalendar(); calendar.setTime(date); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE);
return ( ( (fromHour || ( (fromHour==hour) && (fromMinute<=minute) ) ) && ( (hour || ( (hour==toHour) && (minute<=toMinute) ) ) ); }
|
private boolean tailGreatHead(int headHour, int headMinute, int tailHour, int tailMinute, boolean includeEqual) { boolean tailGreatHeadHour = (headHour < tailHour); boolean tailEqualHeadHour = (headHour == tailHour); boolean tailGreatHeadMinute = (headMinute < tailMinute); boolean tailEqualHeadMinute = (headMinute == tailMinute);
boolean tailGreatEqualHeadMinute = tailGreatHeadMinute || includeEqual && tailEqualHeadMinute;
return (tailGreatHeadHour || (tailEqualHeadHour && tailGreatEqualHeadMinute)); }
private boolean tailGreatHead(int headHour, int headMinute, int tailHour, int tailMinute) { return tailGreatHead(headHour, headMinute, tailHour, tailMinute, false); }
private boolean tailGreatEqualHead(int headHour, int headMinute, int tailHour, int tailMinute) { return tailGreatHead(headHour, headMinute, tailHour, tailMinute, true); }
public boolean isStartAfter(Date date) { Calendar calendar = BusinessCalendar.getCalendar(); calendar.setTime(date); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE);
return this.tailGreatEqualHead(hour, minute, fromHour, fromMinute); }
public boolean includes(Date date) { Calendar calendar = BusinessCalendar.getCalendar(); calendar.setTime(date); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE);
return this.tailGreatEqualHead(fromHour, fromMinute, hour, minute) && this.tailGreatEqualHead(hour, minute, toHour, toMinute); }
|
int count = 0; if(taskList != null && !taskList.isEmpty()){
//skip...
return count; } else { return count; }
|
int count = 0; if(taskList == null || taskList.isEmpty()){ return 0; }
//skip...
return count;
|
public boolean contain(int year, Month month, int day) { boolean found = false; for (IPolyDate date : dateList) { if (date.same(year, month.getMonth(), day)) { found = true; break; } }
return found; }
|
public boolean contain(int year, Month month, int day) { for (IPolyDate date : dateList) { if (date.same(year, month.getMonth(), day)) { return true; } }
return false; }
|
public void testGetIntPart() throws Exception { assertEquals("0", digitTransform.getIntPart("0.01"); assertEquals("1", digitTransform.getIntPart("1.2"); assertEquals("1234", digitTransform.getIntPart("1234"); assertEquals("1", digitTransform.getIntPart("1.01"); assertEquals("0", digitTransform.getIntPart("0.01"); assertEquals("11111", digitTransform.getIntPart("11111"); assertEquals("1000", digitTransform.getIntPart("1000.11"); }
|
public void testGetIntPart() throws Exception { String[][] cases = new String[][] { { "0.01", "0" }, { "1.2", "1" }, { "1234", "1234" }, { "1.01", "1" }, { "0", "0" }, { "11111", "11111" }, { "1000.11", "1000" } };
for (int i = 0, len = cases.length; i < len; i++) { assertEquals(cases[i][1], digitTransform.getIntPart(cases[i][0])); } }
|
public class ExceedMaxWeekIndexOfMonthException extends IndexOutOfBoundsException {
private static final long serialVersionUID = 1L;
public ExceedMaxWeekIndexOfMonthException(String message) { super(message); }
}
|
public class ExceedMaxWeekIndexOfMonthException extends IndexOutOfBoundsException {
private static final long serialVersionUID = 1L;
public ExceedMaxWeekIndexOfMonthException(int index, int maxCountOfWeekDay, Month month) { super(formatMessage(index, maxCountOfWeekDay, month)); }
private static String formatMessage(int index, int maxCountOfWeekDay, Month month) { return "Arguement index[" + index + "] exceeds max week index[" + maxCountOfWeekDay + "] of month[" + month.toString() + "]."; }
}
|
public int getYear() { Calendar date = this.getCalendarDate();
return date.get(Calendar.YEAR); }
public int getMonth() { Calendar date = this.getCalendarDate();
return date.get(Calendar.MONTH); }
public int getDay() { Calendar date = this.getCalendarDate();
return date.get(Calendar.DAY_OF_MONTH); }
public int getHour() { Calendar date = this.getCalendarDate();
return date.get(Calendar.HOUR_OF_DAY); }
public int getMinute() { Calendar date = this.getCalendarDate();
return date.get(Calendar.MINUTE); }
public int getSecond() { Calendar date = this.getCalendarDate();
return date.get(Calendar.SECOND); }
public int getMillisSecond() { Calendar date = this.getCalendarDate();
return date.get(Calendar.MILLISECOND); }
|
private int get(int field) { Calendar date = this.getCalendarDate();
return date.get(field); }
public int getYear() { return this.get(Calendar.YEAR); }
public int getMonth() { return this.get(Calendar.MONTH); }
public int getDay() { return this.get(Calendar.DAY_OF_MONTH); }
public int getHour() { return this.get(Calendar.HOUR_OF_DAY); }
public int getMinute() { return this.get(Calendar.MINUTE); }
public int getSecond() { return this.get(Calendar.SECOND); }
public int getMillisSecond() { return this.get(Calendar.MILLISECOND); }
|
protected String[] getConfigLocations() { String[] baseCfgs = this.getBaseCfgs(); String[] extra = this.getExtraCfgs(); if (extra != null && extra.length > 0) { int baseCfgLen = baseCfgs.length; int extraLen = extra.length; String[] cfgLocations = new String[baseCfgLen + extraLen]; for(int i = 0; i < baseCfgLen; i++){ cfgLocations[i] = baseCfgs[i]; }
for(int i = 0; i < extraLen; i++){ cfgLocations[baseCfgLen + i] = extra[i]; }
return cfgLocations; } else { return baseCfgs; } } |
protected String[] getConfigLocations() { String[] baseCfgs = this.getBaseCfgs(); String[] extra = this.getExtraCfgs(); if (extra != null && extra.length > 0) { int baseCfgLen = baseCfgs.length; int extraLen = extra.length; String[] cfgLocations = new String[baseCfgLen + extraLen; System.arraycopy(baseCfgs, 0, cfgLocations, 0, baseCfgLen); System.arraycopy(extra, 0, cfgLocations, baseCfgLen, extraLen;
return cfgLocations; } else { return baseCfgs; } }
|