likes
comments
collection
share

出差解决了一个史诗级大BUG声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行

作者站长头像
站长
· 阅读数 33

声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行使用了四年多,前期没有经过测试直接上线的,三年多的时间反反复复的修改,开发人员换了一批又一批,代码早已堆成屎山,而这个问题是项目里所有功能的基础,竟然才才才被发现,实在是伪装的太好了

测试反馈

BUG:先选择一个标签再滑动鼠标至此类型的底部选择一个标签,传人标签与页面选择不一致

步骤1,先选择两个标签(此时传入正确)

出差解决了一个史诗级大BUG声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行

出差解决了一个史诗级大BUG声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行

意思就是勾选tree组件下的子节点,先选择两个,此时传值是正常的

步骤二,滑动鼠标至此一级类型的底部选择其他类型的标签, 此时传入只显示两个标签

这里不贴图了,意思是滑到底部勾选第三个标签,结果还是传了两个标签,替换掉了其中一个

现场复现

发现: 当父节点下子节点较少时,传值可以传大于两个,一旦子节点多了之后,出现滚动条,传值就会有丢失。

于是,我就开启了漫漫找bug之路,屎山代码找bug真的是像淘金一样😂... 终于,经过各种组件套组件,山路十八弯,耗时大半天,摸清楚为什么会出现这么莫名其妙的错误。

罪魁祸首

function getLeafCheckedKeys(checkedKeys) {
    return uniqWith(reverse(sortBy(checkedKeys, it => it.length)), (t1, t2) => {
        if (t1.length > t2.length) {
            return t1.indexOf(t2) === 0
        } else {
            return t2.indexOf(t1) === 0
        }
    })
}

问题就在这里,看样子就不像是好代码的样子(先入为主),哈哈

代码分解及含义

这里用到了 lodash 库中的函数

当我勾选选择框时,传入的chekedKeys 是这样的一个数组 ['0-0-1','0-0-2,0-0-12'];

1. sortBy(checkedKeys, it => it.length)
  • sortBy 是一个排序函数,它按照每个键的长度从小到大对 checkedKeys 数组进行排序。
  • it => it.length 是排序的依据,这意味着越短的字符串会排在前面。
2. reverse(...)
  • reverse 函数将排序后的数组反转。这样一来,较长的键(可能是子节点)会排在前面,较短的键(可能是父节点)会排在后面。
3. uniqWith(...)
  • uniqWith 是一个去重函数,带有自定义的比较器。它用于生成一个没有重复项的数组,根据我们自定义的逻辑来判定两个项是否“相等”。
4. 自定义比较器 (t1, t2) => {...}
  • 这个比较器用于判断两个键是否具有“父子关系”或“包含关系”。
if (t1.length > t2.length) {
    return t1.indexOf(t2) === 0;
} else {
    return t2.indexOf(t1) === 0;
}
  • t1t2 是两个待比较的键。
  • 如果 t1 的长度大于 t2 的长度(即,t1 更长,可能是 t2 的子集),那么我们检查 t1 是否以 t2 为前缀(t1.indexOf(t2) === 0)。
  • 如果 t2 的长度大于或等于 t1 的长度,那么我们检查 t2 是否以 t1 为前缀(t2.indexOf(t1) === 0)。
  • 该比较器的逻辑是:如果一个字符串是另一个字符串的前缀,则认为它们是“相等”的。
5. 整体逻辑
  • 经过 sortByreverse 处理后,较长的键排在前面,较短的键排在后面。
  • uniqWith 通过比较器过滤掉所有包含更短前缀的键。最终,结果数组只保留最深层次的“叶子节点”键值。
举个例子

假设 checkedKeys 是以下数组:

javascript
复制代码
const checkedKeys = ['1', '1-2', '1-2-3', '1-4', '2', '2-1'];

按照步骤,函数执行过程如下:

  1. 排序并反转:

    ['1-2-3', '1-2', '1-4', '2-1', '1', '2']
    
  2. 过滤冗余键:

    • '1-2-3' 与 '1-2','1':1-2-3 保留,因为 '1-2' 和 '1' 是其前缀。
    • '1-4' 与 '1':1-4 保留。
    • '2-1' 与 '2':2-1 保留。
    • '1' 和 '2' 都会被过滤掉,因为它们是其他键的前缀。
  3. 最终输出:

    ['1-2-3', '1-4', '2-1']
    

代码拆解完,你发现什么问题了吗??? 乍一看似乎都挺对的,还让人更信服了呢,难怪这么不容易被发现

问题其实就出现在 t1.indexOf(t2) === 0 t2.indexOf(t1) === 0 这两句代码中,简单地检查字符串的开头会导致错误的判断。例如,如果 t1"0-1-12"t2"0-1-1"t1.indexOf(t2) === 0 会返回 true,根据比较器逻辑,uniqWith 认为 '0-1-12''0-1-1' 是相同的,并保留 '0-1-12',这种情况下,0-1-12 被认为是 0-1-1 的“扩展”,但这并不符合预期的逻辑,因为 0-1-12 并不是 0-1-1 的真正子集。

BUG解决

为什么会出现上述测试反馈的问题,我们也能想通了,其实并不是出现滚动条后才出现的bug,只要是点击大于等于x-x-10的子节点,就会出现这个问题。

找到原因问题自然迎刃而解,我们修改这两行代码,使用字符串的 startsWith方法。

  • t1.startsWith(t2 + '-'):检查 t1 是否以 t2 加上 '-' 为前缀,确保 t1t2 的子节点而不是仅仅一个字符串前缀。

  • t2.startsWith(t1 + '-'):确保 t2t1 的子节点。

*startsWith 是 JavaScript 字符串对象的一个方法,用于判断一个字符串是否以另一个指定的子字符串开头。它返回一个布尔值 (truefalse)。

修改后代码

function getLeafCheckedKeys(checkedKeys) {
    return uniqWith(
        reverse(sortBy(checkedKeys, it => it.length)), 
        (t1, t2) => {
            if (t1.length > t2.length) {
                return t1.startsWith(t2 + '-');
            } else {
                return t2.startsWith(t1 + '-');
            }
        }
    );
}

转载自:https://juejin.cn/post/7412502916642619404
评论
请登录