CH11_重构API

news2024/11/28 4:47:32

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

在这里插入图片描述

function getTotalOutstandingAndSendBill() {
	const result = customer.invoices.reduce((total, each) => each.amount + total, 0);
	sendBill();
	return result;
}
function totalOutstanding() {
	return customer.invoices.reduce((total, each) => each.amount + total, 0);
}
function sendBill() {
	emailGateway.send(formatBill(customer));
}

动机

如果某个函数只是提供一个值,没有任何看得到的副作用,那么这是一个很有价值的东西。我可以任意调用这个函数,也可以把调用动作搬到调用函数的其他地方。这种函数的测试也更容易。

明确表现出“有副作用”与“无副作用”两种函数之间的差异,是个很好的想法。下面是一条好规则:任何有返回值的函数,都不应该有看得到的副作用——命令与查询分离(Command-Query Separation)[mf-cqs]。

做法

  • 复制整个函数,将其作为一个查询来命名。
  • 从新建的查询函数中去掉所有造成副作用的语句。
  • 执行静态检查。
  • 查找所有调用原函数的地方。如果调用处用到了该函数的返回值,就将其改为调用新建的查询函数,并在下面马上再调用一次原函数。每次修改之后都要测试。
  • 从原函数中去掉返回值。
  • 测试。

完成重构之后,查询函数与原函数之间常会有重复代码,可以做必要的清理。

函数参数化(Parameterize Function)

曾用名:令函数携带参数(Parameterize Method)

在这里插入图片描述

function tenPercentRaise(aPerson) {
	aPerson.salary = aPerson.salary.multiply(1.1);
}
function fivePercentRaise(aPerson) {
	aPerson.salary = aPerson.salary.multiply(1.05);
}
function raise(aPerson, factor) {
	aPerson.salary = aPerson.salary.multiply(1 + factor);
}

动机

如果发现两个函数逻辑非常相似,只有一些字面量值不同,可以将其合并成一个函数,以参数的形式传入不同的值,从而消除重复。这个重构可以使函数更有用,因为重构后的函数还可以用于处理其他的值。

做法

  • 从一组相似的函数中选择一个。
  • 运用改变函数声明(124),把需要作为参数传入的字面量添加到参数列表中。
  • 修改该函数所有的调用处,使其在调用时传入该字面量值。
  • 测试。
  • 修改函数体,令其使用新传入的参数。每使用一个新参数都要测试。
  • 对于其他与之相似的函数,逐一将其调用处改为调用已经参数化的函数。每次修改后都要测试。

移除标记参数(Remove Flag Argument)

曾用名:已明确函数取代参数(Replace Parameter with Explicit Methods)

在这里插入图片描述

function setDimension(name, value) {
    if (name === "height") {
        this._height = value;
        return;
    }
    if (name === "width") {
        this._width = value;
        return;
    }
}
function setHeight(value) {this._height = value;}
function setWidth (value) {this._width = value;}

动机

“标记参数”是这样的一种参数:调用者用它来指示被调函数应该执行哪一部分逻辑。这类参数让人难以理解到底有哪些函数可以调用、应该怎么调用。

如果明确用一个函数来完成一项单独的任务,其含义会清晰得多。

标记参数区分:如果调用者传入的是程序中流动的数据,这样的参数不算标记参数;只有调用者直接传入字面量值,这才是标记参数。在函数实现内部,如果参数值只是作为数据传给其他函数,这就不是标记参数;只有参数值影响了函数内部的控制流,这才是标记参数。

做法

  • 针对参数的每一种可能值,新建一个明确函数。

    如果主函数有清晰的条件分发逻辑,可以用分解条件表达式(260)创建明确函数;否则,可以在原函数之上创建包装函数。

  • 对于“用字面量值作为参数”的函数调用者,将其改为调用新建的明确函数。

保持对象完整(Preserve Whole Object)

在这里插入图片描述

const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (aPlan.withinRange(low, high))
if (aPlan.withinRange(aRoom.daysTempRange))

动机

“传递整个记录”的方式能更好地应对变化:如果将来被调的函数需要从记录中导出更多的数据,就不用为此修改参数列表。如果有很多函数都在使用记录中的同一组数据,处理这部分数据的逻辑常会重复,此时可以把这些处理逻辑搬移到完整对象中去。

做法

  • 新建一个空函数,给它以期望中的参数列表(即传入完整对象作为参数)。
  • 在新函数体内调用旧函数,并把新的参数(即完整对象)映射到旧的参数列表(即来源于完整对象的各项数据)。
  • 执行静态检查。
  • 逐一修改旧函数的调用者,令其使用新函数,每次修改之后执行测试。
  • 所有调用处都修改过来之后,使用内联函数(115)把旧函数内联到新函数体内。
  • 给新函数改名,从重构开始时的容易搜索的临时名字,改为使用旧函数的名字,同时修改所有调用处。

以查询取代参数(Replace parameter with Query)

曾用名:以函数取代参数(Replace parameter with Method)

反向重构:以参数取代查询(Replace Query with Parameter)

在这里插入图片描述

availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {
 // calculate vacation...
}
availableVacation(anEmployee)
function availableVacation(anEmployee) {
    const grade = anEmployee.grade;
    // calculate vacation...
}

动机

函数的参数列表应该总结该函数的可变性,标示出函数可能体现出行为差异的主要方式。和任何代码中的语句一样,参数列表应该尽量避免重复,并且参数列表越短就越容易理解。

如果调用函数时传入了一个值,而这个值由函数自己来获得也是同样容易,这就是重复。

不使用以查询取代参数最常见的原因是,移除参数可能会给函数体增加不必要的依赖关系。

如果想要去除的参数值只需要向另一个参数查询就能得到,这是使用以查询取代参数最安全的场景。如果可以从一个参数推导出另一个参数,那么几乎没有任何理由要同时传递这两个参数。

做法

  • 如果有必要,使用提炼函数(106)将参数的计算过程提炼到一个独立的函数中。
  • 将函数体内引用该参数的地方改为调用新建的函数。每次修改后执行测试。
  • 全部替换完成后,使用改变函数声明(124)将该参数去掉。

以参数取代查询(Replace Query with Parameter)

反向重构:以查询取代参数(Replace parameter with Query)

在这里插入图片描述

targetTemperature(aPlan)

function targetTemperature(aPlan) {
    currentTemperature = thermostat.currentTemperature;
    // rest of function...
}
targetTemperature(aPlan, thermostat.currentTemperature)

function targetTemperature(aPlan, currentTemperature) {
	// rest of function...
}

动机

需要使用本重构的情况大多源于想要改变代码的依赖关系——为了让目标函数不再依赖于某个元素,把这个元素的值以参数形式传递给该函数。这里需要注意权衡:如果把所有依赖关系都变成参数,会导致参数列表冗长重复;如果作用域之间的共享太多,又会导致函数间依赖过度。

以参数取代查询并非只有好处。把查询变成参数以后,就迫使调用者必须弄清如何提供正确的参数值,这会增加函数调用者的复杂度。

做法

  • 对执行查询操作的代码使用提炼变量(119),将其从函数体中分离出来。
  • 现在函数体代码已经不再执行查询操作(而是使用前一步提炼出的变量),对这部分代码使用提炼函数(106)。
  • 使用内联变量(123),消除刚才提炼出来的变量。
  • 对原来的函数使用内联函数(115)。
  • 对新函数改名,改回原来函数的名字。

移除设置函数(Remove Setting Method)

在这里插入图片描述

class Person {
    get name() {/*...*/}
    set name(aString) {/*...*/}
}
class Person {
    get name() {/*...*/}
}

动机

如果为某个字段提供了设值函数,这就暗示这个字段可以被改变。如果不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数。

做法

  • 如果构造函数尚无法得到想要设入字段的值,就使用改变函数声明(124)将这个值以参数的形式传入构造函数。在构造函数中调用设值函数,对字段设值。

    如果想移除多个设值函数,可以一次性把它们的值都传入构造函数,这能简化后续步骤。

  • 移除所有在构造函数之外对设值函数的调用,改为使用新的构造函数。每次修改之后都要测试。

  • 如果不能把“调用设值函数”替换为“创建一个新对象”(例如需要更新一个多处共享引用的对象),请放弃本重构。

  • 使用内联函数(115)消去设值函数。如果可能的话,把字段声明为不可变。

  • 测试。

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

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

在这里插入图片描述

leadEngineer = new Employee(document.leadEngineer, 'E');
leadEngineer = createEngineer(document.leadEngineer);

动机

很多面向对象语言都有特别的构造函数,专门用于对象的初始化。需要新建一个对象时,客户端通常会调用构造函数。但与一般的函数相比,构造函数又常有一些丑陋的局限性。

工厂函数的实现内部可以调用构造函数,但也可以换成别的方式实现。

做法

  • 新建一个工厂函数,让它调用现有的构造函数。
  • 将调用构造函数的代码改为调用工厂函数。
  • 每修改一处,就执行测试。
  • 尽量缩小构造函数的可见范围。

以命令取代函数(Replace Function with Command)

曾用名:以函数对象取代函数(Replace Method with Method Object)

反向重构:以函数取代命令(Replace Command with Function)

在这里插入图片描述

function score(candidate, medicalExam, scoringGuide) {
    let result = 0;
    let healthLevel = 0;
    // long body code
}
class Scorer {
    constructor(candidate, medicalExam, scoringGuide) {
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
    }
    execute() {
        this._result = 0;
        this._healthLevel = 0;
        // long body code
    }
}
function score(candidate, medicalExam, scoringGuide) {
	return new Scorer().execute(candidate, medicalExam, scoringGuide);
}

动机

命令对象为处理复杂计算提供了强大的机制。借助命令对象,可以轻松地将原本复杂的函数拆解为多个方法,彼此之间通过字段共享状态;拆解后的方法可以分别调用;开始调用之前的数据状态也可以逐步构建。

做法

  • 为想要包装的函数创建一个空的类,根据该函数的名字为其命名。

  • 使用搬移函数(198)把函数移到空的类里。

    遵循编程语言的命名规范来给命令对象起名。如果没有合适的命名规范,就给命令对象中负责实际执行命令的函数起一个通用的名字,例如“execute”或者“call”。

  • 可以考虑给每个参数创建一个字段,并在构造函数中添加对应的参数。

以函数取代命令(Replace Command with Function)

反向重构:以命令取代函数(Replace Function with Command)

在这里插入图片描述

class ChargeCalculator {
    constructor (customer, usage){
        this._customer = customer;
        this._usage = usage;
    }
    execute() {
    	return this._customer.rate * this._usage;
    }
}
function charge(customer, usage) {
	return customer.rate * usage;
}

动机

大多数时候,只是想调用一个函数,让它完成自己的工作就好。如果这个函数不是太复杂,那么命令对象可能显得费而不惠,就应该考虑将其变回普通的函数。

做法

  • 运用提炼函数(106),把“创建并执行命令对象”的代码单独提炼到一个函数中。
  • 对命令对象在执行阶段用到的函数,逐一使用内联函数(115)。
  • 使用改变函数声明(124),把构造函数的参数转移到执行函数。
  • 对于所有的字段,在执行函数中找到引用它们的地方,并改为使用参数。每次修改后都要测试。
  • 把“调用构造函数”和“调用执行函数”两步都内联到调用方(也就是最终要替换命令对象的那个函数)。
  • 测试。
  • 用移除死代码(237)把命令类消去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1188105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信公众号H5在手机上分享功能不生效

病情描述: 在代码里配置了分享功能updateAppMessageShareData和updateTimelineShareData,在开发者工具里看log都是成功的,通过手机打开H5链接后,分享的功能不生效,分享出去的是一个链接地址。 在开发者工具里看都是成…

SQL Server SSIS ETL job执行相关操作

创建SSIS项目 Excel导入SQL Server 构建Excel源 配置Excel源信息 配置SQL Server目标 双击“ADO NET目标” job执行 新建job 右键“SQL Server代理”的“作业”,点击“新建作业”,弹出“新建作业”的选项页 首先是“常规”选项页,…

CodeWhisperer--轻松使用一个超级强大的工具!

CodeWhisperer 简介 CodeWhisperer 是亚⻢逊出品的一款基于机器学习的通用代码生成器,可实时提供代码建议。 CodeWhisperer 有以下几个主要用途: 解决编程问题,提供代码建议,学习编程知识等等,并且 CodeWhisperer 会…

在Node.js中,什么是Promise?如何使用Promise处理异步操作?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【华为】防火墙核心知识梳理

一、概述 在通信领域,防火墙是一种安全设备。它用于保护一个网络区域免受来自另一个网络区域的攻击和入侵,通常被应用于网络边界,例如企业互联网出口、企业内部业务边界、数据中心边界等。 防火墙根据设备形态分为,框式防火墙、盒…

COGI控制删除按钮增强

1、业务需求 COGI在SAP中主要用于解决生产订单异常的问题。通常发生在生产订单执行过程中,当出现了特定的异常情况时,需要进行处理。这些异常情况可能包括物料缺失、数量不匹配、质量问题等等。 当这些异常情况发生时,系统会生成异常记录并…

【计算机网络基础实验】实验二 有线IP互通网络实践

任务一 IP路由协议实现企业路由器通信 目录如下: 任务一 IP路由协议实现企业路由器通信2.1.1 任务描述2.1.2 任务目的2.1.3 任务实施实验需求实验步骤步骤1:更改每台设备的名称步骤2: 给R1接口配置相应IP地址步骤3: 给R2接口配置相…

康耐视VisionPro 9.0 R2破解安装教程

文章目录 说明下载安装VisionPro破解匹配的Visual Studion将VisionPro的控件添加到VS工具箱中 说明 康耐视VisionPro 9.0 R2 破解版仅用于个人学习使用,如企业中需要请自行购买正版哦。 下载 百度网盘链接:https://pan.baidu.com/s/1rreSzpe8r2Gz8qSp…

TSINGSEE青犀AI智能分析网关V4人员离岗识别算法的说明及应用

人员离岗AI识别算法,是基于计算机视觉深度学习神经网络技术,通过配合现场部署的监控摄像头,自动识别人员是否在工位或作业区域内,结合离岗时间的配置,可以触发人员离岗告警。该算法目前可应用在监控室、、值班室、中控…

sqlserver查询表结构

SELECT 表名case when a.colorder1 then d.name else end, 表说明case when a.colorder1 then isnull(f.value,) else end, 字段序号a.colorder, 字段名a.name, 标识case when COLUMNPROPERTY( a.id,a.name,IsIdentity)1 then √else end, 主键case when exists(SELECT 1…

2023年初中古诗文大会真题分析和备考建议

2023年上海市中学生古诗文大会(初中组)正在如火如荼地进行中,截止时间是本周日(11月12日),还没有参加的孩子请记得准时参加。 这几天有孩子在咨询今年的初中古诗文大会难度怎么样?都有哪些题型&…

会打字就能编程,自动写代码的ai助手 | 通义灵码

通义灵码介绍 通义灵码是一款由阿里云出品的智能编码辅助工具。 它基于通义大模型,可以提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研发智能问答、异常报错排查等能力。 它支持Java、Python、Go、C/C、JavaScript、Type…

nginx -s reload, 提示 [emerg] duplicate location “/“

问题描述: 执行nginx -s reload, 提示 duplicate location / 问题原因: 是因为location / 有两个,匹配重复了 解决方案: 把其中一个location / 注释掉,即可

华为L410上制作内网镜像模板01

原文链接:华为L410上制作离线安装软件模板01 hello,大家好啊,今天给大家带来一篇在内网搭建Apache服务器,用于安装完内网操作系统后,在第一次开机时候,为系统安装软件,今天给大家用WeChat举例&a…

java excel、word、PPT转换成pdf预览

先引入包:[lib下载地址](https://mp.csdn.net/mp_download/manage/download/UpDetailed)Controllerpublic AjaxResult fileToPdf(RequestBody VerifyCode url, HttpServletResponse response, HttpServletRequest request) throws IOException {String fileUrl req…

轻量封装WebGPU渲染系统示例<19>- 使用GPU Compute材质多pass实现元胞自动机之生命游戏(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifeMultiMaterialPass.ts 系统特性: 1. 用户态与系统态隔离。 细节请见:引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调…

让深度神经网络绘画以了解它们是如何工作的

一、说明 深度学习如此有效,这真是一个谜。尽管有一些关于深度神经网络为何如此有效的线索,但事实是没有人完全确定,并且深度学习的理论理解是一个非常活跃的研究领域。 在本教程中,我们将以一种不寻常的方式触及问题的一个小方面…

物联网中的毫米波雷达:连接未来的智能设备

随着物联网(IoT)技术的飞速发展,连接设备的方式和效能变得越来越重要。毫米波雷达技术作为一种先进的感知技术,正在为物联网设备的连接和智能化提供全新的可能性。本文将深入探讨毫米波雷达在物联网中的应用,以及它是如…

基于SSM的高校疫情防控出入信息管理系统(有报告)。Javaee项目。

演示视频: 基于SSM的高校疫情防控出入信息管理系统(有报告)。Javaee项目。 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 项目介绍: 采…

Linux服务器上搭建JupyterNotebook教程

搭建需知 1.确保是Linux服务器; 2.已经在linux服务器上安装好anaconda3; 搭建教程 请按照顺序依次执行下面的命令: 1、安装Jupyter Notebook 执行以下命令,安装jupyter notebook conda install jupyter【注】 如果anaconda3…