[代码重构]简化函数调用

在对象技术中,最重要的概念莫过于“接口”,容易被理解和被使用的接口是开发良好面向对象软件的关键。本章介绍的重构手法是用来使接口变得更简洁易用的。

简化函数调用

1. 重构手法

1.1 函数改名

概要:
函数的名称未能揭示函数的用途。
修改函数名称。
动机:
a. 让函数名称准确表达它的用途
示例:
重构前:

public String getTelephoneNumber() {
    return mOfficeAreaCode + "-" +mOfficeNumber;
}

重构后:

// 此方法是否要删除,需要根据它是否被客户代码所使用
public String getTelephoneNumber() {
    return getOfficeTelephoneNumber();
}

public String getOfficeTelephoneNumber() {
    return mOfficeAreaCode + "-" +mOfficeNumber;
}

总结:
    将复杂的处理过程分解成小函数是一种良好的编程风格,如果函数命名的好,理解整个处理过程就像阅读一行行的注释一样,理想情况是函数的名称能像自然语言一样表达出自己的功能。如果看到一个函数名称不能很好地表达它的用途,应该立马加以修改。起个好名字是成为编程高手的必备技能。
    给函数命名有一个好办法:首先考虑应该给这个函数写上一句怎样的注释,然后想办法将注释变成函数名称。
    如果旧函数已被客户调用,可以新增一个命名良好的函数实现相同功能,让旧函数转调新函数。

1.2 添加参数

概要:
某个函数需要从调用端得到更多信息。
为此函数添加一个对象参数,让该对象带进函数所需信息。
动机:
a. 函数需要一些过去没有的信息,通过参数将所需信息传递进来
示例:
重构前:

getContact() {
    // do something
}

重构后:

//需求变更或做其他重构,必须修改此函数,让它从对象参数获得某些信息
getContact(Date date) {
    // do something
}

总结:
    使用本重构手法时要仔细考虑是否一定需要添加参数,是否有其他的选择。

1.3 移除参数

概要:
函数本体不再需要某个参数。
将该参数去除。
动机:
a. 去除冗余,简化函数调用
示例:

getContact(Date date) {
    // do something
}

重构后:

//需求变更或做了其他重构,已不再需要参数对象提供信息
getContact() {
    // do something
}

总结:
    参数代表着函数所需的信息,不同的参数有不同的意义,函数调用者需要考虑每一个调用所需要的参数,因此,如果已经不再需要某个参数了,要及时去除。

1.4 将查询函数和修改函数分离

概要:
某个函数既返回对象状态值,又修改对象状态。
建立两个不同的函数,其中一个负责查询,另一个负责修改。
动机:
a. 保持函数职责单一,避免函数调用的副作用
示例:
重构前:

Object getTotalOutstandingAndSetReadyForSummaries() {
}

重构后:

Object getTotalOutstanding() {
}

setReadyForSummaries() {
}

总结:
    承担多个责任的函数一般较难命名,往往需要在命名时引入And/Or等,当你遇到这种命名难题时,考察一下函数是否既做了查询又做了修改。
    每次调用查询函数同时又会修改对象某个状态值的话,很容易引起难以排查的bug。

1.5 令函数携带参数

概要:
若干函数做了类似的工作,但在函数本体中却包含了不同的值。
建立单一函数,以参数表达那些不同的值。
动机:
a. 通过参数处理变化的情况,简化问题,去除重复代码
示例:
重构前:

void tenPercentRaise() {
    salary *= 1.1;
}

void fivePercentRaise() {
    salary *= 1.05;
}

重构后:

void raise(double factor) {
    salary *= (1 + factor);
}

总结:
    有时候这种方法并不能处理整个函数,但可以处理函数中的一部分代码,即便如此,也应将这部分代码提炼到一个独立函数中,用函数调用去除重复的代码。

1.6 以明确函数取代参数

概要:
你有一个函数,其中完全取决于参数值而采取不同行为。
针对该参数的每一个可能值,建立一个独立函数。
动机:
a. 获得更清晰的接口
示例:
重构前:

void setValue(String name, int value) {
    if (name.equals("height")) {
        mHeight = value;
        return;
    }
    if(name.equals("width")) {
        mWidth = value;
        return;
    }
    Assert.shouldNeverReachHere();
}

重构后:

void setHeight(int height) {
    mHeight = height;
}

void setWidth(int width) {
    mWidth = width;
}

总结:
    如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数做出不同的行为,就应该使用本项重构。重构后不仅可以使接口更清晰,还避免了对参数值进行合法性检测的步骤。

1.7 保持对象完整

概要:
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
改为传递整个对象。
动机:
a. 预防被调用函数将来需要新的数据项
b. 避免过长的参数列
示例:
重构前:

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

重构后:

withinPlan = plan.withinRange(daysTempRange());

总结:
    当把对象当作参数传递给函数时,被调用函数所在的对象就需要依赖此参数对象,要谨防依赖结构恶化。
    如果被调用函数使用来自另一个对象的很多项数据,要考虑这个函数是否应该定义在数据所属的对象中。

1.8 以函数取代参数

概要:
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。
而接受该参数的函数本身也能够调用前一个函数。
让参数接受者去除该项参数,并直接调用前一个函数。
动机:
a. 函数可以通过其他途径获得参数值,就不应该通过参数取得该值
示例:
重构前:

int basePrice = mQuantity * mItemPrice;
discountLevel = getDiscountLevel();
double finalPrice = discountedPrice(basePrice, discountLevel);

重构后:

int basePrice = mQuantity * mItemPrice;
double finalPrice = discountedPrice(basePrice);

总结:
    过长的参数列会增加程序阅读者的理解难度,应该尽可能地缩短参数列长度。如果所传参数本身可以被函数直接调用到,就没有必要再通过参数来传递了。

1.9 引入参数对象

概要:
某些参数总是很自然地同时出现。
以一个对象取代这些参数。
动机:
a. 缩短参数列
b. 通过参数对象使代码对数据的访问更具一致性
示例:
重构前:

Bill getBill(Date start, Date end) {
    //统计参数日期范围内的账单并返回
}

重构后:

class DateRange {
    private final Date mStart;
    private final Date mEnd;

    public DateRange(Date start, Date end) {
        mStart = start;
        mEnd = end;
    }

    public Date getStart() {
        return mStart;
    }

    public Date getEnd() {
        return mEnd;
    }
}

Bill getBill(DateRange dateRange) {
    //统计参数日期范围内的账单并返回
}

总结:
    要留意这组参数是否总是在多个地方被一起传递。另外,把这些参数组织到一起之后,就会发现可以将一些行为移至新建的类中,还可减少很多重复代码。

1.10 移除设值函数

概要:
类中的某个字段应该在对象创建时被设值,然后就不再改变。
去掉该字段的所有设值函数。
动机:
a. 使字段不可被修改的意图更清晰
b. 排除字段被修改的可能性
示例:
重构前:

class Account {
    private String mId;

    Account(String id) {
        mId = id;
    }

    void setId(String id) {
        mId = id;
    }
}

重构后:

class Account {
    private final String mId;

    Account(String id) {
        mId = id;
    }
}

总结:
    如果不想用户修改,就不应该提供可以修改的函数,否则,是无法知晓用户会怎样使用的。另外,为了清晰地表达这层意图,应该通过一些语法修饰(如用final修饰)来明确这种意图。

1.11 隐藏函数

概要:
有一个函数,从来没有被其他任何类用到。
将这个函数修改为private。
动机:
a. 降低函数可见度
示例:
重构前:

class Employee {
    public void unusedMethodByOtherClass() {
    }
}

重构后:

class Employee {
    private void unusedMethodByOtherClass() {
    }
}

总结:
    将未被其他类用到的方法封装起来,还可以明确地告诉代码阅读者此方法是在类内部使用的,不属于类对外提供的服务接口。

1.12 以工厂函数取代构造函数

概要:
你希望在创建对象时不仅仅是做简单的建构动作。
将构造函数替换为工厂函数。
动机:
a. 在派生子类的过程中以工厂函数取代类型码
示例:
重构前:

class Person {}
class Male extends Person {}
class Female extends Person {}

Person jack = new Male();

重构后:

class Person {
    static Person createMale() {
        return new Male();
    }

    static Person createFemale() {
        return new Female();
    }
}
class Male extends Person {}
class Female extends Person {}

Person jack = Person.createMale();

总结:
    根据使用的场景,你还可以使用简单工厂模式或工厂方法模式来解决这类问题。

《Effective Java》一书中,Joshua Bloch介绍的第一条经验法则就是:考虑用静态工厂方法代替构造器,并介绍了这种方法的优点和缺点,以及在Java Collections Framework中的运用。

1.13 封装向下转型

概要:
某个函数返回的对象,需要由函数调用者执行向下转型。
将向下转型动作移到函数中。
动机:
a. 尽量给用户提供准确的类型,减少用户非必要的工作
示例:
重构前:

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

重构后:

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

总结:
    这种情况常出现返回迭代器或集合的函数身上,Java5引入泛型之后,实际上做了很多类似的工作,可以参看Java集合源码。

1.14 以异常取代错误码

概要:
某个函数返回一个特定的代码,用以表示某种错误情况。
改用异常。
动机:
a. 将“普通程序”和“错误处理”区分开,使程序更容易理解
示例:
重构前:

int withdraw(int amount) {
    if (amount > mBalance) {
        return -1;
    } else {
        mBalance -= amount;
        return 0;
    }
}

重构后:

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

总结:
    程序中发现错误的地方并不一定知道如何处理错误,当一段子程序发现错误时,它需要让它的调用者知道这个错误,而调用者也可能将这个错误继续沿着调用链传递上去。Unix系统和基于C语言的软件是以返回值来表示子程序的成功或失败。Java语言引入了异常这种错误处理机制,可以使得你写出更健壮、清晰的代码。

1.15 以测试取代异常

概要:
面对一个调用者可以预先检查的条件,你抛出了一个异常。
修改调用者,使它在调用函数之前先做检查。
动机:
a. 避免滥用异常
示例:
重构前:

double getValueForPeriod(int periodNumber) {
    try {
        return mValues[periodNumber];
    } catch (ArrayIndexOutOfBoundsException e) {
        return 0;
    }
}

重构后:

double getValueForPeriod(int periodNumber) {
    if (periodNumber >= mValues.length) {
        return 0;
    }
    return mValues[periodNumber];
}

总结:
    异常的出现是程序语言的一大进步,但异常也不应该被滥用,它只应该被用于异常的、罕见的行为,指的是那些会产生意料之外的错误的行为,异常不应该成为条件检查的替代品,当可以对输入做预先检查时,就先对其进行取值检查,而不是任其产生异常,将错误抛出。

原文地址:https://www.cnblogs.com/justuntil/p/12116118.html

时间: 2024-11-05 16:39:57

[代码重构]简化函数调用的相关文章

重构摘要10_简化函数调用

<重构-改善既有代码的设计>Martin Fowler 摘要: 第十章 简化函数调用 Rename Method 函数改名 改一个自表达的名字吧!骚年 Add Parameter 添加参数 某个函数需要从调用端得到更多信息. 为此函数添加一个对象参数,让该对象代价函数所需信息.并发编程大多数参数很长,不放在一个类中,因为这样你可以保证传递给函数的参数都是不可修改的. Remove Parameter 移除参数 移除不必要的某个参数 Separate Query from Modifier 将查

【软件架构】代码重构之道

1.何为重构? 重构是对软件内部结构的一种调整,它不是改变代码的功能,而是在不改变软件可观察行为的前提下,提高其可理解性,降低修改成本. 用比较通俗的话来说就是把代码从一个地方移动到另外一个地方,保持其简短.易读. 2.为何重构? 如果没有重构,程序会逐渐腐败甚至变质. 当我们只为了短期的目的或者在没有完全理解代码之前,就贸然修改代码,程序就会逐渐失去已有的结构,程序员则愈来愈难通过阅读源码来理解原来的设计. 重构其实就像是在整理代码一样,你所做的就是让所有东西回到应处的位置上.就像我们收拾屋子

代码重构(二):类重构规则(Swift版)

在上篇博客<代码重构(一):函数重构规则(Swift版)>中,详细的介绍了函数的重构规则,其中主要包括:Extract Method, Inline Method, Inline Temp, Replace Temp with Query, Introduce Explaining Variable, Split Temporary Variable, Remove Assignments to Parameters, Replace Method with Method Object等.关于

代码重构方向原则指导

http://www.aqee.net/hill-climbing-wonkish/ 重构是一种对软件进行修改的行为,但它并不改变软件的功能特征,而是通过让软件程序更清晰,更简洁和更条理来改进软件的质量.代码重构之于软件,相当于结构修改之于散文.每次人们对如何对代码进行重构的讨论就像是讨论如果对一篇文学作品进行修订一样无休无止.所有人都知道应该根据项目的自身情况来对代码进行重构,而重构是无止境的.莫扎特从来不不对他的作品进行修订,特罗洛普对自己作品修订的恰到好处,大多数作家认为他们俩这样做都是合

【iOS开发-44】通过案例谈iOS代码重构:合并、格式化输出、宏变量、利用数组字典存储数据,以及利用plist的终极知识

首先我们今天的案例就是如下5个页面通过上一张下一张来切换: (1)第一步,基本是以很傻很直接的方式来创建,这里用到的主要点有: --把对象变量设置为全局变量使得可以在其他方法中调用来设置它们的属性 --设置了一个全局变量index,默认是0,然后通过增加减少这个index值并结合switch来调用不同的数据. --利用先调用一次change方法初始化页面,使得页面定格在第一帧. --利用按钮的enabled属性来设置按钮是否可以被点击,然后结合index的值分别在第1张和第5张时分别把上一张和下

设计模式与代码重构——ios篇

有一阵子没写技术分享文了,最近每个月写一篇个人空间日记.主要是觉得自己技术比较一般写不出有质量的东西,误人子弟.互联网信息膨胀,让我们获取信息更加便捷,然而获取个人所需的正确信息,却需要每个人具备更强的搜索能力.搜索能力作为代码,就需要更优的算法.算法就像是程序的CPU,决定着程序的运行效率. 与其说电脑改变了世界,不如说是电脑改变了人类改变世界的效率.电脑其实是根据人脑设计的,而程序思想和人的思想相通,所以一个程序员在学会一门语言后,学习第二门语言会来的容易很多,因为编程思想是相通的.我认为,

代码重构(OOP)-小栗子(PyQt5)

主要是为了练习下 面向对象, 不断提醒自己代码一定要写成 营销风格, 和优雅. 最近在B站上看一下关于 Python GUI 编程的内容. 恰好呢, 前不久的一个 将本地 Ecxcel 数据 发布到 Tableau Server 中, 当核心接口搞定后, 工程化领导让弄 web, 我们果断拒绝了, 不太熟前端也暂时不想学, 就用 Python 自带的 Tk 模块来写一版, 涉及安全就不能共享出来, 总体蛮简单的. 后来, 倒是引发我对 gui 的一点小兴趣. 就没事看看 PyQt5 的内容, 没

Windows程序代码重构

代码重构:在程序功能实现之后,对代码进行一定规模的整理,使之符合"高内聚.低耦合"的软件设计原则,便于维护和使用. ①用函数封装消息处理代码--对Windows程序窗口函数中的每一个case程序段进行封装以形成一个消息处理函数,而在case中调用这个函数. ②利用数组或链表实现消息映射表进一步实现代码的隔离--因为窗口函数switch-case结构实质上实现的就是一个根据消息标识来查找消息处理代码的功能,故可以用消息映射表和一段查表程序来替代它,表中的每一项可以使用一个函数指针来指向消

Job Service代码重构遐想

最近有大概半个月的时间可以重构下代码,刚好可以重新整理下Job Service相关的代码.前段时间由于忙于完成Job Service所有功能以及完成对Auto Job的支持以正常上线,使得有些地方的代码写得不是特别优雅.主要集中在以下一些地方: DAG状态的转移 目前DAG状态分为3层,分别为ApplicationStatus.TaskStatus.InstanceStatus.每个层次都有以下几种状态,Waiting.Running.Finished.Stopped.Failed.并且DAG有