重构-改善既有代码的设计,这本书是很多公司要求
JAVA
程序员必读的三本书之一(另外两本书是《Java编程思想》和《Effective Java》)
前言
看到别人的代码时感觉就像屎一样,有一种强烈的想重写的冲动,但一定要压制住这种冲动,你完全重写,可能比原来的好一点,但浪费时间不说,还有可能引入原来不存在的Bug,而且,你不一定比原来设计得好,也许原来的设计考虑到了一些你没考虑到的情况。我们写的代码,终有一天也会被别人接手,很可能到时别人会有和我们现在一样的冲动。所以,我们要做的是重构,从小范围的重构开始
为啥要重构
何为重构:
重构就是对软件内部结构的一种调整,目的是不改变软件可观察行为的前提下,提高其可理解性,降低其可修改成本
通俗的定义:
重构是优化代码结构,使其阅读性更好,扩展性更强的一种高级技术
程序是首先写给人看的,其次才是写给机器看
重构函数
1. 重复代码
编程中不要有大量的重复代码,解决办法就是去提炼到一个单独的函数中
void A() { ..... System.out.println("name" + _name);}void B() { ..... System.out.println("name" + _name);}
更改为↓
void A() { .... }void B() { .... }void printName(String name) { System.out.println("name" + name);}
2. 内联临时变量
如果你对一个变量只引用了一次,那就不妨对他进行一次重构。
int basePrice = order.basePrice();return (basePrice > 100);
更改为↓
return (order.basePrice() > 1000);
3. 尽量去掉临时变量
临时变量多了会难以维护,所以尽量去掉所使用的临时变量。
int area = _length * _width;if (area > 1000) return area * 5;else return area *4;
更改为↓
if (area() > 1000) return area() * 5;else return area() *4;int area() { return _length * _width;}
4. 引入解释性变量
跟上面那个相反,如果使用函数变得很复杂,可以考虑使用解释型变量了。
if ((platform.toUpperCase().indexOf("mac") > -1) && (brower.toUpperCase().indexOf("ie") > -1) && wasInitializes() && resize > 0) { ...... }
更改为↓
final boolean isMacOS = platform.toUpperCase().indexOf("mac") > -1;
final boolean isIEBrowser = brower.toUpperCase().indexOf("ie") > -1;
final boolean wasResized = resize > 0;
if (isMacOS && isIEBrowser && wasInitializes() && wasResized) { ...... }
5. 移除对参数的赋值
int discount (int inputVal, int quantity, int yearToDate) { if (inputVal > 50) inputVal -= 2;}
更改为↓
int discount (int inputVal, int quantity, int yearToDate) { int result = inputVal; if (result > 50) result -= 2;}
另外,函数中声明的临时变量最好只被赋值一次,如果超过一次就考虑再声明变量对其进行分解了。
一个函数也不应该太长,如果太长首先影响理解,其次包含的步骤太多会影响函数复用。做法是将里面的步骤提取为很多小函数,并且函数命名要体现出函数做了什么,清晰明了。
重构类
1. 搬移方法
每一个方法应该放在她最适合的位置,不能随便乱放,所以很多时候你需要考虑,一个方法在这里是不是最适合的。
class Class1 { aMethod();}class Class2 {}
更改为↓
class Class1 {}class Class2 { aMethod();}
2. 搬移字段
每一个字段,变量都应该放到其自己属于的类中,不能随便放,不属于这个类中的字段也需要移走。
class Class1 { aField;}class Class2 {}
更改为↓
class Class1 {}class Class2 { aField;}
3. 提炼一个新类
将不属于这个类中的字段和方法提取到一个新的类中。所以说在你写代码的时候一定要考虑这句话放这里是不是合适,有没有其他更合适的地方?
class Person {
private String name;
private String officeAreaCode; private String officeNumber;
public String getTelephoneNumber() { ..... } }
提炼到新的类中↓
class TelephoneNumber {
private String areaCode;
private String number;
public String getTelephoneNumber() { ..... } }
class Person {
private String name;
private TelephoneNumber _officeNumber; }
上面这种提炼类不一定就是合适的方式,有时候一个类不再有足够的价值的时候,我们就需要考虑提炼类的反向操作了。将类变为内联类了。
4. 内容移动
有时候每一个子类都有声明一个字段或方法,但是父类里面却没有这个字段或方法,这时候就考虑把这个字段或方法移动到父类里面,去除子类的这个字段和方法。相反情况,如果父类有一个字段或方法,但只是某个子类需要使用,就需要考虑吧这个字段或方法移动到这个特定的子类里面了。
5. 提炼接口
接口也就是协议,现在比较推崇的是面向接口编程。有时候接口将责任分离这个概念能发挥的淋漓尽致,把某些特性功能的方法提炼到接口中也是比较好的做法,这样其他想要这种功能的类只需要实现这个接口就行了。
重新组织数据
1. 自封装字段
在一个类中访问自己的字段是不是应该把字段封装起来呢?这个每个人的观点是不一样的,把字段封装起来的好处就是:如果子类复写这个字段的getter函数,那么可以在里面改变这个字段的获取结果,这样子扩展性可能会更好一点
private int _length. _width;public int area() { return _length * _width;}
更改为↓
private int _length. _width;
public int area() {
return getLength * getWidth();
}
int getLength() {
return _length;
}
int getWidth() {
return _width;
}
2. 以对象取代数值
随着开发的进行,有时候一个数据项表示不再简单了,比如刚开始只需要知道一个人的名字就行了,可是后来的需求变成了不但要知道这个人的名字还要知道这个人的电话号码,还有住址等。这个时候就需要考虑将数据变成一个对象了。
class Order { private String name;}
更改为↓
class Order { private Person person;}class Person { private String name; private String tel; private String addr;}
我们有时候需要把Person写成单利类,因为一个Person对象可以拥有很多份订单,但是这个对象只能有一个,所以Person我们应该写成单利。但有时候换成其他场景我们不能把他写成单利。这都是要视情况而定的。随意写代码要小心谨慎。
3. 常量取代数字
有时候使用一个固定的数值并不是太好,最好使用建立一个常量,取一个有意思的名字来替换这个常量。
double circleArea(int redius) { return 3.14 * redius * redius}
更改为↓
double circleArea(int redius) { return pi * redius * redius;}public final double pi = 3.14;
简化条件表达式
1. 分解条件表达式
有时候看着一个if else语句很复杂,我们就试着把他分解一下。我想不出好的例子了,就简化一下了,各位莫怪。
if (isUp(case) || isLeft(case)) num = a * b;else num = a * c;
更改为↓
if (isTrue(case))
numberB(a);
}else
numberC(a);
boolean isTrue(case) {
return isUp(case) || isLeft(case);
}
int numberB(a) {
return a + b;
}
int numberC(a) {
return a + c;
}
2. 合并条件表达式
有时我们写的多个if语句是可以合并到一起的。
double disabukutyAmount() { if (_seniority < 2) return 0; if (_monbtdiable > 12) return 0; if (_isPartyTime) retutn 0;}
更改为↓
double disablilityAmount() {
if (isNotEligibleForDisability())
return 0;
}
boolean isNotEligibleForDisability() {
return _seniority < 2 || _monbtdiable > 12 || _isPartyTime;
}
3. 合并重复的条件片段
有时候你可能会在if else 语句中写重复的语句,这时候你需要将重复的语句抽出来。
if (isSpecialDeal()) { total = price * 0.95; send();} else { total = price * 0.98; send();}
更改为↓
if (isSpecialDeal()) total = price * 0.95;else total = price * 0.98;send();
4. 以卫语句取代嵌套表达式
这个可能有点难以理解,但是我感觉用处还是比较大的,就是加入return语句去掉else语句
if (a > 0) result = a + b;else { if (b > 0) result = a + c; else { result = a + d; }}return result;
更改为↓
if (a > 0) return a + b;if (b > 0) return a + c;return a + d;
5. 以多态取代switch语句
这个我感觉很重要,用处非常多,以后你们写代码的时候只要碰到switch语句就可以考虑能不能使用面向对象的多态来替代这个switch语句呢?
int getArea() { switch (_shap) case circle: return 3.14 * _r * _r; break; case rect; return _width + _heigth;}
更改为↓
class Shap {
int getArea(){};
}
class Circle extends Shap {
int getArea() {
return 3.14 * _r * _r; break;
}
}
class Rect extends Shap {
int getArea() {
return _width + _heigth;
}
}
然后在调用的时候只需要调用Shap的getArea()方法就行了,就可以去掉switch语句了。
然后我们还可以在一个方法中引入断言,这样可以保证函数调用的安全性,让代码更加健壮。
简化函数调用
首先要说明的是函数命名一定要有意思,一定要有意思,一定要有意思,重要的事情说三遍,不要随便命名,命名一个函数或者方法的时候一定要能表明这个方法是干什么的。
将参数对象化
函数或方法最好不要有太多的参数,太长的参数难以理解,容易造成前后不一致,最好将参数对象化,传入一个对象而不是几个参数。
public void amountReceived(int start, int end);
该更为↓
public void amountReceived(DateRange range);
当然现在参数还是比较少,你可能看不出很多好处,但是一旦参数比较多的话,你就能看出好处了,将多个参数变成了一个参数。
总结
下面是我做的一些小注意点的笔记,在文章的末尾顺便粘贴一下,看看加深一下脑子的印象。
- 当添加功能变得比较难的时候,就应该重构代码,先重构代码然后添加功能,重构代码应该一小步一小步的走。
- 方法要放到合适的类里面,找到自己合适的位置
- 尽量去除多余的临时变量
- 把大方法分割为很多小方法,函数内容越小越容易管理。
- 尽量使用多态。
- 不要有过长的参数,和过大的类
- 重构时修改接口,要保留旧接口,并让旧接口调用新接口。
- 出现switch就考虑使用多态来替换了。
- 尽可能的把大函数提炼成不同的小函数
- 有时候尽量使用内联函数
- 将一些临时变量用函数代替
- 当if语句中的判断表达式很多的时候,考虑使用临时变量分解
- 临时变量不应该赋值超过一次,应该使用final表示
- 移除对参数的改变,参数传进函数中不应该被改变本身的值
- 有些难以提炼的函数可以考虑使用函数对象
- 代码尽量不要过多出现if else语句
文章摘自 http://www.jianshu.com/p/d6ff54d72afb
http://www.cnblogs.com/angeldevil/p/3601730.html
感谢两位作者,学到很多东西