CH10_简化条件逻辑

news2024/11/29 2:59:33

分解条件表达式(Decompose Conditional)

在这里插入图片描述

if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
	charge = quantity * plan.summerRate;
else
	charge = quantity * plan.regularRate + plan.regularServiceCharge;
if (summer())
	charge = summerCharge();
else
	charge = regularCharge();

动机

程序之中,复杂的条件逻辑是最常导致复杂度上升的地点之一。大型函数本身就会使代码的可读性下降,而条件逻辑则会使代码更难阅读。

将复杂的条件逻辑分解为多个独立的函数,根据每个小块代码的用途,为分解而得的新函数命名,并将原函数中对应的代码改为调用新函数,从而更清楚地表达自己的意图。对于条件逻辑,将每个分支条件分解成新函数还可以带来更多好处:可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因。

分解条件表达式其实只是提炼函数(106)的一个应用场景。

做法

  • 对条件判断和每个条件分支分别运用提炼函数(106)手法。

合并条件表达式(Consolidate Conditional Expression)

在这里插入图片描述

if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;
if (isNotEligibleForDisability()) return 0;
function isNotEligibleForDisability() {
    return ((anEmployee.seniority < 2)
            || (anEmployee.monthsDisabled > 12)
            || (anEmployee.isPartTime));
}

动机

检查条件各不相同,最终行为却一致。如果发现这种情况,就应该使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。

之所以要合并条件代码,有两个重要原因。首先,合并后的条件代码会表述“实际上只有一次条件检查,只不过有多个并列条件需要检查而已”,从而使这一次检查的用意更清晰。其次,这项重构往往可以为使用提炼函数(106)做好准备。

条件语句的合并理由也同时指出了不要合并的理由:如果认为这些检查的确彼此独立的确不应该被视为同一次检查。

做法

  • 的确定这些条件表达式都没有副作用。
  • 使用适当的逻辑运算符,将两个相关条件表达式合并为一个。
  • 测试。
  • 重复前面的合并过程,直到所有相关的条件表达式都合并到一起。
  • 可以考虑对合并后的表达式实施提炼函数(106)。

以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses)

在这里插入图片描述

function getPayAmount() {
	let result;
	if (isDead)
		result = deadAmount();
	else {
		if (isSeparated)
			result = separatedAmount();
		else {
			if (isRetired)
				result = retiredAmount();
			else
				result = normalPayAmount();
		}
	}
	return result;
}
function getPayAmount() {
    if (isDead) return deadAmount();
    if (isSeparated) return separatedAmount();
    if (isRetired) return retiredAmount();
    return normalPayAmount();
}

动机

条件表达式通常有两种风格:第一种风格是:两个条件分支都属于正常行为。第二种风格则是:只有一个条件分支是正常行为,另一个分支则是异常的情况。

如果两条分支都是正常行为,就应该使用形如if…else…的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”(guard clauses)。

以卫语句取代嵌套条件表达式的精髓就是:给某一条分支以特别的重视。如果使用if-then-else结构,对if分支和else分支的重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。卫语句就不同了,它告诉阅读者:“这种情况不是本函数的核心逻辑所关心的,如果它真发生了,请做一些必要的整理工作,然后退出。”

以前的编程语言都强调“每个函数只能有一个入口和一个出口”的观念,现今的编程语言都会强制保证每个函数只有一个入口,至于“单一出口”规则,其实不是那么有用,保持代码清晰才是最关键的。如果单一出口能使这个函数更清楚易读,那么就使用单一出口;否则就不必这么做。

做法

  • 选中最外层需要被替换的条件逻辑,将其替换为卫语句。
  • 测试。
  • 有需要的话,重复上述步骤。
  • 如果所有卫语句都引发同样的结果,可以使用合并条件表达式(263)合并之。

以多态取代条件表达式(Replace Conditional with Polymorphism)

在这里插入图片描述

switch (bird.type) {
	case 'EuropeanSwallow':
		return "average";
    case 'AfricanSwallow':
        return (bird.numberOfCoconuts > 2) ? "tired" : "average";
    case 'NorwegianBlueParrot':
    	return (bird.voltage > 100) ? "scorched" : "beautiful";
    default:
    	return "unknown";
}
class EuropeanSwallow {
    get plumage() {
        return "average";
    }
}
class AfricanSwallow {
    get plumage() {
        return (this.numberOfCoconuts > 2) ? "tired" : "average";
    }
}
class NorwegianBlueParrot {
    get plumage() {
        return (this.voltage > 100) ? "scorched" : "beautiful";
	}
}

动机

复杂的条件逻辑是编程中最难理解的东西之一。很多时候,可以将条件逻辑拆分到不同的场景(或者叫高阶用例),从而拆解复杂的条件逻辑。这种拆分有时用条件逻辑本身的结构就足以表达,但使用类和多态能把逻辑的拆分表述得更清晰。

常用场景:可以针对switch语句中的每种分支逻辑创建一个类,用多态来承载各个类型特有的行为,从而去除重复的分支逻辑。

另一种情况:有一个基础逻辑,在其上又有一些变体。基础逻辑可能是最常用的,也可能是最简单的。把基础逻辑放进超类,这样可以首先理解这部分逻辑,暂时不管各种变体,然后可以把每种变体逻辑单独放进一个子类,其中的代码着重强调与基础逻辑的差异。

多态是面向对象编程的关键特性之一。跟其他一切有用的特性一样,它也很容易被滥用。大部分条件逻辑用基本的条件语句——if/else和switch/case就能应付,并不需要劳师动众地引入多态。

做法

  • 如果现有的类尚不具备多态行为,就用工厂函数创建之,令工厂函数返回恰当的对象实例。
  • 在调用方代码中使用工厂函数获得对象实例。
  • 将带有条件逻辑的函数移到超类中。
  • 任选一个子类,在其中建立一个函数,使之覆写超类中容容纳条件表达式的那个函数。将该子类相关的条件表达式分支复制到新函数中,并对它进行适当调整。
  • 重复上述过程,处理其他条件分支。
  • 在超类函数中保留默认情况的逻辑。或者,如果超类应该是抽象的,就把该函数声明为abstract,或在其中直接抛出异常,表明计算责任都在子类中。

引入特例(Introduce Special Case)

曾用名:引入Null对象(Introduce Null Object)

在这里插入图片描述

if(aCustomer === "unknown") customerName="occupant";
class UnknownCustomer {
	get name() {return "occupant";}
}

动机

一种常见的重复代码是这种情况:一个数据结构的使用者都在检查某个特殊的值,并且当这个特殊值出现时所做的处理也都相同。

处理这种情况的一个好办法是使用“特例”(SpecialCase)模式:创建一个特例元素,用以表达对这种特例的共用行为的处理。

特例有几种表现形式。如果只需要从这个对象读取数据,可以提供一个字面量对象(literal object),其中所有的值都是预先填充好的。如果除简单的数值之外还需要更多的行为,就需要创建一个特殊对象,其中包含所有共用行为所对应的函数。特例对象可以由一个封装类来返回,也可以通过变换插入一个数据结构。

做法

从一个作为容器的数据结构(或者类)开始,其中包含一个属性,该属性就是要重构的目标。容器的客户端每次使用这个属性时,都需要将其与某个特例值做比对。然后把这个特例值替换为代表这种特例情况的类或数据结构。

  • 给重构目标添加检查特例的属性,令其返回false。
  • 创建一个特例对象,其中只有检查特例的属性,返回true。
  • 对“与特例值做比对”的代码运用提炼函数(106),确保所有客户端都使用这个新函数,而不再直接做特例值的比对。
  • 将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生成。
  • 修改特例比对函数的主体,在其中直接使用检查特例的属性。
  • 测试。
  • 使用函数组合成类(144)或函数组合成变换(149),把通用的特例处理逻辑都搬移到新建的特例对象中。
  • 对特例比对函数使用内联函数(115),将其内联到仍然需要的地方。

引入断言(Introduce Assertion)

在这里插入图片描述

if (this.discountRate)
	base = base - (this.discountRate * base);
assert(this.discountRate>= 0);
if (this.discountRate)
	base = base - (this.discountRate * base);

动机

常常会有这样一段代码:只有当某个条件为真时,该段代码才能正常运行。这样的假设通常并没有在代码中明确表现出来,必须阅读整个算法才能看出。

断言是一个条件表达式,应该总是为真。如果它失败,表示程序员犯了错误。断言的失败不应该被系统任何地方捕捉。整个程序的行为在有没有断言出现的时候都应该完全一样。实际上,有些编程语言中的断言可以在编译期用一个开关完全禁用掉。

做法

  • 如果发现代码假设某个条件始终为真,就加入一个断言明确说明这种情况。

因为断言应该不会对系统运行造成任何影响,所以“加入断言”永远都应该是行为保持的。

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

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

相关文章

C#中List<T>的 AddRange方法使用说明

文章目录 C#中List<T>的 AddRange方法使用说明 C#中List的 AddRange方法使用说明 AddRange可以一次性添加集合A到当前集合B。 本身不能为空&#xff0c;但是可以保存null的数据。 List 中会保留集合中元素的顺序 将集合A添加到集合B的末尾 本身为null&#xff0c;使用 A…

国际测试委员会BenchCouncil首发“开源系统杰出成果榜” 百度飞桨上榜

&#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于恒川的日常汇报系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏C语言初阶、C…

轻量封装WebGPU渲染系统示例<14>- 多线程模型载入(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/ModelLoadTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调用隔离。 …

uniapp 解决H5跨域的问题

uniapp 解决h5跨域问题 manifest.json manifest.json文件中&#xff0c;点击“源码视图”,在此对象的最后添加以下代码&#xff1a; "h5" : {"devServer" : {"port" : 8080, //端口号"disableHostCheck" : true,"proxy" :…

作为一个初学者,入门大模型其实没那么难

在生成式 AI 盛行的当下&#xff0c;你是否被这种技术所折服&#xff0c;例如输入一段简简单单的文字&#xff0c;转眼之间&#xff0c;一幅精美的图片&#xff0c;又或者是文笔流畅的文字就展现在你的面前。 相信很多人有这种想法&#xff0c;认为生成式 AI 深不可测&#xf…

通过@ConfigrationProperties读取配置文件属性并赋值

这种设计使得 Anything 成为 Something 类的静态成员&#xff0c;因此不依赖于外部类的实例。静态内部类通常更独立&#xff0c;它们可以单独存在且无需引用外部类的实例。 如果将 Anything 类定义为非静态的内部类&#xff0c;它将依赖于 Something 类的实例。这意味着每个 S…

Sybase PowerDesigner 16.7.7.7536 Crack

Power Designer 是Sybase公司的CASE工具集&#xff0c;使用它可以方便地对管理信息系统进行分析设计&#xff0c;他几乎包括了数据库模型设计的全过程。利用Power Designer可以制作数据流程图、概念数据模型、物理数据模型&#xff0c;还可以为数据仓库制作结构模型&#xff0c…

安装Nginx并且添加fastDFS模块

安装Nginx并且添加fastDFS模块 因为这个模块必须在Nginx的安装的过程中才能添加&#xff0c;所有我们需要重新安装一个nginx&#xff0c;为了和原来已安装的Nginx进行区分&#xff0c;我们把新安装的Nginx取名为nginx_fdfs 将Nginx的tar包上传到Linux上 解压上传的Nginx文件 …

C++初阶(七)类和对象

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、流插入流提取1、流插入演示2、流提取演示3、cplusplus推荐 二、const成员三、取地址及con…

Ubuntu更新apt-get安装镜像源

前往清华开源镜像站获取镜像链接 修改镜像源&#xff0c;打开配置文件&#xff0c;将镜像源链接粘贴到配置文件 sudo vim /etc/apt/sources.list更新软件列表到本地 sudo apt-get update更新所有软件&#xff08;非必要&#xff09; sudp apt-get upgrade

Leetcode—100.相同的树【简单】

2023每日刷题&#xff08;十八&#xff09; Leetcode—100.相同的树 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool isSameTree(struct TreeNode* p, struc…

如何当好一面面试官?

最近公司恢复了招聘&#xff0c;重新启动了校招流程&#xff0c;现在都已经 11 月了&#xff0c;刚好赶上校招黄金期&#xff0c;不过提前批的时间已经过了&#xff0c;也是有点神奇。 公司停止招聘好久了&#xff0c;突然重启&#xff0c;赶忙赶急地让我们推荐面试官&#xf…

「Verilog学习笔记」异步复位的串联T触发器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 这道题目里我们有两个需要明确的点&#xff1a; 1. 什么是异步复位 2. 什么是串联的T触发器 关于第一个点&#xff0c;可以看我的这篇文章&#xff0c;已经整理好了&a…

[C++ 中]:6.类和对象下(static成员 + explicit +友元函数 + 内部类 + 编译器优化)

(static成员 explicit 友元函数 内部类 编译器优化&#xff09; 一.static 成员&#xff1a;1.概念引入&#xff1a;1-1&#xff1a;定义全局变量记录个数&#xff1f; 2.如果有多个类需要分开去记录类对象的个数&#xff1f;2-1&#xff1a;可不可以声明成员变量解决&#…

单片机温湿度-光照-DHT11-烟雾气体检测控制系统-proteus仿真-源程序

一、系统方案 本设计采用52单片机作为主控器&#xff0c;液晶1602显示&#xff0c;DHT11温湿度&#xff0c;光照、烟雾气体检测&#xff0c;按键设置报警阀值&#xff0c;蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 // // …

(免费领源码)php#MySQL软件测试文档管理系统28035-计算机毕业设计项目选题推荐

目 录 摘 要 Abstract 第1章 前 言 1.1 研究背景 1.2 开发意义 1.3 系统开发目标 第2章 系统开发环境 6 2.1 HTTP协议 6 2.2 HTML网页技术 6 2.3 B/S结构 6 2.4 PHP脚本语言 7 2.5 MySQL数据库 7 2.6 Apache简介 8 第3章 需求分析 3.1 需求分析 3.2 系统可…

ChatGPT商务英语邮件写作方法及案例模板

目录 1 前言 2 商务英语email写作技巧 3 商务email完整模板 4 求学科研email模板 5 工作email模板 6 商务沟通email模板 在现代商务环境中&#xff0c;通过电子邮件进行沟通已经成为一种普遍的方式。良好的商务英语邮件写作技巧和模板能够帮助我们有效地传达信息&#xf…

K8s-----pod详解

目录 一、Pod介绍 1、Pod的基础概念 2、pod定义&#xff08;资源清单&#xff09; 二、Pod相关实例 1、初始化容器&#xff08;init容器&#xff09; 2、镜像拉取策略&#xff08;image PullPolicy&#xff09; 3、pod容器重启策略&#xff08;restartPolicy&#xff09…

自定义类型枚举

目录 枚举类型枚举类型的声明扩展枚举类型的优点枚举的优点 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f978; C语言 &#x1f43f;️&#x1f43f;️&#x1f43f…

取消elementUI中table的选中状态和勾选状态赋值

一、取消所有选中 1、表格上绑定ref 2、清空用户选中数据 this.$refs.loopRef.clearSelection()二、勾选状态赋值 获取数据&#xff0c;flag为true则是选中状态&#xff0c;并将前面勾选框设为选中状态 this.listData.forEach(item> {if(row.flag1){this.$refs.loopRef.to…