CH08_搬迁特性

news2024/11/28 5:35:03

搬迁函数(Move Function)

曾用名:搬迁函数(Move Method)

在这里插入图片描述

class Account{
    get overdraftCharge(){...}
    ...
}
class AccountType{
    get overdraftCharge(){...}
    ...
}

动机

模块化是优秀软件设计的核心所在,好的模块化能够在修改程序时只需理解程序的一小部分。为了设计出高度模块化的程序,需要保证互相关联的软件要素都能集中到一块,并确保块与块之间的联系易于查找、直观易懂。

任何函数都需要具备上下文环境才能存活。这个上下文可以是全局的,但它更多时候是由某种形式的模块所提供的。对一个面向对象的程序而言,类作为最主要的模块化手段,其本身就能充当函数的上下文;通过嵌套的方式,外层函数也能为内层函数提供一个上下文。不同的语言提供的模块化机制各不相同,但这些模块的共同点是,它们都能为函数提供一个赖以存活的上下文环境。

搬移函数最直接的一个动因是,它频繁引用其他上下文中的元素,而对自身上下文中的元素却关心甚少。此时,让它去与那些更亲密的元素相会,通常能取得更好的封装效果,因为系统别处就可以减少对当前模块的依赖。

做法

  • 检查函数在当前上下文里引用的所有程序元素(包括变量和函数),考虑是否需要将它们一并搬移

    如果发现有些被调用的函数也需要搬移,我通常会先搬移它们。这样可以保证移动一组函数时,总是从依赖最少的那个函数入手。

    如果该函数拥有一些子函数,并且它是这些子函数的唯一调用者,那么你可以先将子函数内联进来,一并搬移到新家后再重新提炼出子函数。

  • 检查待搬移函数是否具备多态性

    在面向对象的语言里,还需要考虑该函数是否覆写了超类的函数,或者为子类所覆写。

  • 将函数复制一份到目标上下文中。调整函数,使它能适应新家。

    如果函数里用到了源上下文(source context)中的元素,就得将这些元素一并传递过去,要么通过函数参数,要么是将当前上下文的引用传递到新的上下文那边去。
    搬移函数通常意味着,还得给它起个新名字,使它更符合新的上下文。

  • 执行静态检查

  • 设法从源上下文中正确引用目标函数

  • 修改源函数,使之成为一个纯委托函数

  • 测试

  • 考虑对源函数使用内联函数(115)

    也可以不做内联,让源函数一直做委托调用。但如果调用方直接调用目标函数也不费太多周折,那么最好还是把中间人移除掉。

搬移字段

在这里插入图片描述

class Customer {
    get plan() {return this._plan;}
    get discountRate() {return this._discountRate;}
    // ...
}
class Customer {
    get plan() {return this._plan;}
    get discountRate() {return this.plan.discountRate;}
    // ...
}

动机

编程活动中需要编写许多代码,为系统实现特定的行为,但往往数据结构才是一个健壮程序的根基。一个适应于问题域的良好数据结构,可以让行为代码变得简单明了,而一个糟糕的数据结构则将招致许多无用代码,这些代码更多是在差劲的数据结构中间纠缠不清,而非为系统实现有用的行为。

好的数据结构至关重要——不过这也与编程活动的许多方面一样,它们都很难一次做对。如果具备一些领域驱动设计(domain-driven design)方面的经验和知识,往往有助于更好地设计数据结构。

如果发现数据结构已经不适应于需求,就应该马上修缮它。如果容许瑕疵存在并进一步累积,使代码愈来愈复杂。

搬移字段的操作通常是在其他更大的改动背景下发生的。实施字段搬移后,可能会发现字段的诸多使用者应该通过目标对象来访问它,而不应该再通过源对象来访问。诸如此类的清理,在此后的重构中一并完成。

做法

  • 确保源字段已经得到可良好封装
  • 测试
  • 在目标对象上创建一个字段(及对应的访问函数)
  • 执行静态检查
  • 确保源对象里能够正常引用目标对象
  • 调整源对象的访问函数,令其使用目标对象的字段
  • 测试
  • 移除源对象上的字段
  • 测试

搬移语句到函数(Move Statements into Function)

反向重构:搬移语句到调用者(217)

在这里插入图片描述

result.push(`<p>title: ${person.photo.title}</p>`);
result.concat(photoData(person.photo));

function photoData(aPhoto) {
    return [
        `<p>location: ${aPhoto.location}</p>`,
        `<p>date: ${aPhoto.date.toDateString()}</p>`,
    ];
}
result.concat(photoData(person.photo));

function photoData(aPhoto) {
    return [
        `<p>title: ${aPhoto.title}</p>`,
        `<p>location: ${aPhoto.location}</p>`,
        `<p>date: ${aPhoto.date.toDateString()}</p>`,
    ];
}

动机

要维护代码库的健康发展,需要遵守几条黄金守则,其中最重要的一条当属“消除重复”。

如果某些语句与一个函数放在一起更像一个整体,并且更有助于理解,建议将语句搬移到函数里去。如果它们与函数不像一个整体,但仍应与函数一起执行,可以用提炼函数(106)将语句和函数一并提炼出去。

做法

  • 如果重复的代码段近离调用目标函数的地方还有些距离,则先用移动语句(223)将这些语句挪动到紧邻目标函数的位置。
  • 如果目标函数仅被唯一一个源函数调用,那么只需将源函数中的重复代码段剪切并粘贴到目标函数中即可,然后运行测试。本做法的后续步骤至此可以忽略。
  • 如果函数不止一个调用点,那么先选择其中一个调用点应用提炼函数(106),将待搬移的语句与目标函数一起提炼成一个新函数。给新函数取个临时的名字,只要易于搜索即可。
  • 调整函数的其他调用点,令它们调用新提炼的函数。每次调整之后运行测试。
  • 完成所有引用点的替换后,应用内联函数(115)将目标函数内联到新函数里,并移除原目标函数。
  • 对新函数应用函数改名(124),将其改名为原目标函数的名字。

搬移语句到调用者(Move Statements to Callers)

反向重构:搬移语句到函数(213)

在这里插入图片描述

emitPhotoData(outStream, person.photo);

function emitPhotoData(outStream, photo) {
    outStream.write(`<p>title: ${photo.title}</p>\n`);
    outStream.write(`<p>location: ${photo.location}</p>\n`);
}
emitPhotoData(outStream, person.photo);
outStream.write(`<p>location: ${person.photo.location}</p>\n`);

function emitPhotoData(outStream, photo) {
	outStream.write(`<p>title: ${photo.title}</p>\n`);
}

动机

作为程序员,我们的职责就是设计出结构一致、抽象合宜的程序,而程序抽象能力的源泉正是来自函数。与其他抽象机制的设计一样,我们并非总能平衡好抽象的边界。随着系统能力发生演进(通常只要是有用的系统,功能都会演进),原先设定的抽象边界总会悄无声息地发生偏移。

函数边界发生偏移的一个征兆是,以往在多个地方共用的行为,如今需要在某些调用点面前表现出不同的行为。

这个重构手法比较适合处理边界仅有些许偏移的场景,但有时调用点和调用者之间的边界已经相去甚远,此时便只能重新进行设计了。

做法

  • 最简单的情况下,原函数非常简单,其调用者也只有寥寥一两个,此时只需把要搬移的代码从函数里剪切出来并粘贴回调用端去即可,必要的时候做些调整。运行测试。如果测试通过,那就大功告成,本手法可以到此为止。

  • 若调用点不止一两个,则需要先用提炼函数(106)将你不想搬移的代码提炼成一个新函数,函数名可以临时起一个,只要后续容易搜索即可。

    如果原函数是一个超类方法,并且有子类进行了覆写,那么还需要对所有子类的覆写方法进行同样的提炼操作,保证继承体系上每个类都有一份与超类相同的提炼函数。接着将子类的提炼函数删除,让它们引用超类提炼出来的函数。

  • 对原函数应用内联函数(115)。

  • 对提炼出来的函数应用改变函数声明(124),令其与原函数使用同一个名字。

以函数调用取代内联代码(Replace Inline Code with Function Call)

在这里插入图片描述

let appliesToMass = false;
for(const s of states) {
	if (s === "MA") appliesToMass = true;
}
appliesToMass = states.includes("MA");

动机

一个命名良好的函数,本身就能极好地解释代码的用途,使读者不必了解其细节。

函数同样有助于消除重复,因为同一段代码不需要编写两次,每次调用一下函数即可。

如果一些内联代码,它们做的事情仅仅是已有函数的重复,通常会以一个函数调用取代内联代码。

当内联代码与函数之间只是外表相似但其实并无本质联系时,这种情况下,当改变了函数实现时,并不期望对应内联代码的行为发生改变。判断内联代码与函数之间是否真正重复,从函数名往往可以看出端倪。

做法

  • 将内联代码替代为对一个既有函数的调用。
  • 测试

移动语句(Slide Statements)

曾用名:合并重复的代码片段(Consolidate Duplicate Conditional Fragments)

在这里插入图片描述

const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
let charge;

动机

让存在关联的东西一起出现,可以使代码更容易理解。如果有几行代码取用了同一个数据结构,那么最好是让它们在一起出现,而不是夹杂在取用其他数据结构的代码中间。

通常来说,把相关代码搜集到一处,往往是另一项重构(通常是在提炼函数(106))开始之前的准备工作。相比于仅仅把几行相关的代码移动到一起,将它们提炼到独立的函数往往能起到更好的抽象效果。

做法

  • 确定待移动的代码片段应该被搬往何处。仔细检查待移动片段与目的地之间的语句,看看搬移后是否会影响这些代码正常工作。如果会,则放弃这项重构。
  • 剪切源代码片段,粘贴到上一步选定的位置上。
  • 测试。

拆分循环(Split Loop)

在这里插入图片描述

let averageAge = 0;
let totalSalary = 0;
for (const p of people) {
    averageAge += p.age;
    totalSalary += p.salary;
}
averageAge = averageAge / people.length;
let totalSalary = 0;
for (const p of people) {
	totalSalary += p.salary;
}
let averageAge = 0;
for (const p of people) {
	averageAge += p.age;
}
averageAge = averageAge / people.length;

动机

一些身兼多职的循环,它们一次做了两三件事情,不为别的,就因为这样可以只循环一次。但如果在一次循环中做了两件不同的事,那么每当需要修改循环时,都得同时理解这两件事情。如果能够将循环拆分,让一个循环只做一件事情,那就能确保每次修改时只需要理7解要修改的那块代码的行为就可以了。

拆分循环还能让每个循环更容易使用。如果一个循环只计算一个值,那么它直接返回该值即可;但如果循环做了太多件事,那就只得返回结构型数据或者通过局部变量传值了。

做法

  • 复制一遍循环代码。
  • 识别并移除循环中的重复代码,使每个循环只做一件事。
  • 测试。

完成循环拆分后,考虑对得到的每个循环应用提炼函数(106)。

以管道取代循环(Replace Loop with Pipeline )

在这里插入图片描述

const names = [];
for (const i of input) {
    if (i.job === "programmer")
    	names.push(i.name);
}
const names = input
    .filter(i => i.job === "programmer")
    .map(i => i.name)

动机

越来越多的编程语言都提供了更好的语言结构来处理迭代过程,这种结构就叫作集合管道(collection pipeline)。集合管道[mf-cp]是这样一种技术,它允许使用一组运算来描述集合的迭代过程,其中每种运算接收的入参和返回值都是一个集合。

最常见的则非map和filter莫属:map运算是指用一个函数作用于输入集合的每一个元素上,将集合变换成另外一个集合的过程;filter运算是指用一个函数从输入集合中筛选出符合条件的元素子集的过程。

做法

  • 创建一个新变量,用以存放参与循环过程的集合。

  • 从循环顶部开始,将循环里的每一块行为依次搬移出来,在上一步创建的集合变量上用一种管道运算替代之。每次修改后运行测试。

  • 搬移完循环里的全部行为后,将循环整个删除。

    如果循环内部通过累加变量来保存结果,那么移除循环后,将管道运算的最终结果赋值给该累加变量。

移除死代码(Remove Dead Code)

在这里插入图片描述

if(false){
    doSomethingThatUsedtoMatter();
}

动机

大多数现代的编译器还会自动将无用的代码移除。但当尝试阅读代码、理解软件的运作原理时,无用代码确实会带来很多额外的思维负担。

一旦代码不再被使用,就该立马删除它。

做法

  • 如果死代码可以从外部直接引用,比如它是一个独立的函数时,先查找一下还有无调用点。
  • 将死代码移除。
  • 测试。

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

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

相关文章

C语言自定义类型讲解:结构体,枚举,联合(2)

&#x1f435;本篇文章将会对位段、枚举和联合的相关知识进行讲解 1. 位段&#x1f4da; 1.1 什么是位段 位段的声明和结构体类似&#xff0c;但是有两点不同&#xff1a; 1.位段的成员必须是int&#xff0c;unsigned int&#xff0c;signed int (C99之后也可以是其他成员&am…

Redis 线程模式

Redis 是单线程吗&#xff1f; Redis 单线程指的是 [接收客户端请求 -> 解析请求 -> 进行数据读写操作 -> 发送数据给客户端] 这个过程是由一个线程 (主线程) 来完成的&#xff0c;这也是常说的 Redis 是单线程的原因。 但是 &#xff0c;Redis 程序不是单线程的&am…

nginx 反向代理 负载均衡 动静分离

一样东西的诞生通常都是为了解决某些问题&#xff0c;对于 Nginx 而言&#xff0c;也是如此。 比如&#xff0c;你出于无聊写了一个小网站&#xff0c;部署到 tomcat 之后可以正常访问 但是后来&#xff0c;你的这个小网站因为内容很诱人逐步的火了&#xff0c;用户越来越多&a…

C#开发的OpenRA游戏之雷达地图

C#开发的OpenRA游戏之雷达地图 从前面的游戏里,就可以看到在上面按钮下面留有一个区域,这个区域的作用,就是用来显示一个雷达地图,如下图所示: 从雷达地图来看,可以清楚地看到全局的动态,自己的兵力分布,还有自己的建筑分布,矿产分布等等。 在这里就来对这个雷达地图…

Python编程:使用PIL进行JPEG图像压缩的简易教程

摘要: 本文介绍了如何使用Python编程语言和wxPython图形用户界面库进行JPEG图像的压缩。通过添加滑块控件&#xff0c;我们可以调整压缩质量&#xff0c;并将压缩后的照片另存为原来的名称加上后缀"压缩质量数字"的新文件。 C:\pythoncode\new\image2small.py 完整…

AI编程助手 Amazon CodeWhisperer 全面解析与实践

目录 引言Amazon CodeWhisperer简介智能编程助手智能代码建议代码自动补全 提升代码质量代码质量提升安全性检测 支持多平台多语言 用户体验和系统兼容性用户体验文档和学习资源个性化体验系统兼容性 功能全面性和代码质量功能全面性代码生成质量和代码安全性 CodeWhisperer的代…

程序启动-大数据平台搭建

1、启动zookeeper集群 /home/cluster/zookeeper.sh start /home/cluster/zookeeper.sh stop 2、启动hadoop和yarn集群 /home/cluster/hadoop-3.3.6/sbin/start-dfs.sh /home/cluster/hadoop-3.3.6/sbin/start-yarn.sh /home/cluster/hadoop-3.3.6/sbin/stop-dfs.sh /home/clust…

以太坊代币标准ERC20、ERC165、ERC721

两个概念 ERC(Ethereum Request for Comment) 以太坊意见征集稿EIP(Ethereum Improvement Proposals)以太坊改进提案 ERC和EIP用于使得以太坊更加完善&#xff1b;在ERC中提出了很多标准&#xff0c;用的最多的标准就是它的Token标准; 有哪些标准详细见https://eips.ethereum…

预制菜行业数据分析(京东数据挖掘)

最近一段时间&#xff0c;关于预制菜进校园事件的讨论热度高涨。而这两天&#xff0c;核酸大王“张核子”转行开预制菜公司卖方便米饭的消息又被传出&#xff0c;直接让预制菜市场饱受关注。 “预制菜是近两年的风口”&#xff0c;这个结论鲸参谋早在以往的内容中专门讨论过&a…

Java 18的未来:新特性和编程实践

文章目录 引言新特性预览1. 基于值的类的进一步改进2. 模式匹配的增强3. 新的垃圾回收器4. 扩展的模块系统5. 更强大的异步编程 编程实践示例1&#xff1a;基于值的类示例2&#xff1a;模式匹配的增强示例3&#xff1a;新的垃圾回收器 结论 &#x1f389;欢迎来到Java学习路线专…

python机器学习融合模型:Stacking与Blending(附代码)

1 堆叠法Stacking 一套弱系统能变成一个强系统吗&#xff1f; 当你处在一个复杂的分类问题面前时&#xff0c;金融市场通常会出现这种情况&#xff0c;在搜索解决方案时可能会出现不同的方法。 虽然这些方法可以估计分类&#xff0c;但有时候它们都不比其他分类好。在这种情况…

[WUSTCTF2020]颜值成绩查询 布尔注入二分法

这道题很简单 就是sql注入 我们来学习一下如何写盲注脚本 ?stunum1 ?stunum123 正确回显 100 错误 显示 not 。。。 这里很显然就是盲注了 我们来写个语句查询 if(ascii(substr(database(),1,1))>1,1,0)发现回显了 我们可以开始编写脚本跑了 import requests impor…

DeepMind 利用无监督学习开发 AlphaMissense,预测 7100 万种基因突变

类基因组共有 31.6 亿个碱基对&#xff0c;无时无刻不在经历复制、转录和翻译&#xff0c;也随时有着出错突变的风险。 错义突变是基因突变中的一种常见形式&#xff0c;然而人类目前只观察到了其中的一小部分&#xff0c;能够解读的更是只有 0.1%。 准确预测错义突变的作用&am…

Windows10/11显示文件扩展名 修改文件后缀名教程

前言 写这篇文章的原因是由于我分享的教程中的文件、安装包基本都是存在阿里云盘的&#xff0c;下载后需要改后缀名才能使用。 但是好多同学不会改。。 Windows 10 随便打开一个文件夹&#xff0c;在上方工具栏点击 “查看”点击 “查看” 后下方会显示更详细的工具栏然后点…

剪映软件专业版的操作与使用,电脑版与手机版APP同步讲解

一、教程描述 什么是剪映&#xff1f;抖音官方推出的一款视频编辑工具&#xff0c;用于短视频的剪辑制作和在线发布&#xff0c;主要在手机端使用&#xff0c;同时支持PC端&#xff0c;操作简单易上手&#xff0c;功能也十分强大&#xff0c;使用过剪映的用户&#xff0c;都将…

ViT细节与代码解读

最近看到两篇解读ViT很好的文章&#xff0c;备忘记录一下&#xff1a; 先理解细节 1&#xff1a;再读VIT&#xff0c;还有多少细节是你不知道的 再理解代码 1&#xff1a;ViT源码阅读-PyTorch - 知乎

此芯科技加入百度飞桨硬件生态共创计划,加速端侧AI生态布局

近日&#xff0c;此芯科技&#xff08;上海&#xff09;有限公司&#xff08;以下简称“此芯科技”&#xff09;与百度签署硬件生态共创计划合作协议&#xff0c;正式加入由百度发起的硬件生态共创计划。双方将共同推动端侧AI和大模型在个人计算、车载计算以及元宇宙计算等领域…

Spring中是否可以存在两个相同ID的bean

文章目录 一、在同一个xml配置文件里配置两个相同ID的bean结论验证过程源码 二、在不同xml配置文件里配置两个相同ID的bean结论验证过程源码 三、在同一个配置类中以Bean方式添加两个名称相同的bean结论验证过程源码 四、在不同配置类中以Bean方式添加两个名称相同的bean结论验…

基础设施建设-企业级全栈测试平台的最佳实践

QECon&#xff08;Quality Efficiency Conference&#xff09;质量效能大会在上海正式开幕&#xff01;本次大会以"数生智慧&#xff1a;高质量发展新引擎"为主题&#xff0c;深入探讨如何借助数字化和智能化技术推动软件质量的发展&#xff0c;为高质量经济发展提供…

华为云,让AI算力入山河

整个2023年&#xff0c;全球科技界都在为大模型沸腾。云计算产业作为AI大模型与产业场景间的最短路径&#xff0c;自然也在大模型浪潮中备受关注。目前阶段&#xff0c;云厂商已经纷纷入局大模型&#xff0c;从多个角度探索大模型带给云计算产业的可能性。 但我们往往会忽略这样…