不要再滥用可选链运算符(?.)啦!

2023-09-22 16:08:20

可选链运算符(?.),大家都很熟悉了,直接看个例子:

const result = obj?.a?.b?.c?.d 

很简单例子,上面代码?前面的属性如果是空值(null或undefined),则result值是undefined,反之如果都不是空值,则会返回最后一个d属性值。

本文不是讲解这种语法的用法,主要是想分析下日常开发中,这种语法 滥用、乱用 的问题。

滥用、乱用

最近在code review一个公司项目代码,发现代码里用到的可选链运算符,很多滥用,用的很无脑,经常遇到这种代码:

 

const userName = data?.items?.[0]?.user?.name

↑ 不管对象以及属性有没有可能是空值,无脑加上?.就完了。

 

// react class component const name = this.state?.name // react hooks const [items, setItems] = useState([]) items?.map(...) setItems?.([]) // 真有这么写的

↑ React框架下,this.state 值不可能是空值,初始化以及set的值都是数组,都无脑加上?.

 

const item1 = obj?.item1 console.log(item1.name)

↑ 第一行代码说明obj或item1可能是空值,但第二行也明显说明不可能是空值,否则依然会抛错,第一行的?.也就没意义了。

 

if (obj?.item1?.item2) { const item2 = obj?.item1?.item2 const name = obj?.item1?.item2?.name }

↑ if 里已经判断了非空了,内部就没必要判断非空了。

问题、缺点

如果不考虑 ?. 使用的必要性,无脑滥用其实也没问题,不会影响功能,优点也很多:

  1. 不用考虑是不是非空,每个变量或属性后面加 ?. 就完了。
  2. 由于不用思考,开发效率高。
  3. 不会有空引用错误,不会有页面点点就没反应或弹错问题。

但是问题和缺点也很明显,而且也会很严重。分两点分析下:

  1. 可读性、维护性:给代码维护人员带来了很多分析代码的干扰,代码可读性和维护性都很差。
  2. 隐式过滤了异常:把异常给隐式过滤掉了,导致不能快速定位问题。
  3. 编译后代码冗余。
  4. 护眼:一串?.看着难受,特别是以一个code reviewer 角度看。

1. 可读性、维护性

可读性和维护性其实是一回事,都是指不是源代码作者的开发维护人员,在捋这块代码逻辑、修改bug等情况时,处理问题的效率,代码写的好处理就快,写的烂就处理慢,很简单道理。

 

const onClick = () => { const user = props.data?.items?.[0]?.user if (user) { // use user to do something } }

已这行代码为例,有个bug现象是点击按钮没反应,维护开发看到这块代码,就会想这一串链式属性里,是不是有可能有空值,所以导致了user是空值,没走进if里导致没反应。然后就继续分析上层组件props传输代码,看data值从哪儿传来的,看是不是哪块代码导致data或items空值了。。。

其实呢?从外部传过来的这一串属性里不会有空值的情况,导致bug问题根本不在这儿。

 

const user = props.data.items[0].user

那把?.都去掉呢?维护开发追踪问题看到这行代码,data items 这些属性肯定不能是空值,不然console就抛错了,但是bug现象里并没有抛错,所以只需要检查user能不能是空值就行了,很容易就排除了很多情况。

总结就是:给代码维护人员带来了很多分析代码的干扰,代码可读性和维护性都很差。

2. 隐式过滤了异常

 

api.get(...).then(result => { const id = result?.id // use id to do something })

比如有个需求,从后台api获取数据时,需要把结果里id属性获取到,然后进行数据处理,从业务流程上看,这个api返回的result以及id必须有值,如果没值的话后续的流程就会走不通。

然后后台逻辑由于写的有问题,导致个别情况返回的 result=null,但是由于前端这里加了?.,导致页面没有任何反应,js不抛错,console也没有log,后续流程出错了,这时候如果想找原因就会很困难,对代码熟悉还行,如果不是自己写的就只能看代码捋逻辑,如果是生产环境压缩混淆了就更难排查了。

 

api.get(...).then(result => { const id = result.id // use id to do something })

?.去掉呢?如果api返回值有问题,这里会立即抛错,后面的流程也就不能进行下去了,无论开发还是生产环境都能在console里快速定位问题,即使是压缩混淆的也能从error看出一二,或者在一些前端监控程序里也能监听到。

其实这种现象跟 try catch 里不加 throw 类似,把隐式异常错误完全给过滤掉了,比如下面例子:

 

// 这个try本意是处理api请求异常 try { const data = getSaveData() // 这段js逻辑也在try里,所以如果这个方法内部抛错了,页面上就没任何反应,很难追踪问题 const result = await api.post(url, data) // result 逻辑处理 } catch (e) { // 好点的给弹个框,打个log,甚至有的啥都不处理 }

总结就是:把异常给隐式过滤掉了,导致不能快速定位问题。

3. 编译后代码冗余

如果代码是ts,并且编译目标是ES2016,编译后代码会很长。可以看下 www.typescriptlang.org/play 效果。

image.png

Babel在个别stage下,编译效果一样。

image.png

但并不是说一点都不用,意思是尽量减少滥用,这样使用的频率会少很多,这种编译代码沉余也会少不少。

应该怎么用?

说了这么多,.? 应该怎么用呢?意思是不用吗?当然不是不能用,这个特性对于开发肯定好处很多的,但是得合理用,不能滥用。

  1. 避免盲目用,滥用,有个点儿就加问号,特别是在一个比较长的链式代码里每个属性后面都加。
  2. 只有可能是空值,而且业务逻辑中有空值的情况,就用;其它情况尽量不要用。

其实说白了就是:什么时候需要判断一个变量或属性非空,什么时候不需要。首先在使用的时候得想下,问号前面的变量或属性值,有没有可能是空值:

  1. 很明显不可能是空值,比如 React类组件里的 this.state this.props,不要用;
  2. 自己定义的变量或属性,而且没有赋值为空值情况,不要用;
  3. 某些方法或者组件里,参数和属性不允许是空值,那方法和组件里就不需要判断非空。(对于比较common的,推荐写断言,或者判断空值情况throw error)
  4. 后台api请求结果里,要求result或其内部属性必须有值,那这些值就不需要判断非空。
  5. 按正常流程走,某个数据不会有空值情况,如果是空值说明前面的流程出问题了,这种情况就不需要在逻辑里判断非空。
 

const userName = data?.items?.[0]?.user?.name // 不要滥用,如果某个属性有可能是空值,则需要?. const userName = data.items[0].user?.name // 比如data.items数组肯定不是空数组

 

const items2 = items1.filter(item => item.checked) if (items2?.length) { } // 不需要?.

 

// react class component const name = this.state?.name // 不需要?. // react hooks const [items, setItems] = useState([]) items?.map(...) // 如果setItems没有赋值空值情况,则不需要?. setItems?.([]) // 不需要?.

 

const item1 = obj?.item1 // 不需要?. console.log(item1.name)

 

const id = obj?.id // 下面代码已经说明不能是空值了,不需要?. const name = obj.name

 

if (obj?.item1?.item2) { const item2 = obj?.item1?.item2 // 不需要?. const name = obj?.item1?.item2?.name // 不需要?. }

 

const id = obj?.item?.id // 不需要?. api.get(id).then(...) // 这个api如果id是空值,则api会抛错

当然,写代码时还得多想一下属性是否可能是空值,会一定程度的影响开发效率,也一定有开发会觉得很烦,不理解,无脑写?.多容易啊,但是我从另外两个角度分析下:

  1. 我觉得一个合格的开发应该对自己的代码逻辑很熟悉,应该有责任知道哪些值可能是空值,哪些不可能是空值(并不是说所有,也有大部分了),否则就是对自己的代码了解很少,觉得代码能跑就行,代码质量自然就低。
  2. 想想在这个新特性出来之前大家是怎么写的,会对每个变量和属性都加if非空判断或者用逻辑与(&&)吗?不会吧。

总结

本文以一个 code reviewer 角度,分析了 可选链运算符(?.) 特性的滥用情况,以及“正确使用方式”,只是代表我本人的看法,欢迎大佬参与讨论,无条件接受任何反驳。

滥用的缺点:

  1. 可读性、维护性:给代码维护人员带来了很多分析代码的干扰,代码可读性和维护性都很差。
  2. 隐式过滤了异常:把异常给隐式过滤掉了,导致不能快速定位问题。
  3. 编译后代码冗余。
  4. 护眼:一串?.看着难受,特别是以一个code reviewer 角度看。

“正确用法”:

  1. 避免盲目用,滥用,有个点儿就加问号,特别是在一个比较长的链式代码里每个属性后面都加。
  2. 只有可能是空值,而且业务逻辑中有空值的情况,就用;其它情况尽量不要用
更多推荐

偶现来电时手机操作出现重启

问题描述:偶现来电时手机操作出现重启问题分析:从系统Log看09-0610:22:44.79182914001425WWatchdog:***WATCHDOGKILLINGSYSTEMPROCESS:Blockedinhandleronmainthread(main)09-0610:22:44.794133140014

CSRF和SSRF有什么不同?

文章目录CSRF复现SSRF复现启动环境漏洞复现探测存活IP和端口服务计划任务反弹shell区别CSRF复现打开dvwa,将难度调为low,点击CSRF,打开后发现有一个修改密码的输入框:在这里修改密码,并用bp抓包,在httphistory查看数据包,点击engagementtools中的GenerateCSRFPo

策略模式,一种广泛应用于各种情况的设计模式(设计模式与开发实践 P5)

文章目录策略模式实现思想实战-表单策略模式定义:定义一系列算法,把它们一个个封装起来,并且可以互相替换例如,我们要计算年终奖,年终奖根据绩效A、B、C来计算最终数值实现最初我们很容易想到用分支if来解决这个问题,如果绩效=A则工资x2,如果绩效=B则工资x3如果经常使用这样的分支结构,你会发现代码耦合度很高,很容易就出

瑞芯微RK3568:Debian系统如何安装Docker

本文基于HD-RK3568-IOT评估板演示Debian系统安装Docker,该方法适用于RK356X全系产品。HD-RK3568-IOT评估板基于HD-RK3568-CORE工业级核心板设计(双网口、双CAN、5路串口),接口丰富,适用于工业现场应用需求,亦方便用户评估核心板及CPU的性能。适用于工业自动化控制、人机

网络安全(黑客)自学

前言:想自学网络安全(黑客技术)首先你得了解什么是网络安全!什么是黑客网络安全可以基于攻击和防御视角来分类,我们经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面性,例如Web安全技术,既有Web渗透,也有

手写模拟Spring的底层原理2.1

先来引入两个问题第一个懒加载还是立即加载的问题,这个问题还是在于就是说,当我们xml配置了一个对象bean的时候,它在spring容器里面是什么时候开始会给我们创建这个对象那如果我们想对某个对象启动懒加载,可以添加@lazy这个注解这个注解一加上,它就只会在得到对象的时候给我们在容器中创建对象也就是在使用下面的方法的时

Reactor 第十二篇 WebFlux集成PostgreSQL

1引言在现代的应用开发中,数据库是存储和管理数据的关键组件。PostgreSQL是一种强大的开源关系型数据库,而WebFlux是Spring框架提供的响应式编程模型。本文将介绍如何使用Reactor和WebFlux集成PostgreSQL,实现响应式的数据库访问。1.环境准备首先,我们需要在项目的pom.xml文件中添

一个例子了解交叉编译

学习嵌入式Linux经常听到交叉编译这个名词,那到底什么是交叉编译,下面通过一个例子来介绍。首先新建一个C文件,其代码如下。#include"stdio.h"voidmain(){inta,b;intc;printf("请输入两个数:\n");scanf("%d%d",&a,&b);c=a+b;printf("a+b=

Ubuntu不能上网解决办法

判断能不能联网1、怎么判断ubuntu确实不能联网?(1)最简单的办法当然是打开一个浏览器,随便输入一个网址,如www.baidu.com,若不能打开该网址,说明可能联网有问题。(2)打开终端,输入ifconfig命令,可以显示当前系统的网络设备,若只出现以下一个设备,表示该系统确实不能联网。(3)同样打开终端,使用p

Python经典练习题(三)

文章目录🍀第一题🍀第二题🍀第三题🍀第一题输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。本题需要我们掌握的知识点在于,判断字符串,是数字还是字母还是啥的,当然在Python内置中几乎都可以找到我们需要的下表我将介绍一些常用的判断函数判断函数描述isalnum()判断是否为字母或数字(字母数字组

SQL Server 数据库变成单个用户怎么办

参考技术A1、首先我们打开SQLSERVER的管理控制台,找到一个要设置角色的用户。2、下面我们将为这个用户赋予创建数据库的角色,我们先用这个用户登录管理工具看一下是否具有创建用户的权限。3、进行数据库创建的时候,提示如下的错误,证明这个用户不具备这个角色的权限。4、下面我们登录sa用户,找到这个用户,右键单击选择属性

热文推荐