NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

编写高质量可维护的代码之优化逻辑判断

  • 2021-04-09
  • 本文字数:5858 字

    阅读完需:约 19 分钟

编写高质量可维护的代码之优化逻辑判断

if else、switch case 是日常开发中最常见的条件判断语句,这种看似简单的语句,当遇到复杂的业务场景时,如果处理不善,就会出现大量的逻辑嵌套,可读性差并且难以扩展。


编写高质量可维护的代码,我们先从最小处入手,一起来看看在前端开发过程中,可以从哪些方面来优化逻辑判断?


下面我们会分别从 JavaScript 语法和 React JSX 语法两个方面来分享一些优化的技巧。


JavaScript 语法篇

嵌套层级优化


function supply(fruit, quantity) {  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];  // 条件 1: 水果存在  if(fruit) {    // 条件 2: 属于红色水果    if(redFruits.includes(fruit)) {      console.log('红色水果');      // 条件 3: 水果数量大于 10 个      if (quantity > 10) {        console.log('数量大于 10 个');      }    }  } else {    throw new Error('没有水果啦!');  }}
复制代码


分析上面的条件判断,存在三层 if 条件嵌套。


如果提前 return 掉无效条件,将 if else 的多重嵌套层次减少到一层,更容易理解和维护。


function supply(fruit, quantity) {  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];  if(!fruit) throw new Error('没有水果啦');     // 条件 1: 当 fruit 无效时,提前处理错误  if(!redFruits.includes(fruit)) return; // 条件 2: 当不是红色水果时,提前 return      console.log('红色水果');      // 条件 3: 水果数量大于 10 个  if (quantity > 10) {    console.log('数量大于 10 个');  }}
复制代码


多条件分支的优化处理


当需要枚举值处理不同的业务分支逻辑时,第一反应是写下 if else ?我们来看一下:


function pick(color) {  // 根据颜色选择水果  if(color === 'red') {    return ['apple', 'strawberry'];   } else if (color === 'yellow') {    return ['banana', 'pineapple'];  } else if (color === 'purple') {    return ['grape', 'plum'];  } else {    return [];  }}
复制代码


在上面的实现中:


  • if else 分支太多

  • if else 更适合于条件区间判断,而 switch case 更适合于具体枚举值的分支判断


使用 switch case 优化上面的代码后:


function pick(color) {  // 根据颜色选择水果  switch (color) {    case 'red':      return ['apple', 'strawberry'];    case 'yellow':      return ['banana', 'pineapple'];    case 'purple':      return ['grape', 'plum'];    default:      return [];  }}
复制代码


switch case 优化之后的代码看上去格式整齐,思路很清晰,但还是很冗长。继续优化:


  • 借助 Object 的 { key: value } 结构,我们可以在 Object 中枚举所有的情况,然后将 key 作为索引,直接通过 Object.key 或者 Object[key] 来获取内容


const fruitColor = {                          red: ['apple', 'strawberry'],  yellow: ['banana', 'pineapple'],  purple: ['grape', 'plum'],}function pick(color) {  return fruitColor[color] || [];}
复制代码


  • 使用 Map 数据结构,真正的 (key, value) 键值对结构;


const fruitColor = new Map().set('red', ['apple', 'strawberry']).set('yellow', ['banana', 'pineapple']).set('purple', ['grape', 'plum']);
function pick(color) {  return fruitColor.get(color) || [];}
复制代码


优化之后,代码更简洁、更容易扩展。


为了更好的可读性,还可以通过更加语义化的方式定义对象,然后使用 Array.filter 达到同样的效果。


const fruits = [  { name: 'apple', color: 'red' },   { name: 'strawberry', color: 'red' },   { name: 'banana', color: 'yellow' },   { name: 'pineapple', color: 'yellow' },   { name: 'grape', color: 'purple' },   { name: 'plum', color: 'purple' }];
function pick(color) {  return fruits.filter(f => f.color == color);}
复制代码


使用数组新特性简化逻辑判断


巧妙的利用 ES6 中提供的数组新特性,也可以让我们更轻松的处理逻辑判断。


多条件判断


编码时遇到多个判断条件时,本能的写下下面的代码(其实也是最能表达业务逻辑的面向过程编码)。


function judge(fruit) {  if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry' || fruit === 'cranberries' ) {    console.log('red');  }}
复制代码


但是当 type 未来到 10 种甚至更多时, 我们只能继续添加 || 来维护代码么?


试试 Array.includes ~


// 将判断条件抽取成一个数组const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];function judge(type) {  if (redFruits.includes(fruit)) {    console.log('red');  }}
复制代码

判断数组中是否所有项都满足某条件


const fruits = [  { name: 'apple', color: 'red' },  { name: 'banana', color: 'yellow' },  { name: 'grape', color: 'purple' }];
function match() {  let isAllRed = true;
  // 判断条件:所有的水果都必须是红色  for (let f of fruits) {    if (!isAllRed) break;    isAllRed = (f.color === 'red');  }
  console.log(isAllRed); // false}
复制代码


上面的实现中,主要是为了处理数组中的所有项都符合条件。


使用 Array.every 可以很容的实现这个逻辑:


const fruits = [  { name: 'apple', color: 'red' },  { name: 'banana', color: 'yellow' },  { name: 'grape', color: 'purple' }];
function match() {  // 条件:所有水果都必须是红色  const isAllRed = fruits.every(f => f.color == 'red');
  console.log(isAllRed); // false}
复制代码


判断数组中是否有某一项满足条件


Array.some,它主要处理的场景是判断数组中是否有一项满足条件。

如果想知道是否有红色水果,可以直接使用 Array.some 方法:


const fruits = [    { name: 'apple', color: 'red' },    { name: 'banana', color: 'yellow' },    { name: 'grape', color: 'purple' }  ];
// 条件:是否有红色水果 const isAnyRed = fruits.some(f => f.color == 'red');
复制代码


还有许多其他数组新特性,比如 Array.find、Array.slice、Array.findIndex、Array.reduce、Array.splice 等,在实际场景中可以根据需要选择使用。


函数默认值


使用默认参数


const buyFruit = (fruit,amount) => {  if(!fruit){    return  }  amount = amount || 1;  console.log(amount)}
复制代码


我们经常需要处理函数内部的一些参数默认值,上面的代码大家都不陌生,使用函数的默认参数,可以很好的帮助处理这种场景。


const buyFruit = (fruit,amount = 1) => {  if(!fruit){    return  }  console.log(amount,'amount')}
复制代码


我们可以通过 Babel 的转译来看一下默认参数是如何实现的。



从上面的转译结果可以发现,只有参数为 undefined 时才会使用默认参数。

测试的执行结果如下:


buyFruit('apple','');  // amountbuyFruit('apple',null);  //null amountbuyFruit('apple');  //1 amount
复制代码


所以使用默认参数的情况下,我们需要注意的是默认参数 amount = 1 并不等同于 amount || 1


使用解构与默认参数


当函数参数是对象时,我们可以使用解构结合默认参数来简化逻辑。

Before:

const buyFruit = (fruit,amount) => { fruit = fruit || {}; if(!fruit.name || !fruit.price){   return; } ...  amount = amount || 1;  console.log(amount)}
复制代码

After:

const buyFruit = ({ name,price }={},amount) => {  if(!name || !prices){    return;  }  console.log(amount)}
复制代码

复杂数据解构


当处理比较简的对象时,解构与默认参数的配合是非常好的,但在一些复杂的场景中,我们面临的可能是更复杂的结构。

const oneComplexObj = { firstLevel: {   secondLevel:[{     name:"",     price:""   }]  }}
复制代码

这个时候如果再通过解构去获取对象里的值。

const {  firstLevel:{    secondLevel:[{name,price]=[]  }={}} = oneComplexObj;              
复制代码


可读性就会比较差,而且需要考虑多层解构的默认值以及数据异常情况。


这种情况下,如果项目中使用 lodash 库,可以使用其中的 lodash/get 方法。


import lodashGet from 'lodash/get';
const { name,price} = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});
复制代码


策略模式优化分支逻辑处理


策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。


使用场景:策略模式属于对象行为模式,当遇到具有相同行为接口、行为内部不同逻辑实现的实例对象时,可以采用策略模式;或者是一组对象可以根据需要动态的选择几种行为中的某一种时,也可以采用策略模式;这里以第二种情况作为示例:


Before:

const TYPE = {  JUICE:'juice',  SALAD:'salad',  JAM:'jam'}function enjoy({type = TYPE.JUICE,fruits}){  if(!fruits || !fruits.length) {    console.log('请先采购水果!');    return; }  if(type === TYPE.JUICE) {    console.log('榨果汁中...');    return '果汁';  }  if(type === TYPE.SALAD) {    console.log('做沙拉中...');    return '拉沙';  }  if(type === TYPE.JAM) {    console.log('做果酱中...');    return '果酱';  }  return;}
enjoy({type:'juice',fruits});
复制代码


使用思路:定义策略对象封装不同行为、提供策略选择接口,在不同的规则时调用相应的行为。

After:


const TYPE = {  JUICE:'juice',  SALAD:'salad',  JAM:'jam'}
const strategies = { [TYPE.JUICE]: function(fruits){ console.log('榨果汁中...'); return '果汁'; }, [TYPE.SALAD]:function(fruits){ console.log('做沙拉中...'); return '沙拉'; }, [TYPE.JAM]:function(fruits){ console.log('做果酱中...'); return '果酱'; },}
function enjoy({type = TYPE.JUICE,fruits}) { if(!type) { console.log('请直接享用!'); return; } if(!fruits || !fruits.length) { console.log('请先采购水果!'); return; } return strategies[type](fruits);}
enjoy({type:'juice',fruits});
复制代码


框架篇之 React JSX 逻辑判断优化


JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。一般在 React 中使用 JSX 来描述界面信息,ReactDOM.render() 将 JSX 界面信息渲染到页面上。


在 JSX 中支持 JavaScript 表达式,日常很常见的循环输出子组件、三元表达式判断、再复杂一些直接抽象出一个函数。


在 JSX 中写这么多  JavaScript 表达式,整体代码看起来会有点儿杂乱。试着优化一下!


JSX-Control-Statements


JSX-Control-Statements (https://www.npmjs.com/package/jsx-control-statements) 是一个 Babel 插件,它扩展了 JSX 的能力,支持以标签的形式处理条件判断、循环。


If 标签


标签内容只有在 condition 为 true 时才会渲染,等价于最简单的三元表达式。


Before:

{ condition() ? 'Hello World!' : null }   
复制代码

After:

<If condition={ condition() }>Hello World!</If>   
复制代码

注意:已被废弃,复杂的条件判断可以使用标签。


Choose 标签


标签下包括至少一个标签、可选的标签。


标签内容只有在 condition 为 true 时才会渲染,相当于一个 if 条件判断分支。


标签则相当于最后的 else 分支。


Before:

{ test1 ? <span>IfBlock1</span> : test2 ? <span>IfBlock2</span> : <span>ElseBlock</span> }
复制代码

After:

<Choose>  <When condition={ test1 }>    <span>IfBlock1</span>  </When>  <When condition={ test2 }>    <span>IfBlock2</span>  </When>  <Otherwise>    <span>ElseBlock</span>  </Otherwise></Choose>
复制代码


For 标签


标签需要声明 of、each 属性。


of 接收的是可以使用迭代器访问的对象。


each 代表迭代器访问时的当前指向元素。


Before:

{  (this.props.items || []).map(item => {    return <span key={ item.id }>{ item.title }</span>  })}
复制代码

After:

<For each="item" of={ this.props.items }>  <span key={ item.id }>{ item.title }</span></For>
复制代码

注意:标签不能作为根元素。


With 标签


标签提供变量传参的功能。


Before:

renderFoo = (foo) => {  return <span>{ foo }</span>;}
// JSX 中表达式调用{  this.renderFoo(47)}
复制代码

After:

<With foo={ 47 }>  <span>{ foo }</span></With>
复制代码


使用这几种标签优化代码,可以减少  JSX 中存在的显式 JavaScript 表达式,使我们的代码看上去更简洁,但是这些标签封装的能力,在编译时需要转换为等价的 JavaScript 表达式。


注意:具体 babel-plugin-jsx-control-statements 插件的使用见第三篇参考文章;Vue 框架已经通过指令的形式支持 v-if、v-else-if、v-else、v-show、slot 等。


总结


以上我们总结了一些常见的逻辑判断优化技巧。当然,编写高质量可维护的代码,除了逻辑判断优化,还需要有清晰的注释、含义明确的变量命名、合理的代码结构拆分、逻辑分层解耦、以及更高层次的贴合业务的逻辑抽象等等,相信各位在这方面也有自己的一些心得,欢迎一起留言讨论~



头图:Unsplash

作者:米亚

原文:https://mp.weixin.qq.com/s/S8nBOuu1W8RaXcdMN5U-yQ

原文:编写高质量可维护的代码之优化逻辑判断

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-04-09 18:085667

评论 4 条评论

发布
用户头像
很赞,也是我平时经常用的优化方法~
2021-06-02 11:33
回复
用户头像
增加了新人阅读成本
2021-05-06 09:57
回复
用户头像
赞!这才是真实的代码
2021-04-19 15:54
回复
用户头像
牛啊
2021-04-17 18:00
回复
没有更多了
发现更多内容

数据库的视图该怎么用?

阿柠xn

MySQL 数据库 视图 9月月更

头大了,Mysql写入数据十几秒后被自动删除了

南城FE

MySQL 前端 nodejs

预约直播 | 大规模稀疏模型演进与DeepRec

阿里云大数据AI技术

开源项目 AI技术 模型稀疏训练

漏洞修复实用指南

SEAL安全

开源 漏洞 安全漏洞 漏洞修复 开源漏洞

“大厂”角力移动办公系统市场,钉钉和企微向左、WorkPlus向右

WorkPlus

如何设计企业级数据埋点采集方案?

字节跳动数据平台

数据分析 用户增长 埋点 数据应用 埋点设计

恍然大悟,才知道什么是真正的思维导图!

博文视点Broadview

亿级日志队列回放性能测试初探

FunTester

高并发之缓存

源字节1号

软件开发

华为云快成长直播间大数据&AI专场,加速经济物联网智能化提升

科技怪咖

推荐|海泰国密通信安全解决方案 助力用户实现安全合规

电子信息发烧客

Java后端每日学点系列?线程知否,List懂否,垃圾回收器晓得否

知识浅谈

线程 垃圾回收器 9月月更

使用 FSM 管理 osm-edge 服务网格入口流量

Flomesh

Service Mesh 服务网格

为什么阿里人成长速度极快?看完他们 Java 架构进化笔记,值得学习

Java-fenn

Java 程序员 java面试 Java学习 Java面试题

阿里云张新涛:连接产业上下游,构建XR协作生态

阿里云弹性计算

交互 XR 视觉计算 沉浸式体验

手把手教大家编译 flowable 源码

江南一点雨

Java workflow flowable

关于用户 email 邮件地址是否允许有加号的问题

Jerry Wang

typescript 正则表达式 邮件 9月月更 输入校验

面试造火箭!连续轰炸50问,我却靠这些"java复习宝典"一一攻克!

收到请回复

Java 云计算 开源 架构 编程语言

“双减”一年,如何让教育回归本质?

旺链科技

区块链 产业区块链 企业号九月金秋榜 教培行业

智能湖仓架构实践:利用 Amazon Redshift 的流式摄取构建实时数仓

亚马逊云科技 (Amazon Web Services)

大数据ELK(一):集中式日志协议栈Elastic Stack简介

Lansonli

ELK 9月月更

从零到一构建完整知识体系,阿里最新SpringBoot原理最佳实践真香

程序员小毕

Java spring 源码 面试 SpringBoot 2

算法基础(二)| 高精度算法详解

timerring

算法 9月月更

阿里大佬力荐6篇实战文档:JVM+多线程+Kafka+Redis+Nginx+MySQL,你确定不看?

收到请回复

Java 云计算 开源 架构 编程语言

变革加速,博睿数据赋能“中国智造”转型升级

博睿数据

可观测性 智能运维 博睿数据

想成为数据科学家,哪些技能你必须具备?

雨果

Github点击破百万!这部《从零开始学架构》神书就此霸榜

Java-fenn

Java 程序员 java面试 Java书籍 Java面试题

华为云快成长直播ERP专场,以数据驱动企业智慧变革

科技怪咖

关关难过关关过!2022年BAT面试通关秘籍:面前规划+面试题集+简历优化+面经分享等!

收到请回复

Java 云计算 开源 架构 编程语言

HUAWEI DevEco Testing注入攻击测试:以攻为守,守护OpenHarmony终端安全

OpenHarmony开发者

OpenHarmony

【指针内功修炼】字符指针 + 指针数组 + 数组指针 + 指针参数(一)

Albert Edison

C语言 二维数组 9月月更 指针数组 数组指针

编写高质量可维护的代码之优化逻辑判断_语言 & 开发_政采云前端团队_InfoQ精选文章