《重构》读书笔记重构

《重构》学习笔记(08)-- 简化函数调用

2019-10-11  本文已影响0人  若隐爱读书

本章主要针对的是对函数的重构,包括函数改名,参数增删等。良好的函数调用可以增加代码的可读性和可维护性。

Rename Method(函数改名)

我们提倡的一种编程风格是:将复杂的处理过程分解成小函数。但是,如果做得不好,这会使你费尽周折却弄不清这些小函数的用途。要避免这种麻烦,关键就在于给函数起一个好的名字。给函数命名有个好的方法:首先考虑给函数写一个注释,然后把注释变成函数的名称。


重构前

重构为


重构后

这种重构常用的做法为:

Add Parameter(添加参数)

如果修改一个函数,而修改后的函数需要一些过去没有的信息,因此你必须给函数增加一个参数。如果有其他选择,尽量不要使用本重构手段。因为过长的参数也是代码坏味道。


重构前

重构为


重构后
这种重构的一般手段是:

Remove Parameter(移除参数)

移除参数与增加参数相反,如果一个参数可以从另一个参数得到,或者可以从其他渠道获取。就要对入参进行删除。如果不去掉多余参数,就会在每次调用时多费一份心。


重构前

重构为


重构后
通常的做法为:

Separate Query from Modifier(将查询函数与修改函数分离)

某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。


重构前

重构为


重构后
这种重构一般的做法为:

Parameterize Method(令函数携带参数)

若干函数做了类似的工作,但在函数本体中却包含了不同的值。那么建立单一函数,以参数表达那些不同的值。


重构前

重构为


重构后
这种重构一般的做法为:

Replace Parameter with Explicit Methods(以明确函数取代参数)

如果你有个函数,其中完全取决于参数值而采取不同行为。针对该参数的每一个可能值,建立一个独立函数。这样,可以避免出现条件表达式,而且接口更加清楚。

public void setValue(String name, int value) {
    if (name.equals("height")) {
        height = value;
    } else if (name.equals("width")) {
        width = value;
    }
}

可以重构为:

public void setHeight(int height) {
    height = this.height;
}

public void setWidth(int width) {
    width = this.width;
}

这种重构的做法:

Preserve Whole Object(保持对象完整)

你从某个对象中取出若干值,将它们作为某一次函数调用时的参数,这种调用方法的问题在于:如果将来调用函数需要新的数据项,你就必须查找并修改对此函数的所有调用。因此,建议改为传递整个对象 。

 int low = daysTempRange().getLow();
 int high = daysTempRange().getHigh();
 withinPlan = plan.withinRange(low, high);

重构为:

withinPlan = plan.withinRange(daysTempRange());

这种重构的做法:

Replace Parameter with Methods(以函数取代参数)

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。

做法

Introduce Parameter Object(引入参数对象)

如果一组特定的参数总是一起传递,可能有好几个函数都使用这一组参数,这一组参数就是所谓的Data Clumps(数据泥团)。这时候我们可以使用一个对象包装所有的这些数据。

amountInvoicedln(start:Date,end:Date)
amountReceivedln(start:Date,end:Date)

可以重构为:

amountInvoicedln(DateRange)
amountReceivedln(DateRange)

这种重构可以减少代码量,并增加代码的可移植性。这种重构的做法通常为:

Remove Setting Method(移除设值函数)

如果某个字段在对象创建的时候就被创建,然后就不再改变。那么就去除该字段的所有设值函数

重构前
重构后
如果为某个字段提供了设值函数,那么就暗示这个字段值可以改变。如果你不希望该字段在创建后还有机会改变,那么就不要为它提供设值函数,同时将该字段设置为final。通常的做法为:

Hide Method(隐藏函数)

如果有一个函数,从来没有被其他任何类用到,那么将这个函数设为private

重构前
重构后
做法:

Replace Constructor with Factory Method(以工厂函数取代构造函数)

这种重构方法就是构建工厂模式的最基本的方法。

Employee (int type){
  _type=type;
}

重构为

static Employee create(int type){
 return new Employee(type);
}

工厂模式的好处会在将来的设计模式文章中详细介绍。使用Replace Constructor with Factory Method 的最显而易见的动机就是在subclassing(子类化) 过程中以factory method 以取代type code(类型代码)。
你可能常常需要根据type code 创建相应的对象,现在,创建名单中还得加上subclasses,那些subclasses 也是根据type code 来创建。然而由于构造函数只能返回「被索求之对象」,因此你需要将构造函数替换为Factory Method [Gang of Four]。
做法:

Encapsulate Downcast(封装向下转型)

某个函数返回的对象,需要由函数调用者执行「向下转型」(downcast)动作。那么将向下转型(downcast)动作移到函数中。

Object lastReading() {  return readings.lastElement();}

重构为:

Reading lastReading() {  return (Reading) readings.lastElement();}

在强型面向对象语言中,向下转型是最烦人的事情之一。之所以很烦人,是因为从感觉上来说它完全没有必要:你竟然越俎代庖地告诉编译器某些应该由编译器自己计算出来的东西。但是,由于「计算对象型别」的动作往往比较麻烦,你还是常常需要亲自告诉编译器「对象的确切型别」。向下转型在Java 特别盛行,因为Java 没有template(模板)机制,因此如果你想从群集(collection)之中取出一个对象,就必须进行向下转型。向下转型也许是一种无法避免的罪恶,但你仍然应该尽可能少做。如果你的某个函数返回一个值,并且你知道「你所返回的对象」其型别比函数签名式(signature) 所昭告的更特化(specialized;译注:意指返回的是原本声明之return type 的subtype),你便是在函数用户身上强加了非必要的工作。这种情况下你不应该要求用户承担向 下转型的责任,应该尽量为他们提供准确的型别。

做法:

Replace Error Code with Exception(用异常取代错误码)

问题:某个函数返回一个特定的代码(special code),用以表示某种错误情况。

解决:改用异常(exception)

int withdraw(int amount) {
    if (amount > _balance)//数量大于平衡值 
       return -1;    
    else {
       _balance -= amount;
        return 0;    
    }
}

重构为

void withdraw(int amount) throws BalanceException {
    if (amount > _balance) 
        throw new BalanceException();   
        _balance -= amount;
}

和生活一样,计算器偶尔也会出错。一旦事情出错,你就需要有些对策。
最简单的情况下,你可以停止程序运行,返回一个错误码。
如果程序崩溃代价很小,用户又足够宽容,那么就放心终止程序的运行好了。
Java 有一种更好的错误处理方式:异常(exceptions)。这种方式之所以更好,因 为它清楚地将「普通程序」和「错误处理」分开了,这使得程序更容易理解——我希望你如今已经坚信:代码的可理解性应该是我们虔诚追求的目标
做法

上一篇 下一篇

猜你喜欢

热点阅读