• 同一份代码怎能在不同环境表现不同?记一个可选链因为代码压缩造成的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,本文结束。

    最后还是要吐槽一句,原神小保底今天歪了个刻晴,死气我了!!!!

  • 相关阅读:
    [leedcode 46] Permutations
    [leedcode 45] Jump Game II
    [leedcode 43] Multiply Strings
    [leedcode 42] Trapping Rain Water
    [leedcode 41] First Missing Positive
    [leedcode 40] Combination Sum II
    全排列
    [leedcode 39] Combination Sum
    [leedcode 38] Count and Say
    调和级数求和(分块)
  • 原文地址:https://www.cnblogs.com/echolun/p/15240139.html
Copyright © 2020-2023  润新知