如何优化复杂的业务判断逻辑来提升业务代码的维护能力

在日常的开发工作当中,优秀的用户界面数据库、构建工具、样式预处理器是我们前端现工作阶段必不可少的三大利器,社区里优秀的团队已经为我们提供了很多便利的解决方案,但是,我们需要做的业务场景不只是面对当前社区提供的技术方案就可以满足我们的需求

声明、输出、判断、循环是开发过程当中最常见,也是我们用到最多的语法,简单语法逻辑在针对复杂的业务情况产生的同时也会变得繁琐且不易维护

条件简介

单条件判断

javascript 复制代码
  if (true) {

  } else {
    
  }
  const status1 = a || b
  const status2 = a && b

多用于在简单的 等单项条件,简单、直接、易理解、好维护

切记不可过度封装!!!

多状态单条件判断

javascript 复制代码
  switch (type) {
    case '1':
      // ...
      break;
    case '2':
      // ...
      break;
    default:
      // ...
      break;
  }

多状态、单条件的情况下直接使用 switch 也是一个非常不错的选择

多条件判断

javascript 复制代码
  if (a && b &&  c) {
    // ...
  }

该情况其实是最常见,也是我们日常业务场景当中,除了以上两种遇见最多的条件判断的方式,优点显而易见:

实现速度快、处理方式简单、用最少得时间快速实现业务需求;

当然,其背后存在的真正问题也是很多:

复杂的多条件下,多人开发的协调性、嵌套类型的判断逻辑代码的可读性、牵一发而动全身的情况大幅度增加

基于这些情况,为了合理的避免在开发项目过程当中,因对 代码熟悉程度 ,和 其中特殊属性的理解性 而导致的开发 bug 大量产生,下面介绍的 高级多条件判断 可以很好的避免这种问题的产生

高级多条件判断

该方式主要针对多条件的 判断,如果按照上述的方式来看,我们可能无法规避到一些实际逻辑当中存在的遗漏状态,从而导致产生 bug ,按照如下实现方案,肉眼可见按照实际逻辑规避掉从上方实现逻辑中可能不能涉及到的问题,大量的 && 判断条件,在加上 if 条件的嵌套,业务情况复杂的场景下,是需要对业务的熟悉程度和个人能力水平有一定的要求

javascript 复制代码
  const condition = new Map([
    [
      'a-b',
      () => {
        // ...
      }
    ],
    [
      'a-c',
      () => {
        // ...
      }
    ],
    [
      'a-b-c',
      () => {
        // ...
      }
    ]
  ])
  condition.has('a-b') // 更加方便的确定当前条件是否在需求范围内
  condition.get('a-b') // 执行相关条件的内容

上述的这些实现方案,在很早之前就在社区里有看到过,但是如何把这些实现方式实际的运用到项目当中,对于我们来说是一种从认识到理解的过渡

实际案例

近日的公司需求 -> 满意度评价,这个案例大家很常见,不同的评分展示不同的内容,但是有些情况下评分不同,但是展示的内容相同,如下:

  • 0分:态度恶劣、方案不行、回复很慢、让我反复描述
  • 1分:态度敷衍、方案不行、回复慢、让我反复描述
  • 2分:态度一般、方案一般、回复慢、让我反复描述

会有很多的 icon 用来展示不同分数的按钮,当点击按钮会显示上方的那些多选按钮选项,可以通过上方列出的多选按钮选项来看,在顺序不能改变的情况下,中间穿插了部分同样的类型选项,但是从产品设计的角度来说,我们不可能改变它的排列顺序,如果按照以往的实现方式来看

参考实现

最省事的办法:

javascript 复制代码
const scoreObj = {
  0: ['态度恶劣', '方案不行', '回复很慢', '让我反复描述'],
  1: ['态度敷衍', '方案不行', '回复慢', '让我反复描述'],
  2: ['态度一般', '方案一般', '回复慢', '让我反复描述']
}

上方的实现方式是最省事的,也是可能大家使用最多的开发方式,它的优缺点也是显而易见的:

  • 优点:简单、直观是该实现方式最大的优点;
  • 缺点:重复的逻辑维护、大量的维护成本、多人开发涉及修改范围覆盖不全的问题

在和我司同事讨论当中,给出了一个新的方案,那就是把所有条件放到一个数组当中,通过条件的遍历来获取实际需求项

javascript 复制代码
const scoreArr = ['态度恶劣', '方案不行', '回复很慢', '回复慢', '方案一般', '让我反复描述']
const condition = [ 1, 2 ]

// 伪代码,主要表达就是通过某些条件来获取实际要渲染的内容
const renderCondition = 1scoreArr.filter(item => condition.includes(item))

这也确实是一个办法,但是针对性场景相对来说较少,我们有多少的 scoreArr 就意味着我们每次的条件判断需要遍历多少遍,且每次 includes 也会再次遍历,那么这种写法的复杂度就是 m * n

最终实现

在实现业务得过程当中,我见过太多太多的 if else 的嵌套,对于一个新人去了解一个新项目来说,太难了,然后就考虑到了之前看到的 高级多条件判断 的实现方案,我觉得是可以实际运用到项目当中的,但是不能照本宣科,是需要一定的调整的

首先,第一步要考虑的就是我需要通过哪些条件去做判断,最终渲染我想要的内容:

javascript 复制代码
getCode(code: number) {
  switch (code) {
    case 0:
      return this.zero;
    case 1:
      return this.one;
    case 2:
      return this.two;
    default:
      return '';
  }
}

上方的代码很简单,就是通过我们某些条件,来获取到我们对应的一个 命令

紧接着,在我们根据所有的条件依次拿到对应的 命令 以后,下一步就是我们要对这些条件去做一些对应的处理,让它们 "连" 起来:

javascript 复制代码
getCondition(arr: string[]) {
  return arr.join('-');
}

当我们获取到了最终的 条件 ,对于我们这个对象来说,它是一个 命令 ,针对当前对象的一个 命令,用来指挥我们当前对象应该去做什么事情:

javascript 复制代码
getResult(condition: string) {
  if (this.map.has(condition)) {
    return this.map.get(condition) || function () {};
  }
  return function () {
    console.error(`条件 ${condition} 不存在对应方法`);
  };
}

如何可读性差的条件判断,给变得易读,只有清晰得了解到代码的意思,才能真正的去了解在某个位置做了那些事情,对业务也可以更进一步的了解,所以在这里,我还是沿用了 高级多条件判断 的方式,通过 命令模式 的方式来将我的每一次需求发送到对应的 状态机 事件当中:

javascript 复制代码
class {
  [x: string]: any;
  zero: string;
  map: Map<string, Function>;
  constructor() {
    this.zero = 'ZERO';
    // ...
    this.map = new Map([
      [
        'ZERO',
        () => {
          return [
            {
              label: '方案不行',
              value: '方案不行'
            }
          ];
        }
      ],
      // ...
    ]);
  }
}

最终的调用方式如下:

javascript 复制代码
import ConditionController from './ConditionController';
const cacheConditionController = new ConditionController();

function getSelectorList(rateActive) {
  const code = cacheConditionController.getCode(rateActive);
  const condition = cacheConditionController.getCondition([code]);
  const config = cacheConditionController.getResult(condition)();
  const arr = [
    {
      label: '让我反复描述',
      value: '让我反复描述'
    }
  ];
  switch (rateActive) {
    case 0:
      return [
        {
          label: '态度恶劣',
          value: '态度恶劣'
        },
        ...config,
        {
          label: '回复很慢',
          value: '回复很慢'
        },
        ...arr
      ];
    case 1:
      return [
        {
          label: '态度敷衍',
          value: '态度敷衍'
        },
        ...config,
        ...arr
      ];
    case 2:
      return [
        {
          label: '态度一般',
          value: '态度一般'
        },
        {
          label: '方案一般',
          value: '方案一般'
        },
        ...config,
        ...arr
      ];
    default:
      return [];
  }
}

export default getSelectorList;

在实现过程当中,考虑到可能同一个业务需求当中,存在多个该场景,但是既然拆分了,就要拆分的清晰一些,不同的判断场景各自独立 ,这样可以保证我们在修复一些问题的时候,进行针对性的代码阅读,但是其中一些公共的处理方法又是可以通用的,例如 getConditiongetResult,所以我使用了 class 类继承的方式来实现该场景,一个完整的 高级多条件判断 实现如下:

javascript 复制代码
// ConditionController.ts
import ConditionSuper from './ConditionSuper';

export default ConditionSuper(
  class {
    [x: string]: any;
    zero: string;
    one: string;
    two: string;
    four: string;
    five: string;
    map: Map<string, Function>;
    constructor() {
      this.zero = 'ZERO';
      this.one = 'ZERO-TWO';
      this.two = 'TWO';
      this.four = 'FOUR';
      this.five = 'FIVE';
      this.map = new Map([
        [
          'ZERO',
          () => {
            return [
              {
                label: '方案不行',
                value: '方案不行'
              }
            ];
          }
        ],
        [
          'ZERO-TWO',
          () => {
            return [
              ...this.getResult(this.getCode(0))(),
              ...this.getResult(this.getCode(2))()
            ];
          }
        ],
        [
          'TWO',
          () => {
            return [
              {
                label: '回复慢',
                value: '回复慢'
              }
            ];
          }
        ]
      ]);
    }

    getCode(code: number) {
      switch (code) {
        case 0:
          return this.zero;
        case 1:
          return this.one;
        case 2:
          return this.two;
        default:
          return '';
      }
    }
  }
);

// ConditionSuper.ts
function ConditionSuper(ExtendsParent: any) {
  return class extends ExtendsParent {
    getCondition(arr: string[]) {
      return arr.join('-');
    }

    getResult(condition: string) {
      if (this.map.has(condition)) {
        return this.map.get(condition) || function () {};
      }
      return function () {
        console.error(`条件 ${condition} 不存在对应方法`);
      };
    }
  };
}

export default ConditionSuper;

到这里大家可能看的会有一些疑惑,下面我通过一个简图来给大家总结一下:

graph TD; 业务参数-->对应条件; 对应条件-- getCode -->当前条件对应命令--getCondition-->完整的对象命令--getResult-->在条件对象中获取对应事件; 在条件对象中获取对应事件--has->yes-->是; 在条件对象中获取对应事件--has->no-->否; 是-->存在对应命令--get->return-->结束; 否-->不存在对应命令-->提示-->结束;

总结

上方的实现逻辑也只是未来可能遇见业务需求的一个缩影,实际情况可能比这个还要复杂,如果只靠单纯的 if (a === '***' && b > *** && c) 这类的判断条件来维护,并不是一个可观的解决方案,所以建议大家在开发过程当中,考虑使用该实现方案投入当项目当中去使用

还有很多朋友可能会问到,这样的拆分是否可以拆分成公共的方法去使用?

在这里我可以直接告诉你:不可以。

在现在的开发环境当中,大家对于代码的拆分的理解日渐趋向于 拆分 === 公共方法 ,这其实完全是两码事,拆分的目的,其实是为了更好的去维护当前的项目,让大家在当前的项目当中去更好的阅读,更好的理解到我们当前需要理解到的业务,而并不是为了公共使用而去拆分

更何况,实际的判断条件是根据不同的业务条件去做的拆分的,怎么可能公共运用到不同的地方,除非业务场景和条件是 全覆盖

还有朋友可能会问到,那么这样写的代码量明显会增多了,这方面是怎么考虑的?

这是个很真实的情况,代码量比直接 if 会增加很多 ,这就是我们实际需要去做取舍的地方了,如果我们是很简单的条件校验,我并建议大家使用上面的方法,这样只会让我们的业务代码变得更多,考虑简单的条件、极限项目优化的角度来看,并不是太优秀,但是针对业务量庞大,且业务条件极度复杂,上面的条件虽然会带来更多的代码量,但是可以让我们更稳定的去运行当前的项目

这就好比小船(简单业务判断)和巨轮(极度复杂的大型项目),小船要的是在最短时间跑起来、跑得快,而不是去和巨轮去比谁功能全;巨轮和小船要比的是如何收益最大化、稳定运行,而不是比谁走出去的快

好久没有写博客了,手法多多少少有些生疏,但是坚持的原则是不变的,多思考,多反思,从业务当中发现问题,寻找最适用于业务的解决办法

希望大家踊跃发言,一起探讨我们实际在开发过程会遇到的问题和解决办法

相关推荐
莹雨潇潇7 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
严文文-Chris9 分钟前
【设计模式-中介者模式】
设计模式·中介者模式
Jiaberrr15 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
刷帅耍帅23 分钟前
设计模式-中介者模式
设计模式·中介者模式
刷帅耍帅41 分钟前
设计模式-组合模式
设计模式·组合模式
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ2 小时前
html+css+js实现step进度条效果
javascript·css·html
刷帅耍帅2 小时前
设计模式-命令模式
设计模式·命令模式
码龄3年 审核中2 小时前
设计模式、系统设计 record part03
设计模式