重构总结回顾

  该文章是对《重构-改善既有代码的设计》一书的总结回顾,以时刻鞭策自己不断提高代码质量,同时供大家借鉴改善。废话不多说,马上开始。

为啥要重构

  何为重构?书中的定义是:“重构就是对软件内部结构的一种调整,目的是不改变软件可观察行为的前提下,提高其可理解性,降低其可修改成本。”太过专业的解释了,通俗的来讲就是:“重构是优化代码结构,使其阅读性更好,扩展性更强的一种高级技术”。通过重构,我们使得代码更加具有扩展性可维护性。
  软件开发中,随着功能的加入,程序将慢慢失去原来的结构。有些人贸然加入功能的实现代码,但却没有理解原来程序的结构和考虑代码的扩展性,程序的可读性也非常低,随着代码数量越来越多,如果不进行重构的话,程序就会越来越难以维护,导致最后放弃这个程序。我们需要重构让程序避免这样的结果。

多写测试

  该书作者提倡大家多写测试代码,每完成一个功能或者做完一次重构就进行单元测试,多写测试代码不断检查自己代码的健壮性。然后书中列举了大量不好的代码示例,提醒我们在实际编程中尽量避免,接下来我们列举一下常见的需要重构的情况以及如何进行重构。

一.重构函数

重复代码

  这种情况应该很多人都遇到过,编程中不要有大量的重复代码,解决办法就是去提炼到一个单独的函数中。例如下面的例子:

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);
}

内联临时变量

  顾名思义,如果你对一个变量只引用了一次,那就不妨对他进行一次重构。

int basePrice = order.basePrice();
return (basePrice > 100);

  更改为:

return (order.basePrice() > 1000);

尽量去掉临时变量

  临时变量多了会难以维护并对内存造成不必要开支,所以尽量去掉所使用的临时变量。

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;
}

引入解释性变量

  跟上面的相反,如果使用函数变得很复杂,使人不明所以,可以考虑使用解释型变量了。

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) {
    ......
}

移除对参数的赋值

  在函数中传入参数时,应该尽量避免对其进行更改。

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;
}

  另外,函数中声明的临时变量最好只被赋值一次,如果超过一次就考虑再声明变量对其进行分解了。
  一个函数也不应该太长,如果太长首先影响理解,其次包含的步骤太多会影响函数复用。做法是将里面的步骤提取为很多小函数,并且函数命名要体现出函数做了什么,清晰明了。

二.重构类

搬移方法

  每一个放大都应该放在最合适的位置,不能随意乱放,所以很多时候,你需要考虑某个方法放在某处是否合适,最适合它的位置应该是哪里。此处的策略一般是由方法的功能所决定,该方法是为专一的类服务还是为基础业务服务。

class Class1 {
    aMethod();
}
class Class2 {
}

  更改为:

class Class1 {
}
class Class2 {
    aMethod();
}

搬移字段

  跟上面的方法一样,每一个字段、变量都应该放在其自己属于的类中,不属于这个类中的字段也需要及时移走。

class Class1 {
    aField;
}
class Class2 {
}

  更改为:

class Class1 {
}
class Class2 {
    aField;
}

提炼一个新类

  将不属于这个类中的字段和方法提取到一个新的类中。所以说在你写代码的时候一定要考虑这句话放这里是不是合适,有没有其他更合适的地方?

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;
}

  上面这种提炼类不一定就是合适的方式,有时候一个类不再有足够的价值的时候,我们就需要考虑提炼类的反向操作了。将类变为内联类了。

内容移动

  有时候每一个子类都有声明一个字段或方法,但是父类里面却没有这个字段或方法,这时候就考虑把这个字段或方法移动到父类里面,去除子类的这个字段和方法。相反情况,如果父类有一个字段或方法,但只是某个子类需要使用,就需要考虑吧这个字段或方法移动到这个特定的子类里面了。

提炼接口

  接口也就是协议,现在比较推崇的是面向接口编程。有时候接口将责任分离这个概念能发挥的淋漓尽致,把某些特性功能的方法提炼到接口中也是比较好的做法,这样其他想要这种功能的类只需要实现这个接口就行了。

三.重新组织数据

自封装字段

  在一个类中访问自己的字段是不是应该把字段封装起来呢?这个每个人的观点是不一样的,把字段封装起来的好处就是:如果子类复写这个字段的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};

以对象取代数值

  随着开发的进行,有时候一个数据项表示不再简单了,比如刚开始只需要知道一个人的名字就行了,可是后来的需求变成了不但要知道这个人的名字还要知道这个人的电话号码,还有住址等。这个时候就需要考虑将数据变成一个对象了。

class Order {
    private String name;
}

  更改为:

class Order {
    private Person person;
}
class Person {
    private String name;
    private String tel;
    private String addr;
}

  我们有时候需要把Person写成单利类,因为一个Person对象可以拥有很多份订单,但是这个对象只能有一个,所以Person我们应该写成单利。但有时候换成其他场景我们不能把他写成单利。这都是要视情况而定的。随意写代码要小心谨慎。

四.简化条件表达式

分解条件表达式

  有时候看着一个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;
}

  当然实际情况可能复杂的多,这样的重构才显得有意思,这里只是让大家脑子里有一个这样的思想,以后遇见这样的情况能想起来可以这样子重构。

合并条件表达式

  有时我们写的多个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;
}

合并重复的条件片段

  有时候你可能会在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();

以卫语句取代嵌套表达式

  这个可能有点难以理解,但是我感觉用处还是比较大的,就是加入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;

  是不是变得很简单,加入卫语句就是合理使用return关键字。有时候反转条件表达式也能简化if else语句。

以多态取代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语句
      到这里就差不多了,文中只是把常用到的,比较好表述的重构方法或情况总结了一下,并没有覆盖到书中的所有情况,如果对重构非常有兴趣的话建议大家阅读原书,绝对值得阅读。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 《重构》读书笔记 总览 第一部分 第一章从实例程序出发,展示设计的缺陷,对其重构可以了解重构的过程和方法。 第二部...
    白桦叶阅读 2,371评论 2 5
  • chapter 1 重构,第一个案例 1.1 什么时候需要重构 需要为程序添加一个特性,但代码结构无法使自己方便的...
    VictorBXv阅读 2,013评论 0 1
  • 可以先看【推荐】:https://www.jianshu.com/p/d6ff54d72afb原文:http://...
    郭某人1阅读 1,831评论 0 0
  • 我呆呆静立垃圾桶里, 被主人无情扫除家门。 前几年,我是宝贝, 因为我无所不能, 我是万能的 帮了主人不少忙。 现...
    旖旎i阅读 315评论 12 5
  • D28阿尔法号阿基米德舱110-黄丹 今天的晨读一开始船长就抛出了几个问题,不禁思考起来,想要的生活都实现了...
    Michelle沐晨阅读 151评论 0 0