面试题如何避免使用过多的 if else?

Posted 前端技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题如何避免使用过多的 if else?相关的知识,希望对你有一定的参考价值。

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

一、引言

相信大家听说过回调地狱——回调函数层层嵌套,极大降低代码可读性。其实,if-else层层嵌套,如下图所示,也会形成类似回调地狱的情况。

当业务比较复杂,判断条件比较多,项目进度比较赶时,特别容易使用过多if-else。其弊端挺多的,如代码可读性差、代码混乱、复杂度高、影响开发效率、维护成本高等。

因此,我们在日常编码时,有必要采取一些措施避免这些问题。本文的初衷不是建议大家完全不用if-else,而是希望我们能够在学会更多解决方案后更优雅地编码。


二、8种if-else的优化/替代方案

1. 使用排非策略:!、!!

逻辑非(logic NOT),是逻辑运算中的一种,就是指本来值的反值。

当你想这么写时……

1、判断是否为空
if(value === null || value === NaN || value === 0 || value === ''|| value === undefined   )

……


2、判断是否数组是否含有符合某条件的元素
const name = arr.find(item => item.status === 'error')?.name;
if(name !== undefined && name !== '')
……

复制代码

不妨尝试这么写:

1、判断是否为空
if(!value)……

2、判断是否数组是否含有符合某条件的元素
if(!!arr.find(item => item.status === 'error')?.name)……
复制代码

2. 使用条件(三元)运算符: c ? t : f

三元运算符: condition ? exprIfTrue : exprIfFalse; 如果条件为真值,则执行冒号(:)前的表达式;若条件为假值,则执行最后的表达式。

当你想这么写时……

let beverage = '';
if(age > 20)
beverage = 'beer';
 else 
beverage = 'juice';

复制代码

不妨尝试这么写:

const beverage = age > 20 ? 'beer' : 'juice';
复制代码

tips: 建议只用一层三元运算符,多层嵌套可读性差。


3. 使用短路运算符:&&、 ||

  • && 为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值;

  • || 为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值。

当你想这么写时……

if (isOnline)
    makeReservation(user);
    
复制代码

不妨尝试这么写:

 isOnline && makeReservation(user);
复制代码

4. 使用 switch 语句

当你想这么写时……

let result;
    if (type === 'add')
    result = a + b;
     elseif(type === 'subtract')
    result = a - b;
     elseif(type === 'multiply')
    result = a * b;
     elseif(type === 'divide')
    result = a / b;
     else 
    console.log('Calculation is not recognized');
    
复制代码

不妨尝试这么写:

let result;
switch (type) 
   case'add':
     result = a + b;
     break;
   case'subtract':
     result = a - b;
     break;
   case'multiply':
     result = a * b;
     break;
   case'divide':
     result = a / b;
     break;
   default:
    console.log('Calculation is not recognized');

复制代码

个人认为,对于这类比较简单的判断,用switch语句虽然不会减少代码量,但是会更清晰喔。


5. 定义相关函数拆分逻辑,简化代码

当你想这么写时……

functionitemDropped(item, location) 
    if (!item) 
        returnfalse;
     elseif (outOfBounds(location) 
        var error = outOfBounds;
        server.notify(item, error);
        items.resetAll();
        returnfalse;
     else 
        animateCanvas();
        server.notify(item, location);
        returntrue;
    

复制代码

不妨尝试这么写:

// 定义dropOut和dropIn, 拆分逻辑并提高代码可读性functionitemDropped(item, location) 
  const dropOut = function () 
    server.notify(item, outOfBounds);
    items.resetAll();
    returnfalse;
  ;

  const dropIn = function () 
    animateCanvas();
    server.notify(item, location);
    returntrue;
  ;

  return !!item && (outOfBounds(location) ? dropOut() : dropIn());

复制代码

细心的朋友会发现,在这个例子中,同时使用了前文提及的优化方案。这说明我们在编码时可以根据实际情况混合使用多种解决方案。


6. 将函数定义为对象,通过穷举查找对应的处理方法

  • 定义普通对象

对于方案3的例子,不妨尝试这么写:

functioncalculate(action, num1, num2) 
  const actions = 
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
    divide: (a, b) => a / b,
  ;
​
  return actions[action]?.(num1, num2) ?? "Calculation is not recognized";

复制代码
  • 定义 Map 对象

普通对象的键需要是字符串,而 Map 对象的键可以是一个对象、数组或者更多类型,更加灵活。

let statusMap = newMap([
  [
     role: "打工人", status: "1" ,
    () =>  /*一些操作*/,
  ],
  [
     role: "打工人", status: "2" ,
    () =>  /*一些操作*/,
  ],
  [
     role: "老板娘", status: "1" ,
    () =>  /*一些操作*/,
  ],
]);

let getStatus = function (role, status) 
  statusMap.forEach((value, key) => 
    if (JSON.stringify(key) === JSON.stringify( role, status )) 
      value();
    
  );
;

getStatus("打工人", "1"); // 一些操作复制代码

tips: JSON.stringify()可用于深比较/深拷贝。


7. 使用责任链模式

责任链模式:将整个处理的逻辑改写成一条责任传递链,请求在这条链上传递,直到有一个对象处理这个请求。

例如 JS 中的事件冒泡

简单来说,事件冒泡就是在一个对象上绑定事件,如果定义了事件的处理程序,就会调用处理程序。相反没有定义的话,这个事件会向对象的父级传播,直到事件被执行,最后到达最外层,document对象上。

这意味着,在这种模式下,总会有程序处理该事件。

再举个🌰,当你想这么写时……

functiondemo (a, b, c) 
  if (f(a, b, c)) 
    if (g(a, b, c)) 
      // ...
    
    // ...elseif (h(a, b, c)) 
      // ...
    
    // ...
   elseif (j(a, b, c)) 
    // ...
   elseif (k(a, b, c)) 
    // ...
  

复制代码

不妨参考这种写法:

const rules = [
  
    match: function (a, b, c)  /* ... */ ,
    action: function (a, b, c)  /* ... */ 
  ,
  
    match: function (a, b, c)  /* ... */ ,
    action: function (a, b, c)  /* ... */ 
  ,
  
    match: function (a, b, c)  /* ... */ ,
    action: function (a, b, c)  /* ... */ 
  
  // ...
]

// 每个职责一旦匹配,原函数就会直接返回。functiondemo (a, b, c) 
  for (let i = 0; i < rules.length; i++) 
    if (rules[i].match(a, b, c)) 
      return rules[i].action(a, b, c)
    
  

复制代码
引申话题——如何降低if else代码的复杂度?
相关文章阅读: 如何无痛降低 if else 面条代码复杂度 建议多读几次!!!

三、小结

本文粗略介绍了8种优化/替代if-else的方法,希望能给你日常编码带来一些启示😄。

正如开头所说,我们的目的不是消灭代码中的if-else,而是让我们在学会更多解决方案的基础上,根据实际情况选择更优的编码方式。因此,当你发现自己的代码里面存在特别多的if-else或当你想用if-else时,不妨停下来思考一下——如何能写得更优雅、更方便日后维护呢

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

使用策略模式+工厂模式干掉代码中过多的if-else

过多if-else项目背景

如果一开始就知道现在的业务需要,大部分人都不会在代码里添加过多的if-else判断的,烂代码基本都是刚开始写代码时并没有太多的需求,随着期需求不断的修改增加,开发时间也较的紧张,代码往往都是怎么快速怎么写。当然多写一个if-else比使用各种设计模式肯定来的更快速了,这也就导致项目代码慢慢变得臃肿,难以维护的主要原因。在有空闲时间的情况下就可以给以前的代码做一次手术了。
先看本次未优化前的代码:

    @Override
    public MMediaInfo copyToLibType(UserQiniuDTO userQiniuDTO, Long mediaId, Integer libType) {
        MMediaInfo mMediaInfo = new MMediaInfo();
        LibTypeEnum libTypeEnum = LibTypeEnum.valueof(libType);
        if (libTypeEnum.getFileType().equals(FileType.VIDEO)) {
		 	 mMediaInfo = mvideoInfoService.copyToLibType(userQiniuDTO, mediaId, libType);
        }
        if (libTypeEnum.getFileType().equals(FileType.AUDIO)) {
            mMediaInfo = mAudioInfoService.copyToLibType(userQiniuDTO, mediaId, libType);
        }
        if (libTypeEnum.getFileType().equals(FileType.PICTURE)) {
             mMediaInfo = mImgInfoService.copyToLibType(userQiniuDTO, mediaId, libType);
        }
        if (libTypeEnum.getFileType().equals(FileType.FILE)) {
            mMediaInfo= mFileInfoService.copyToLibType(userQiniuDTO, mediaId, libType);
        }
        return mMediaInfo;
    }

之所有会有些段代码,主要原因是项目前期,需求中只有 视频/音频的 ,并且音频操作接口,逻辑都是分开的,后面又增加了图片/文件需求,并且要求视频/音频/文件/图片 能合并在同一接口处理, 这就导致了代码中很多操作有大量 if-else判断
再看优化后的代码:

    @Override
    public MMediaInfo copyToLibType(UserQiniuDTO userQiniuDTO, Long mediaId, Integer libType) {
        LibTypeEnum libTypeEnum = LibTypeEnum.valueof(libType);
        IFileService iFileService = FileFactory.getFileService(libTypeEnum.getFileType());
        return   iFileService.copyToLibType(userQiniuDTO, mediaId, libType);
    }

此断代码看起来就清爽多了,实现的基本思路就是使用了 策略模式+工厂模式,代码通过不同的文件类型返回对应的实现类来实现复制逻辑

基本视频步骤

  1. 创建IFileService接口
public interface IFileService {
    Integer getFileType();
    MMediaInfo copyToLibType(UserQiniuDTO userQiniuDTO, Long mediaId, Integer libType);
}

  1. 视频/音频/图片/文件类分别视频IFileService接口
public class MVideoInfoServiceImpl extends ServiceImpl<MvideoInfoMapper, MvideoInfo> implements MvideoInfoService, IFileService {
    
    @Override
    public Integer getFileType() {
        return FileType.VIDEO;
    }
    @Override
    public MMediaInfo copyToLibType(UserQiniuDTO userQiniuDTO, Long id, Integer libType) {
 		//复制视频逻辑
 		
        return mMediaInfo;
    }
  1. 创建FileFactory工厂类
@Component
public class FileFactory implements ApplicationContextAware {
 
    private static Map<Integer, IFileService> fileServiceMap;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IFileService> map = applicationContext.getBeansOfType(IFileService.class);
        fileServiceMap = new HashMap<>();
        map.forEach((key, value) -> fileServiceMap.put(value.getFileType(), value));
    }
 
    public static <T extends IFileService> T getFileService(Integer fileType) {
        return (T)fileServiceMap.get(fileType);
    }
 
}

总结

copyToLibType只是优化的一个方式,IFileService中还有很多 如 移动/删除 等操作的代码也可以干掉过多的if-else了。

以上是关于面试题如何避免使用过多的 if else?的主要内容,如果未能解决你的问题,请参考以下文章

Java面试题|if-else-if-else与switch的区别

如何解决代码中if…else 过多的问题

前端面试题之性能优化篇

MySQL面试题(无答案版) 中高级必看

如何解决if else过多的问题,各种方法盘点

如何解决if else过多的问题,各种方法盘点