if-elses十大优化方案,你用起来了吗?

1,019 阅读7分钟

一、背景

在产品快速迭代过程中,由于追求开发速度,忽略代码的可读性与扩展性,不合理的使用if-else条件判断会使我们的程序复杂度大大提升,同时也会使代码的可读性急速下降,后期维护难度也大大提高。

二、目的

优化是为了让代码可读性、可维护性更好;

  • 对老业务:在合适的基础上适当优化,核心是不影响已有功能
  • 对新业务:选择合适的策略,尽可能让 if 层级少

三、优化方案

3.1 单个 if 语句优化策略
3.1.1 逻辑与运算符

好处:代码在一行很清晰,简洁好读

// 优化前
if( falg ){
    someMethod()
}

// 优化后
falg && someMethod();

3.2 多个if语句优化策略
3.2.1 逻辑与取反逻辑

 // 优化前
 if (user && password) {
    // 逻辑处理
  } else {
   throw('用户名和密码不能为空!')
  }
    
    
// 优化后    
if (!user || !password) return throw('用户名和密码不能为空!')
// 逻辑处理


function handleEvent() {
  if (A) {
    doA
    if (B) {
      doB
    }
  }
}

function handleEvent() {
  if (!A || !B) {
    return;
  }
  ...
}

3.2.2 合并条件表达式

[解释]多个条件检查对应点的执行功能函数一致时,通过逻辑或把条件检查合并成一个表达式

// 优化前
 function getVipDiscount() {
        if(age<18){
            return 0.8;
        }
        if("深圳".equals(city)){
            return 0.8;
        }
        if(isStudent){
            return 0.8;
        }
        //do somethig
    }

// 优化后
 function getVipDiscount(){
        if(age<18|| "深圳".equals(city)||isStudent){
            return 0.8;
        }
        //doSomthing
    }




3.2.3 合并条件片段
// 优化前:每个条件分支有一些重复代码
var paging = function( currPage ){
    if ( currPage <= 0 ){
        currPage = 0;
        jump( currPage ); // 跳转
    }else if ( currPage >= totalPage ){
        currPage = totalPage;
        jump( currPage ); // 跳转
    }else{
        jump( currPage ); // 跳转
    }
};

//优化后:将分支重复的代码独立出来
var paging = function( currPage ){
    if ( currPage <= 0 ){
        currPage = 0;
    }else if ( currPage >= totalPage ){
        currPage = totalPage;
    }
    jump( currPage ); // 把jump 函数独立出来
};
3.3 单个 if else 语句优化策略
3.3.1 排非提前return策略

[解释]排非策略,先排除不满足大多数业务情况的 false 的情形,通过后再执行为 true 时的业务逻辑。

好处:可以干掉else,减少代码分支,提高代码的可维护性和可阅读性。

// 优化前
if(condition){
    //doSomething
}else{
    return ;
}

//优化后
if(!condition){
    return ;
}
//doSomething

3.3.2 使用三元运算符精简代码,提升可读性

case1: 优化前 VS 优化后

转存失败,建议直接上传图片文件

case2: 三元条件赋值

// 优化前
if( offsetTop < 0 ){
    this.titleFixed = true
} else {
    this.titleFixed = false
}

// 优化后

// 改成三元
(offsetTop < 0) ? this.titleFixed = true : this.titleFixed = false;
    
// 条件块里面的赋值情况是布尔值,所以可以更简化成:
this.titleFixed = offsetTop < 0;
3.4 多个 if else 嵌套优化策略
// “金字塔一样的if else嵌套”
if (result.data) {
  if (result.data.userInfo) {
    if (Array.isArray(result.data.userInfo.hobby)) {
      if (result.data.userInfo.hobby.length) {
        // 进行业务逻辑操作
      } else {
        return "hobby字段为空";
      }
    } else {
      return "hobby字段不是一个数组";
    }
  } else {
    return "userInfo字段不存在";
  }
} else {
  return "data字段不存在";
}
3.4.1 异常条件提前返回

[解释] 尽可能地维持正常流程代码在最外层,而不是在if内部,逻辑更清晰,一目了然,没有 if-else 嵌套难以理解的流程。

[Tips] 把外层if表达式进行反转

if (!result.data) return "data字段不存在";
if (!result.data.userInfo) return "userInfo字段不存在";
if (!Array.isArray(result.data.userInfo.boddy)) return "hobby字段不是一个数组";
if (result.data.userInfo.hobby.length) {
  // 进行业务逻辑操作
}
3.4.2 采取的try catch策略
try {
  // 有可能出现错误的代码写在这里
  if (result.data.userInfo.hobby.length) {
    // 进行业务逻辑操作
  }
} catch (error) {
  // 出错后的处理写在这里
}
3.4.3 可选链优化
// 可选链优化
if (result?.data?.userInfo?.hobby?.length) {
  // 进行业务逻辑操作
}
3.5 多个else if分支优化

多个else if条件判断代码,随着逻辑复杂性的增加,else if的代码将变得越来越肿,导致重构困难。

if (this.type === 'A') {
    this.handleA();
   } else if (this.type === 'B') {
     this.handleB();
   } else if (this.type === 'C') {
     this.handleC();
   } else if (this.type === 'D') {
     this.handleD();
   } else {
     this.handleE();
   }
3.5.1 switch-case

好处:不同的条件分支之间没有嵌套,并且彼此独立, 逻辑很清楚。

switch(val){
    case 'A':
      handleA()
      break
    case 'B':
      handleB()
      break
    case 'C':
      handleC()
      break
    case 'D':
      handleD()
      break
  }
3.5.2 Key-value 对象枚举

好处:将条件与特定操作相关联的键值,减少代码体积


let enums = {
  'A': handleA, 
  'B': handleB,
  'C': handleC,
  'D': handleD,
  'E': handleE
}
function action(val){
  let handleType = enums[val]
  handleType()
}




3.5.3 Map构建键/值对

let enums = new Map([
  ['A', handleA],
  ['B', handleB],
  ['C', handleC],
  ['D', handleD],
  ['E', handleE]
])

function action(val){
  let handleType = enums(val)
  handleType()
}

对比Object的话,Map具有许多优点
  对象的键只能是字符串或符号,而Map的键可以是任何类型的值。
  我们可以使用Map size属性轻松获取Map的键/值对的数量,而对象的键/值对的数量只能手动确定。
  具有极快的查找速度。

多层复杂条件,Map语句优势就更明显了:
if (mode == 'kline') {
    if (this.type === 'A') {
        this.klineA()
    } else if (this.type === 'B') {
        this.klineB()
    } else if (this.type === 'C') {
        this.klineC()
    } else if (this.type === 'D') {
        this.klineD()
    }
} else if ((mode = 'depth')) {
    if (this.type === 'A') {
        this.depthA()
    } else if (this.type === 'B') {
        this.depthB()
    } else if (this.type === 'C') {
        this.depthC()
    } else if (this.type === 'D') {
        this.depthD()
    }
}

// 优化后:把条件判断拼接成字符串作为键,每个分支下的处理逻辑作为值,在使用时传入参数使用 Map 查询,这种方法非常适合于二元或多元条件判断。
let enums = new Map([
  ['kline_A', handleKlineA],
  ['kline_B', handleKlineB],
  ['kline_C', handleKlineC],
  ['kline_D', handleKlineD],
  ['kline_E', handleKlineE],
  ['depth_A', handleDepthA],
  ['depth_B', handleDepthB],
  ['depth_C', handleDepthC],
])

function action(mode, type){
  let key = `${mode}_${type}`
  let handleType = enums(key)
  handleType()
}
3.6 使用数组新特性优化逻辑判断
使用 includes 处理多重条件
// 优化前
if( code === '202' || code === '203' || code === '204' ){
    someMethod()
}

// 优化后
if( ['202','203','204'].includes(code) ){
    someMethod()
}

3.7 策略模式优化分支逻辑

策略模式是一种对象行为型模式:定义一系列算法(对应各个业务逻辑),将每一个算法封装起来。每一个策略都会有一个标识名,根据策略选择要执行的函数,其实就是使用key寻找value,然后执行vlaue的过程。

//优化前
function percent50(price) {
  // 五折的算法
}

function percent70(price) {
  // 七折的算法
}

function percent90(price) {
  // 九折的算法
}

function calculatePrice(price) {
  if (五折的商品) {
    return percent50(price);
  }

  if (七折的商品) {
    return percent50(price);
  }

  if (九折的商品) {
    return percent50(price);
  }
}

calculatePrice(price);

//优化后
// 策略类
let strategy = {
  // 5折
  percent50(price) {
    return price * 0.5;
  },
  // 7折
  percent70(price) {
    return price * 0.7;
  },
  // 9折
  percent90(price) {
    return price * 0.9;
  },
  // 满300-50
  fullReduce50(price) {
    // 执行满300-50的业务逻辑
  },
  // 满300-80
  fullReduce80(price) {
    // 执行满300-80的业务逻辑
  },
  // vip用户五折
  vip50(price) {
    // vip用户五折的业务逻辑
  },
};

// 调用策略类中的方法
// 环境类
function calculatePrice(strategyName, price) {
  return strategy[strategyName] && strategy[strategyName](price);
}
console.log(calculatePrice("percent50", 100)); // 50



3.8 封装内部函数

【解释】把 if-else 内的代码都封装成一个公共函数,函数的好处是屏蔽内部实现,缩短 if-else 分支的代码,代码结构和逻辑上清晰,能一下看出来每一个条件内做的功能。

// 优化前
toothAppointCancel(res: any) {
  if (TYPE === XXXX || TYPE === YYYY) {
      history.replace({
      pathname: `/appointmentdetail?jdAppointmentId=${res.msg}&type=${TYPE}`,
    });
    } else {
    window.location.reload();
  }
}

//优化后
toothAppointCancel(res: any) {
  const isTooth = () => {
    return TYPE === XXXX || TYPE === YYYY;
  };
  const handleToothUpdate = () => {
    history.replace({
    pathname: `/appointmentdetail?jdAppointmentId=${res.msg}&type=${TYPE}`,
  });
  const handleReload = () => {
    window.location.reload();
  };
  isTooth() ? handleToothUpdate() : handleReload();
}



3.9 用多态替换switch场景

拓展一下思路啦,当遇到有一个基础逻辑,基于这个基础逻辑又有一些变体,那么我们可以把基础逻辑放到超类,把各种变体逻辑放到各个子类

// 优化前
fixAppointToUrl: (obj: any, type: number) => {
  let url = '';
  switch (type) {
    case TYPEA:
      ...
      url = "B_JD_PE_FIX_URL";
      break;
    case TYPEB:
      ...
      url = "BC_TOOTH_FIX_URL";
      break;
    case TYPEC:
      ...
      url = "C_BACTERIN_FIX_URL";
      break;
    case TYPED:
      ...
      url = "B_TOOTH_FIX_URL";
      break;
    default:
      ...
      url = "DEFAULT_FIX_URL";
  }
  return url;
}

repeatAppointToUrl: (obj: any, type: number) => {
   let url = '';
   switch (type) {
   case TYPEA:
      ...
      url = "B_JD_PE_REPEAT_URL";
      break;
   case TYPEB:
      ...
      url = "BC_TOOTH_REPEAT_URL";
      break;
   case TYPEC:
      ...
      url = "C_BACTERIN_REPEAT_URL";
      break;
   case TYPED:
      ...
      url = "B_TOOTH_REPEAT_URL";
      break;
   default:
      ...
      url = "DEFAULT_REPEAT_URL";
   }
   return url; 
}

// 优化后
class HandleUrl {
  get fixUrl(){
    return 'DEFAULT_FIX_URL';
  }
  get repeatUrl(){
    return 'DEFAULT_REPEAT_URL';
  }
}
class TYPEA extends HandleUrl{
   get fixUrl(){
    return 'B_JD_PE_FIX_URL';
   }
   get repeatUrl(){
    return 'B_JD_PE_REPEAT_URL';
   }
}
class TYPEB extends HandleUrl{
   get fixUrl(){
    return 'BC_TOOTH_FIX_URL';
   }
   get repeatUrl(){
    return 'BC_TOOTH_REPEAT_URL';
   }
}
class TYPEC extends HandleUrl{
   get fixUrl(){
    return 'C_BACTERIN_FIX_URL';
   }
   get repeatUrl(){
    return 'C_BACTERIN_REPEAT_URL';
   }
}
class TYPED extends HandleUrl{
   get fixUrl(){
    return 'B_TOOTH_FIX_URL';
   }
   get repeatUrl(){
    return 'B_TOOTH_REPEAT_URL';
   }
}

const typed = new TYPED();
console.log(typed.fixUrl)


四、总结

  1. 更少的嵌套,尽早return
  2. 倾向于使用对象或使用 map 结构来优化if else,而不是 Switch语句 。
  3. 多重判断时使用Array.includes
  4. 对 所有/部分 判断使用 Array.every & Array.some
  5. 使用默认参数和解构 。
  6. 当一个项目中需要大量算法,大量匹配模式时可以考虑使用策略模式。

五、参考文献

[1]【前端词典】你或许可以这样优化 if else 结构

[2] 代码中大量的if/else,你有什么优化方案?

[3] if-else重构优化

[4] if-else逻辑判断优化

[5] 大量if-else的金字塔代码该如何优化

[6] juejin.cn/post/717214…