同一份代码怎能在不同环境表现不同?记一个可选链因为代码压缩造成的bug

壹 ❀ 引
某一天,CSM日常找我反馈客户紧急工单,说有一个私有部署客户升级版本后,发现一个功能使用不太正常。因为我们公司客户分为两种,一种是SaaS客户,客户侧使用的版本被动跟随主版本变动,而私有部署客户而是由我们交付版本给客户,客户有选择权决定是否升级,且用户数据都由客户侧服务器自行存储,对客户来说更安全。而我在排查这个问题的过程中,其实心里也产生了一些自认为的真理,结果排查下来也是啪啪打脸,因为问题解决的过程也挺有意思,所以本文做个记录。
贰 ❀ 场景还原
先来描述下问题,因为我们公司是做研发管理工具(ONES,欢迎体验),那么自然有工时登记相关的功能,对于一个工作项,假设当前用户有所有用户登记工时权限,那么他点击登记工时,就可以选择为某个用户登记工时(下拉框可以选择),假设他只有登记自己工时的权限,那么登记工时的用户下拉将会是禁用状态且用户默认选择就是自己。


而客户侧问题是,一个用户明明只有给自己登工时的权限,但现在登记工时,用户选择居然可以选其他人,而且选择其他人后登记接口报错提示无权限登记工时,且这个问题是私有部署升级后才出现,之前都很正常。
我在与产品经理确认过工时权限对应表现后,分别在自己的开发环境,测试环境,集成测试环境以及SaaS正式环境分别做了测试,发现都无法复现这个问题,但客户侧就是能稳定复现,同一份前端代码,不同环境表现不同,所以初步推测可能是后端问题,会不会是账号脏数据,或者接口返回数据异常。
目前已知信息:
1.客户侧控制台无报错,接口无报错,但需要确认客户侧权限接口返回是否正常
2.近期升级引发,那么很可能是近期改动,找到对应功能文件查看近期commit记录,便于搜集信息。
3.目前掌握信息过少,客户侧稳定复现,支持远程排查。
叁 ❀ 不稳定的可选链.?
既然客户能稳定复现,而且支持远程,那么最有效的方法就是远程客户搜集信息,大不了直接读客户侧源码,但远程机会有限,我也不好意思远程半天占用客户太久时间,所以远程前去梳理了下对应功能的逻辑,并查看了近期代码提交记录,果然发现 2 个月前对应文件有改动,遗憾的是与作者沟通也并未获得太多有用信息(毕竟如果他知道问题这个bug就不会逃逸了)。
前面说了只有管理所有成员工时权限时,下拉框才能展开,而控制的开关是一个名为hasUpdateAllManHourPermission的变量。另外,因为交付给客户的私有部署包都是高度压缩的代码(很多单词被压缩成字母,文件名也变了),一个文件几十万行代码,为了避免找不到位置,我也提前保留了几个具有特征性的单词,运气好可以通过它们大致定位范围。
顺利连接客户电脑,在让客户演示后,问题确实与客户说的一样,但由于没有接口报错,咱也没办法直接定位代码文件。接管鼠标,打开控制台,选择 Search,这样控制台底端会出现一个搜索栏,而这个搜索的范围是当前页面运行的所有资源,输入hasUpdateAllManHourPermission回车,运气还不错,这个单词没被压缩掉。


匹配结果可能会有多个,因此需要一个个点击,去阅读单词所在的逻辑是不是我们想要的,通过这种办法可以快速缩小代码范围,简单查看后,终于成功定位到我想要的范围,直接断点hasUpdateAllManHourPermission查找,刷新页面,鼠标移上去一看一个false,说明确实返回的是没权限....甩锅后端失败 = =,妥妥前端问题。
莫慌,排查问题的一个思路是,如果问题点的排查法失效,那就从这个点扩散范围,形成一个圈,我们接着阅读它的上下文,一遍没看出问题,那就反反复复看不断加范围,皇天不负有心人,阅读中我终于成功定位问题。原来除了权限点获取外,还有段这样的逻辑,假设当前用户有所有用户登记工时权限,那么选择人员的下拉框会将用户默认成当前工作项的负责人,因此前端需要通过工作项ID获取到工作项对象,再从这个对象中知晓此工作项负责人是谁。
而获取工作项ID的逻辑,是这么写的,注意,这是未压缩前的代码:
// 工作项id默认从state中获取,这里用了可选链?.
getCurrentTask = (taskUUID = this.state?.taskUUID) => {
const { getTask } = this.props;
if (!taskUUID) return null;
const task = getTask(taskUUID);
return task;
}
taskYYUD默认从state中取,如果没找到工作项ID,那么就不查找工作项了,直接返回null,而接下来其实还有段逻辑,如果找不到task也会返回null,这段我就不贴了
而在客户侧压缩后变成了这样:

之前可选链在代码压缩后变成了三元表达式,这里的 e 就是taskUUID,默认是undefined,导致下面的t(e || this.state.taskUUID)没执行,直接返回了一个null。

而我将断点移动到下面括号中的state,可以看到state中确实存在UUID,只是压缩后因为三元的缘故它没有机会被赋值了。
成功破案,罪魁祸首就是在函数形参中使用了可选链 ?. ,可以设想一下,当前组件是一定存在state的,由于state是一个对象,即便taskUUID不存在,直接这么复制其实也没问题,所以修复方法就是去除可选链。
taskUUID = this.state?.taskUUID
// 改为
taskUUID = this.state.taskUUID
那为什么这个问题只有私有部署环境出现,其它环境都没问题呢?在与构建以及运维部署那边的同学沟通后得知,私有部署因为要交付代码,提供的代码压缩规则其实与SaaS环境压缩规则不同,这就是为啥在其它环境怎么都复现不了....
但不管怎么说,我们不建议在形参使用可选链,或者说不建议在给一个参数设置的默认值是一个obj.xx.xx的格式,因为此时函数还没执行,在未知情况下,可能这个obj或者某个上下文都没准备好,代码报错还好,像上面的问题被压缩转义,错误都没有,真就很难排查了。
我们还是推荐形参默认值是一个确定的数值,如果一定是对象链式访问的值,还是推荐将其写入函数中,这样风险最小,比如:
const fn = (name) {
let nameCopy = name || this.obj.name;
}
肆 ❀ 总
最近因为荣耀这个客户签约,可以说公司是全员进入加班状态,上层投入了一大半研发加入了修bug队伍,每天也都有同事找我资讯一些痛苦bug的排查思路,可以说是一件略微开心的事(都知道我们组的痛苦了!!!)。

今天大佬也是找到我,让我把身上简单的bug都转出来给新人练手,对我说这种bug不符合我现在的段位,让我负责一些难度更高的问题....未来估计还会痛苦好一段时间,不过也会发现很多有趣的bug吧。
另外,关于可选链用法可以参考这篇文章JS 可选链操作符?. 空值合并运算符?? 详解,更精简的安全取值与默认值设置小技巧,那么到这里又分享了一个有趣的bug,本文结束。
同一份代码怎能在不同环境表现不同?记一个可选链因为代码压缩造成的bug的更多相关文章
- 一个 11 行 Python 代码实现的神经网络
一个 11 行 Python 代码实现的神经网络 2015/12/02 · 实践项目 · 15 评论· 神经网络 分享到:18 本文由 伯乐在线 - 耶鲁怕冷 翻译,Namco 校稿.未经许可,禁止转 ...
- 窥探原理:实现一个简单的前端代码打包器 Roid
roid roid 是一个极其简单的打包软件,使用 node.js 开发而成,看完本文,你可以实现一个非常简单的,但是又有实际用途的前端代码打包工具. 如果不想看教程,直接看代码的(全部注释):点击地 ...
- 给 VS 2010 选一个好用的代码行数统计器(转)
给 VS 2010 选一个好用的代码行数统计器 分类: Tricks2011-02-25 05:40 3891人阅读 评论(0) 收藏 举报 2010c 推荐一个VS插件,支持2005/2008/20 ...
- 做了一个简易的git 代码自动部署脚本
做了一个简易的git 代码自动部署脚本 http://my.oschina.net/caomenglong/blog/472665 发表于2个月前(2015-06-30 21:08) 阅读(200 ...
- 想做一个整合开源安全代码扫描工具的代码安全分析平台 - Android方向调研
想做一个整合开源安全代码扫描工具的代码安全分析平台 - Android方向调研 http://blog.csdn.net/testing_is_believing/article/details/22 ...
- 50个必备的实用jQuery代码段+ 可以直接拿来用的15个jQuery代码片段
50个必备的实用jQuery代码段+ 可以直接拿来用的15个jQuery代码片段 本文会给你们展示50个jquery代码片段,这些代码能够给你的javascript项目提供帮助.其中的一些代码段是从j ...
- 一个只有99行代码的JS流程框架
张镇圳,腾讯Web前端高级工程师,对内部系统前端建设有多年经验,喜欢钻研捣鼓各种前端组件和框架. 最近一直在想一个问题,如何能让js代码写起来更语义化和更具有可读性. 上周末的时候突发奇想,当代码在运 ...
- 一个只有99行代码的JS流程框架(二)
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 张镇圳,腾讯Web前端高级工程师,对内部系统前端建设有多年经验,喜欢钻研捣鼓各种前端组件和框架. 导语 前面写 ...
- CUDA学习,第一个kernel函数及代码讲解
前一篇CUDA学习,我们已经完成了编程环境的配置,现在我们继续深入去了解CUDA编程.本博文分为三个部分,第一部分给出一个代码示例,第二部分对代码进行讲解,第三部分根据这个例子介绍如何部署和发起一个k ...
- 40多行python代码开发一个区块链。
40多行python代码开发一个区块链?可信吗?我们将通过Python 2动手开发实现一个迷你区块链来帮你真正理解区块链技术的核心原理.python开发区块链的源代码保存在Github. 尽管有人认为 ...
随机推荐
- java基础(12)--static变量/方法 与 无 static的变量/方法的区别
一.static方法与非static方法的区别: 1.带有static方法调用:使用类名.方法名(),(建议,但也支持,"引用".变量的方式访问) 2.没有static方法调用(实 ...
- 洛谷 P9683 A Certain Forbidden Index 题解
题目链接:\(\color{Purple}\texttt{P9683 A Certain Forbidden Index}\). 填坑.提供一个相对好写的做法. 考虑把一堆不交的区间绑在一起问(即先询 ...
- Dubbo入门2:Springboot+Dubbo2.6.0+ZooKeeper3.4.8整合
整合Springboot+Dubbo2.6.0+ZooKeeper3.4.8 本文主要目的:记录整合以上3个框架的配置文件的写法 此文只在<Dubbo入门1>的基础上略作修改,仅记录修改的 ...
- 基于java+springboot的旅游信息网站、旅游景区门票管理系统
该系统是基于java+springboot开发的旅游景区门票管理系统.是给师弟开发的大四实习作品.学习过程中,遇到问题可以咨询github作者. 演示地址 前台地址: http://travel.gi ...
- Nacos源码 (1) 源码编译及idea环境
本文介绍从gitee下载nacos源码,在本地编译,并导入idea进行本地调试. 从gitee下载源码 由于github访问速度慢,所以我选择使用gitee的镜像仓库: git clone https ...
- Laravel - 控制器的session ( 转载 )
设置路由 //使用session,需要开启session,//session的开始类在/app/Kernel下//protected $middlewareGroups = [// 'web' =&g ...
- 【转帖】JVM的发展历程
目录 1.Sun Classic VM 2.Exact VM 3.Sun HotSpot(主流) 4.JRockit 5.IBM J9 6.下一代虚拟机Graal VM 1.Sun Classic V ...
- Redis性能问题诊断以及scan命令耗时分析
Redis性能问题诊断以及scan命令耗时分析 摘要 最近公司有项目反馈卡顿. 卡顿一小时后自己被拉入群聊. 同事已经基本上定位到问题原因. 我这边想使用朴素的性能观点进行一下性能问题的拆解 为了提高 ...
- Windows 磁盘部分性能数据获取
Windows 磁盘部分性能数据获取 摘要 每次晚上加班总有收获 这次发现了一个fio for windows版本的压测程序, 准备学习和使用一下. https://github.com/axboe/ ...
- Vue基础系列文章11---router基本使用
1.系统中引入路由js文件,加两个连接,分别到用户管理和用户注册页面 <router-link to="/user">用户列表</router-link> ...