面对过多的if-else,代码可能看起来比较冗余,搞不好又是一张被人到处转发的“我们项目几百几千行if”的图。但是经过各种设计模式和封装,if大大减少,但可读性可能稍微降低了,而且比较抽象。那我们应该如何取舍呢
抛开其他因素,如果if-else过多,可读性也许会好也可能会降低,可维护性也是或高或低;如果if-else少,代码高度抽象,可读性会低或者不变,可维护性可能会高也可能会低。这里大概可能会有几种情况
if平铺条件单一
这种情况,if精简不精简,可读性是不会变的,但是精简程度和可维护性是正相关的。至于为什么,看一下代码就可以感受到了
执行语句单一
if (a === 1) { console.log('this is 1')} else if (a === 2) { console.log('this is 2')} else if (a === 3) { console.log('this is 3')} // ...还有很多else { console.log('this is other')}
精简代码:
// 如果上面的if a是从1到10console.log(`this is ${a > 0 && a < 10 ? a : 'other'}`)// 如果上面的if a是从1到5和从7-10if ((a > 0 && a < 5) || (a > 7 && a < 10) { console.log(`this is ${a}`)} else { console.log('this is other')}// a取值灵活区间const area = [[1,2], [5,7], [10, 11]]console.log(`${area.find(([from, to]) => { return from <= a && a <= to}) ? a : 'other'}`)
这种情况,有没有精简,可读性都没有发生变化,如果是未精简的,写一堆if,你还是很容易看得出干啥。而可维护性就不一样了,要加一个或者多个数字,那么就要深入到某个if分支,一个个动手改,维护性低。但是,如果精简了的话,维护性大大增加,代码也简短。只需要寻找if的规律并封装所有的case即可,最后做到“条件驱动”
一些极简情况
有一些非常简单的情况,可以使用&&、||、三元解决
// beforeif (cb) { cb()}//aftercb && cb()// beforeif (!obj) { obj = {}}obj.a = 1//after(obj || obj = {}).a = 1// beforeif (type === true) { value = 1} else { value = 2}//aftervalue = type ? 1 : 2// beforeif (type === DEL) { this.delateData(id)} else { this.addData(id)}// afterthis[type === DEL ? 'delateData' : 'addData'](id)// or;(type === DEL ? this.delateData : this.addData)(id)// beforeif (a === 1 && a === 2 && a === 10) { console.log('ok')}// afterif ([1, 2, 10].includes(a)) { console.log('ok')}
条件单一、执行语句单一的情况,建议优化指数:★★★★★
执行语句复杂
if (a === 1) { console.log('this is 1')} else if (a === 2) { console.log('this is 二')} else if (a === 3) { console.log('this is three')} // ...还有很多else { console.log('this is other')}
精简代码:
const map = { 1: 'this is 1', 2: 'this is 二', 3: 'this is three', // ...很多}console.log(map[a] || 'this is other')
这种情况,和执行语句单一类似,也是可读性不变,代码减少了可维护性只是略好一点。通常的解决办法就是k-v映射了。加一个条件,就在map中加多一对k-v(由于条件处理复杂,所以条件上没有优化空间了,必须写出来)
这种场景,平时应该会比较常见转为switch。如果执行语句很复杂无规律,写k-v的缺陷就来了:一个key被迫对应一个callback函数,还会花时间斟酌传值问题,而且代码量也没发生变化,此时不建议优化
if (a === 1) { console.log('this is 1') alert(a * 100);} else if (a === 2) { console.log('this is 二') document.body.innerHTML = a + 1 + b}// afterconst map = { 1: (a) => { console.log('this is 1') alert(a * 100); }, 2: (a, b) => { console.log('this is 二') document.body.innerHTML = a + 1 + b }}map[a](a, b) // 代码量并没有减少也没有增强维护性复制代码
问题来了,条件单一,但处理语句有简单的也有复杂的怎么办?case by case,先归类再分情况,最终只会剩下少量if和switch的
小结: 条件单一、执行语句复杂的情况,有规律时建议优化指数:★★★,无规律时,建议指数:★
if平铺条件复杂
如果条件复杂,执行语句单一,那么条件可以通过&&、||、三元来简化,或者是平铺if-return,问题也迎刃而解。然而,条件复杂,执行语句大概率也是复杂的。
if (a === 1) { console.log(1);} else if (arr.length === 3) { alert('this is length 3 arr');} else if (a === 2 && b === 1) { console.log(2); console.info('haha');} else if (a === 2) { console.log(222); document.title = 'a = 2';} else if (arr.length) { console.error('arr is not empty');}
都没有规律可循,那么就真的没有进一步方案了。但是我们观察一下,发现一些条件是有交集的,如a === x,我们可以把这种类型的if抽出来:
const handleA = { 1: () => { console.log(1); }, 2: () => { if (b === 1) { console.log(2); console.info('haha'); } else { console.log(222); document.title = 'a = 2'; } }}const handleArrLen = { 3: () => { alert('this is length 3 arr'); }}if (handleA[a]) { handleA[a]()} else if (arr.length) { ;(handleArrLen[arr.length] || () => { console.log(222); document.title = 'a = 2'; })()}
这样子,可以把逻辑模块化,增加可读性和可维护性,但是牺牲了精简性。
注意,上面这样子条件模块化了,意味着同一类的条件都会归到一起。如果业务逻辑对if条件有严格要求的,比如一定要先判断a === 1,再看看arr.length === 3,再看a === 2 && b === 1,按照这样的顺序,那就不能这样做了
还有一种情况,就是if里面直接调用已经封装好的函数,没有其他语句(其实就相当于退化为条件复杂,执行语句简单了):
if (a === 1) { f1()} else if (arr.length === 3) { f2()} else if (a === 2 && b === 1) { f3()} else if (a === 2) { f4()} else if (arr.length) { f5()}
这种情况(前提条件,不会经常改这里,如果是经常改,还是会吐血的),减少if后也不赖:
const index = [a === 1, arr.length === 3, a === 2 && b === 1, a === 2, arr.length].findIndex(Boolean)if (index !== -1) { [f1, f2, f3, f4, f5][index]()}
如果所有的else if都是互斥的,没有交集,那么换成if-return更好
if (a) { // do xx about a return}if (b) { // do xx about b return}
小结:如果条件复杂,执行语句单一,建议优化指数: ★★★★★;如果执行语句也复杂,当条件可以模块化的且没有顺序要求,建议优化指数: ★★★★。当条件有严格顺序要求、无规律可循,不建议强行减少if-else
if条件有嵌套
嵌套实际上就是平铺的增强,平铺嵌套平铺,我们可以当作是多个if平铺条件复杂的情况来看。例如有如下代码,我们寻找一些规律来优化一下
if (id === 1) { console.log(1); if (type === 2) { console.log('type2'); } else { console.log('type3'); } } else if (id === 2) { console.log(2); if (type === 3) { console.log('id2 type3'); if (ext === 1) { console.log('ext1'); } } } else { console.log('other id'); }
根据id划分:牺牲了其他因素的可读性,但对于id的维护性增强了
const handleId = { 1: () => { console.log(1) console.log(type === 2 ? 'type2' : 'type3') }, 2: () => { console.log(2) // 这里建议优化指数为★★,可能可读性低,所以保持现状也行 // eslint一开,这套凉凉,必须写回普通if-else type === 3 && (console.log('id2 type3'), ext === 1) && console.log('ext1') }}handleId[type] ? handleId[type]() : console.log('other id')
如果此时根据type划分,那么难度大大增加了,这就是人人惧怕的重构了。如果后面业务逻辑,的确是以type为主导的,那重构也是早晚的事情了。所以,前期的设计以及产品逻辑,将会决定后面的维护舒服不舒服了
小结: if条件有嵌套情况,拆分if,其实就是平铺的if嵌套平铺的if,如果有规律可循,那么按照前面的平铺来减少if。如果没有规律、也不是逻辑侧重的点,那么就不建议减少if了
总结
- 条件简单,执行语句单一,强烈建议减少if-else来优化,用条件驱动结果(&& ||三元或者是自己写小逻辑)
- 条件简单,执行语句复杂,可保持现状或者换成switch,如果不复杂可以使用map映射
- 条件复杂,执行语句单一,强烈建议减少if-else来优化;如果执行语句也复杂,当条件可以模块化的且没有顺序要求,比较建议优化。当条件有严格顺序要求、无规律可循,不建议任何改动
- 嵌套if,拆分为平铺if来判断如何优化或者不改动
作者:lhyt
链接:https://juejin.im/post/5e86a0e56fb9a03c4a4966fb