避免使用过多if-else语句

“ 在项目开发中,由于之前项目难以维护,从而导致我们使用过多的if-else,这样导致写出来的代码又臭又长,给下面接手的同事留下了巨多问题,并且也加大了排查周期,接下来我们看看如何优化if else分支过多的问题。”

1. 三元运算符

其实在很多简单的判断情况下,我们可以放心的使用三元运算符去替代if-else语句,注意:三元运算符也不能嵌套太多层,因为它不具有较高的可读性,推荐一层嵌套是最优的。
举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 三元运算符使用的场景:条件赋值、递归等
// 条件赋值
const ageResult = (age) => {
return age >= 18 ? '成年' : '未成年'
}
const printContent = ageResult(18) // 成年

// 递归
// 求整数和(0-n之间)
let sum = 0
const toSum = (n) => {
sum += n
return n >= 2 ? toSum(n - 1) : sum
}
console.log(toSum(5)) // 15

2. 短路运算

其实在js语法中的或(||)运算,也就是我们说的短路运算,在一些比较简单的判断中也是可以替代if-else语句的。
举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 逻辑或运算使用:如果左边的判断为true,则返回左边式子的值;
// 否则返回右边式子的值。
// 优化之前的代码
let sum
const num1 = 1
const num2 = 2
if (num1) {
sum = num1
} else {
sum = num2
}

// 优化之后的代码
const sum = num1 || num2

3.switch语句

在上面说的三元运算符和短路运算之后,它们两个代码虽然简洁,但是它们都只能用于简单的判断,如果当判断复杂且条件多时,就无法使用了。
这个时候我们就可以使用switch语句,switch语句对比if-else语句,它的可读性是比较高的,但是它写起来的话就比较麻烦的。
举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 代码没有改造之前,使用if-else语句
let answer
const val = '2'
if (val === '1') {
answer = 'a'
} else if (val === '2') {
answer = 'b'
} else {
answer = 'c'
}
console.log(answer) // 'b'

// 代码被改造后,使用switch语法
let answer
const val = '2'
switch (val) {
case '1':
answer = 'a'
break
case '2':
answer = 'b'
break
default:
answer = 'c'
}
console.log(answer) // 'b'

4. 对象配置/策略模式

对象配置/策略模式定义:根据不同的参数使用不同的数据/算法/函数,换句话说就是把他们一个个的封装起来,目的就是把算法的实现和使用分开来。
实现:通过一个对象或者其他hash数据结构,来映射不同的策略。
主要解决的问题:在有多种算法相似的情况下,使用if-else所带来的复杂和难以维护。
优点:(1)策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句(避免使用多重条件判断);(2)策略模式提供了开放-封闭原则,使代码更容易理解和扩展(扩展性好);(3)策略模式中的代码可以复用(复用性好);
缺点:(1)策略类会增多;(2)所有策略类都需要对外暴露。
使用场景:(1)需要动态地根据行为改变运行方法;(2)为了减少代码,同时为了让代码具有更好的可读性;(3)需要对策略进行统一管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 对象配置
const val = '2'
let result = {
1: 'a',
2: 'b',
default: 'c',
}
console.log(result[val]) // 'b'

// 策略模式
// 举一个例子:公司的年终奖根据员工的工资基数和绩效等级来发放的。
// 例如,绩效为A的人年终奖有4倍工资,绩效为B的有3倍,绩效为C的只有2倍。
// 代码没有改造之前
const calculateBonus = (performanceLevel, salary) => {
if (performanceLevel === 'A'){
return salary * 4
}
if (performanceLevel === 'B'){
return salary * 3
}
if (performanceLevel === 'C'){
return salary * 2
}
}
calculateBonus('A', 10000) // 输出:40000

上面计算年终奖例子中,我们可以看到calculateBonus函数整体计算是很庞大的,然后逻辑分支都包括在if-else语法中,如果当我们又增加了一种新的绩效等级D或者又更改等级系数时,那么又需要去读取calculateBonus函数然后做对应的修改。

1
2
3
4
5
6
7
8
9
10
// 代码改造之后之使用策略模式
let strategies = new Map([
['A', 4],
['B', 3],
['C', 2]
])
const calculateBonus = (performanceLevel, salary) => {
return strategies.get(performanceLevel) * salary
}
calculateBonus('A', 10000) // 输出:40000

在js中,函数是一等公民,策略模式的使用常常隐藏在高阶函数中,稍微变换下上述形式,可以发现我们平时已经在使用这种模式了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 创建策略对象,这个策略对象中包含了不同的计算策略,其实也是对象的方法;
// 我们将不同的计算方法放到了一起作为对象的方法,就相当于把他们组合放置
// 在一个篮子里,之后需要哪个就去拿哪个
const strategies = {
A: function (salary) {
return salary * 4
},
B: function (salary) {
return salary * 3
},
C: function (salary) {
return salary * 2
},
}
// 创建一个计算函数
// 相比使用类的方式,使用策略模式会更清晰明了。再相比最开始直接使用
// if-else的方法,省去了条件判断,增加了程序的弹性、易扩展。
function calculateBouns (performanceLevel, salary) {
return strategies[performanceLevel](salary);
}
console.log(calculateBouns('A', 10000)) // 40000

// 对上面我们还可以使用隐藏在策略模式下的高阶函数
const s1 = (salary) => {
return salary * 4
}
const s2 = (salary) => {
return salary * 3
}
const s3 = (salary) => {
return salary * 2
}
const calculateBonus = (func, salary) => {
return func(salary)
}
calculateBonus(s1, 10000) // 40000

5. 总结

我们再结合上述代码来看策略模式的定义:其实就是定义一系列的算法,然后把它们各自封装成策略类,算法就被封装在策略类内部的方法里,供外部直接调用而不用关心策略的具体实现。
策略模式程序至少由两部分组成:
(1)一组策略类,策略类封装了具体的算法,并负责具体的计算过程(上面例子中就是我们定义的strategies来充当);
(2)环境类Context,也叫做Context上下文,Context接受客户的请求,随后把请求委托给某一个策略类,要做到这点,说明Context中要维持对某个策略对象的引用(上面例子中Context上下文由calculateBouns充当)。
其实策略模式的实现并不复杂,关键是如何从策略模式的实现背后,找到封装变化、委托和多态性这些思想的价值。

6. 延展

我们再来举一个表单的例子,下面表单验证的规则如下:
(1)用户名不为空
(2)密码长度不能少于六位
(3)手机号码必须符合样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<form action="http://xxx.com" id="registerForm" method="post">
输入用户名:<input type="text" name="userName" />
输入密码:<input type="text" name="password" />
输入手机号码:<input type="text" name="phoneNumber" />
<button>提交</button>
</form>

// 传统方式验证表单,可以看到验证算法通过if语句来实现,直接包含在表单提交方法里,
// 如果后期增加规则的话,还要来改动表单提交方法中的表单验证这一段代码,扩展性极差
const registerForm = document.getElementById('registerForm');
registerForm.onsubmit = () => {
if (registerForm.userName.value === '') {
alert('用户名不能为空');
return false;
}
if (registerForm.password.value.length < 6) {
alert('密码长度不能少于6 位');
return false;
}
if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert('手机号码格式不正确');
return false;
}
}

优化之后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 接下来我们通过策略模式来优化代码
// 将验证规则封装成策略对象
const strategies = {
isNonEmpty: (value, errorMsg) => { // 不为空
if (value === '') {
return errorMsg;
}
},
minLength: (value, length, errorMsg) => { // 限制最小长度
if (value.length < length) {
return errorMsg;
}
},
isMobile: (value, errorMsg) => { // 手机号码格式
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
}

// Validator仅仅负责用户的请求,并将它委托给strategies策略对象
class Validator {
constructor() {
this.cache = [] // 保存校验规则
}
add(dom, rule, errorMsg) {
const ary = rule.split(':')
this.cache.push(() => {
const strategy = ary.shift()
// 把input的value添加进参数列表
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
start() {
for (let i = 0; i < this.cache.length; i++) {
const validatorFunc = this.cache[i]
const msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
if (msg) { // 如果有确切的返回值,说明校验没有通过
return msg;
}
}
}
}

const validataFunc = function () {
const validator = new Validator()
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位')
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确')
const errorMsg = validator.start() // 获得校验结果
return errorMsg; // 返回校验结果
}

const registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
// 如果errorMsg 有确切的返回值,说明未通过校验
const errorMsg = validataFunc()
if (errorMsg) {
alert(errorMsg)
return false // 阻止表单提交
}
}

代码优化之后,我们是将验证规则封装到了strategies策略对象中,各个验证规则的实现都是在这个策略对象中实现的;然后定义了一个Validator类,这个类是用来接收用户的请求的,然后将其委托给strategies策略对象去执行验证;validataFunc方法中是定义用户请求发送的,在validataFunc方法中我们实例化了Validator类。

提升代码可读性,减少 if-else 的几个小技巧

干掉过多if-else,试试状态模式!

【JavaScript设计模式】策略模式