if/else 的重构方案

if/else 的重构方案

背景

日常开发中总会遇到条件判断,而写程序归根结底也是各种条件判断来控制程序执行的。最常用也是最信手拈来的就是 if/else, 或者优雅点会用到 switch 来实现多个条件的判断。当然 if/else 的使用在开发人员当中也时常会引起热烈讨论,到底条件判断怎么写会更优雅效率更高,也在每个程序编写者心中有着各自不同的认识和定义。

接下来做了一些条件判断的使用场景和优化方案的介绍,目的不是批判某种用法,仅是把更多的可使用方案介绍给大家。

介绍

总结前置:

  1. if...
  2. if...else...
  3. if...else if...else
  4. switch / case
  5. JavaScript 三元运算
  6. 短路运算符
  7. Logic + Control (数据结构 + 算法)方式

首先,明确两个最基本的条件判断,if/else 和 switch/case 的用法。


1. if 应用场景:单个分支执行中插入额外执行 如下:

var n = 1
console.log('n 等于', n )
if ( n < 10 ) {
	console.log('n 小于10 ')
}
n++
console.log('n 等于 ', n )

# 输入出结果:
n 等于 1
n 小于10 
n 等于  2

# 当n=20时,输出结果:
n 等于 20
n 等于 21

工作原理:

     if 只有当指定条件为 Boolean值 为 true 时,该语句才会执行代码。

应用场景:

     a. 值匹配

     b. 区间判断


2. if/else 应用场景:两个分支的判断 如下:

var n = true
if (n) {
  console.log('n 等于 true')
}
else {
  console.log('n 不等于 true')
}

3. if/else if/else 应用场景:至少三个分支的判断 如下:

a.值匹配:

var n = 'c'

if (n==='a') {
    console.log('---> a')
}
else if (n==='b') {
    console.log('---> b')
}
else {
    console.log('---> c')
}

b.区间判断

var n = 6
if ( n < 5 ) {
    console.log('n 小于 5')
}
else if (n >= 5 && n < 10) {
    console.log('n 小于 10,并且不小于 5')
}
else {
    console.log('n 不小于 10')
}

应用场景:

     a. 值匹配

     b. 区间判断


4. switch/case 应用场景:多分支判断 如下:

var n = 'c'

switch (n) {
    case 'a':
        console.log('---> a')
        break
    case 'b':
        console.log('----> b')
        break
    default:
        console.log('----> default')
}

工作原理:

     首先设置表达式 n(通常是一个变量)。随后表达式的值会与结构中的每个 case 的值做比较。如果存在匹配,则与该 case 关联的代码块会被执行。请使用 **break** 来阻止代码自动地向下一个 case 运行。

应用场景:

     a. 值匹配

其次, 除了上述常用的两种条件判断方式,还有一个不得不说的判断方式,JavaScript三目运算


5. JavaScript三元运算的应用场景:两个分支判断的快捷写法 如下:

var n = 5
n > 3 ? alert("5大于3") : alert("5小于3");

工作原理:

     (condition1) ? ture-doing : else-doing; 语句在条件为 true 时执行代码,在条件为 false 时执行其他代码。

应用场景:

     a. 值匹配

     b. 区间判断

可以看出,三目运算可以替代 if...else... 的使用,但与 if..else... 稍有区别,if/else没有返回值,而三元运算是有返回值的,如下:

// if...else..
var n=1;
if(n>1){
    n=0;
}else{
    ++n;
}
console.log(n);
#输出结果:2

// 三目运算
var n=1;
n = n>1?0 : ++n; 
console.log(n); 
#输出结果为:2

6. 短路运算符 如下:

var a = true
var b = true
var c = function() {
  console.log('c函数执行了!')
  return false
}
var d = function() {
  console.log('d函数执行了!')
  return true
}

var e = function() {
    console.log('e函数执行了!')
    return true
}

a && b && c() && d() || e()

#输出结果为:
c函数执行了!
e函数执行了!

应用场景:

     Boolean 值 的连续判断,“&&” 只要不遇到 false 就会一直往下执行,遇到 false 会去执行 “||”后边的执行语句

值得注意的是:

     1. 如果执行的是个函数,没有返回值,那么默认是 return false
     2. 判断值是 0 和 1 时,0相当于false,1相当于true


7. Logic + Control (数据结构+算法),如下:

const sendLog = function(LogName) {
    console.log( 'log result ----> ', LogName )
}

const jumpTo = function (pageName) {
    console.log( '要去到的页面 ----> ', pageName )
}

const actions = {
    '1': ['processing', 'IndexPage'],
    '2': ['fail', 'FailPage'],
    '3': ['fail', 'FailPage'],
    '4': ['success', 'SuccessPage'],
    '5': ['cancel', 'CancelPage'],
    'default': ['other', 'Index']
}

const clickHandler = (status) => {
    let action = actions[status] || actions['default'], 
      LogName = action[0],
      pageName = action[1]
    sendLog(LogName)
    jumpTo(pageName)
}

应用场景:

     判断条件(即:状态值)有统一明确的匹配规则,如:可枚举型,可 switch/case 判断 等有明确的状态值的场景。


先举个一个多层 if/else 嵌套的代码重构的例子,让大家理解其使用场景(代码取于网上 稍作修改):


案例:

const sendLog = function(LogName) {
    console.log( 'log result ----> ', LogName )
}

const jumpTo = function (pageName) {
    console.log( '要去到的页面 ----> ', pageName )
}

const clickHandler = (status) => {
  if(status === 1) {
    sendLog('processing')
    jumpTo('IndexPage')
  } else if(status === 2) {
    sendLog('fail')
    jumpTo('FailPage')
  } else if(status === 3) {
    sendLog('fail')
    jumpTo('FailPage')
  } else if(status === 4) {
    sendLog('success')
    jumpTo('SuccessPage')
  } else if(status === 5) {
    sendLog('cancel')
    jumpTo('CancelPage')
  } else {
    sendLog('other')
    jumpTo('Index')
  }
}


优化1:

swich/case 方式重构:

const clickHandler = (status) => {
  switch (status) {
    case 1:
      sendLog('processing')
      jumpTo('IndexPage')
      break
    case 2:
    case 3:
      sendLog('fail')
      jumpTo('FailPage')
      break
    case 4:
      sendLog('success')
      jumpTo('SuccessPage')
      break
    case 5:
      sendLog('cancel')
      jumpTo('CancelPage')
      break
    default:
      sendLog('other')
      jumpTo('Index')
  }
}

注意: case 2case 3 的逻辑一样,可以省略写法。

代码优雅了些,并清洗了许多。


优化2:

logic + control 方式 重构:

const actions = {
    '1': ['processing', 'IndexPage'],
    '2': ['fail', 'FailPage'],
    '3': ['fail', 'FailPage'],
    '4': ['success', 'SuccessPage'],
    '5': ['cancel', 'CancelPage'],
    'default': ['other', 'Index']
}

const clickHandler = (status) => {
    let action = actions[status] || actions['default'], 
      LogName = action[0],
      pageName = action[1]
    sendLog(LogName)
    jumpTo(pageName)
}

代码清爽了许多,并去除了重复代码的编写。


优化3:

logic + control 方式,加Map 对象的应用:

const actions = new Map([
  ['1', ['processing', 'IndexPage']],
  ['2', ['fail', 'FailPage']],
  ['3', ['fail', 'FailPage']],
  ['4', ['success', 'SuccessPage']],
  ['5', ['cancel', 'CancelPage']],
  ['default', ['other', 'Index']]
])

const clickHandler = (status) => {
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])
}

Map对象的优势,是 key 值可以是任意值(Boolean, String, Object, 正则 等)。


理解与启发:一个多层 if/else 嵌套 重构的例子

一个两层的if/else嵌套,根据用户身份 和 status 做不同的处理,理解整个重构的过程,你会获益匪浅

例子:

const clickHandler = (status, identity) => {
  if(identity == 'guest') {
    if(status === 1) {
      // to do something
    } else if (status === 2) {
      // to do something
    } else if (status === 3) {
      // to do something
    } else if (status === 4) {
      // to do something
    } else if (status === 5) {
      // to do something
    } else {
      // to do something
    }
  } else if(identity == 'master') {
    if(status === 1) {
      // to do something
    } else if (status === 2) {
      // to do something
    } else if (status === 3) {
      // to do something
    } else if (status === 4) {
      // to do something
    } else if (status === 5) {
      // to do something
    } else {
      // to do something
    }
  }
}

缺点:代码冗长,逻辑梳理起来较为复杂,后续维护困难:


优化 1:

const actions = new Map([
  ['guest_1', () => {/* to do something */}],
  ['guest_2', () => {/* to do something */}],
  ['guest_3', () => {/* to do something */}],
  ['guest_4', () => {/* to do something */}],
  ['guest_5', () => {/* to do something */}],
  ['master_1', () => {/* to do something */}],
  ['master_2', () => {/* to do something */}],
  ['master_3', () => {/* to do something */}],
  ['master_4', () => {/* to do something */}],
  ['master_5', () => {/* to do something */}],
  ['default', () => {/* to do something */}],
])

上述代码的逻辑:

  • 把两个条件拼接成字符串
  • 以拼接的条件字符串作为key,以处理函数作为值的Map对象进行查找并执行

优化 2:

也可以用对象的方式来实现,这也是我们常用的方式:

const actions = {
  'guest_1': () => {/* to do something */},
  'guest_2': () => {/* to do something */},
  'guest_3': () => {/* to do something */},
  'guest_4': () => {/* to do something */},
  'guest_5': () => {/* to do something */},
  'master_1': () => {/* to do something */},
  'master_2': () => {/* to do something */},
  'master_3': () => {/* to do something */},
  'master_4': () => {/* to do something */},
  'master_5': () => {/* to do something */},
  'default': () => {/* to do something */}
}

优化 3:

当然觉得拼接字符串的方式不够优雅,也可以用对象作为key, 如下:

const actions = new Map([
    [{indentity: 'guest', status: 1}, () => {/* to do something */}],
    [{indentity: 'guest', status: 2}, () => {/* to do something */}],
    [{indentity: 'guest', status: 3}, () => {/* to do something */}],
    [{indentity: 'guest', status: 4}, () => {/* to do something */}],
    [{indentity: 'guest', status: 5}, () => {/* to do something */}],
    [{indentity: 'guest', status: 'default'}, () => {/* to do something */}],
    [{indentity: 'master', status: 1}, () => {/* to do something */}],
    [{indentity: 'master', status: 2}, () => {/* to do something */}],
    [{indentity: 'master', status: 3}, () => {/* to do something */}],
    [{indentity: 'master', status: 4}, () => {/* to do something */}],
    [{indentity: 'master', status: 5}, () => {/* to do something */}],
    [{indentity: 'master', status: 'default'}, () => {/* to do something */}],
])

const clickHandler = (identity, status) => {
  let action = [...actions].filter((key, value) => {
    key.identity === identity && key.status === status
  })
  action.forEach(([key, value]) => {
    value.call(this)
  })
}

MapObject 的区别在于,Mapkey值可以是任意数据类型, 这也正是 Map 对象的优势。


优化 4:

Map 加 正则表达式匹配的方式:

function A () {
  // to do something
}

function B () {
  // to do something
}

function C () {
  // to do something
}

const actions = new Map([
  [/^guest_[1-4]$/, fucnctionA],
  [/^master_[1-5]$/, functionB],
  [/^guest_.$/, functionC]
])

const clickHandler = (identity, status) {
  let action = [...actions].filter((key, value) => {key.test(`${identity}_${status}`)})
  action.forEach(([key, value]) => {value.call(this)})
}

个人总结:

几种条件判断的最佳使用场景:

  1. 单分支插入执行块

    // a. if 方式
    if (【条件】) { doSomething...}
    
    // b.短路运算符方式
    【条件】 && (doSomething...)
    

  1. 两个分支的执行

    // a. if...else... 方式
    if (【条件】) {
      // do something ...
    }
    else {
      // do something ...
    }
    
    // b. 三元运算符
    【条件】 ? (doSomethingA...) : (doSomethingB...)
    
    // c. 短路运算符方式
    【条件】 && (doSomethingA...) || (doSomethingB)
    

  1. 三个分支的执行

    // a. if...else if...else... 方式
    if (【条件1】) {
    	// do something ...
    }
    else if (【条件2】) {
    	// do something ...
    }
    else {
    	// do something ...
    }
    
    // b. 三元运算符 (勉强接受)
    【条件1】 ? (doSomethingA...) : (【条件2 ? (doSomethingB...) : (doSomethingC...)】)
    
    // c.短路运算符 方式(不推荐)
    【条件1】&& (doSomethingA...) || (【条件2】&& (doSomethingB..., true)) || (doSomethingC...)
    

  1. 多个分支的执行

    // a. if...else if...else... (不推荐)
    
    // b. switch...case...(确定的枚举个数,并且数量不小于3个,推荐使用,并且 可枚举个数越多越不推荐)
    switch (status) {
    	case 【status值1】:
    		// do something ...
    		break
    	case 【status值2】:
    		// do something ...
    		break
    	case 【status值3】:
    		// do something ...
    		break
    	default:
    		// do something ...
    		break
    }
    
    // c. logic + control(推荐)
    const actions = new Map([
    	[【key1】, 【value1】],
    	[【key2】, 【value2】],
    	[【key3】, 【value3】],
    	[【key4】, 【value4】],
    	...
    ])
    
    const handler = function(【条件】) {
    	let action = [...actions].filter((key, value) => { key 匹配 【条件】 })
    	action.forEach(([key, value]) => {value.call(this)})
    }
    
    

【注】:如果判断的条件是区间值,可先将区间进行分类划分后再使用 b 或 c 的判断方式


参考:
    一个多层 if / else 嵌套的代码重构案例(JavaScript)

posted @ 2021-05-17 18:57  驸马爷  阅读(755)  评论(0编辑  收藏  举报