出差解决了一个史诗级大BUG声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行
声明:“史诗级” 对于bug难度来讲有点严重了,对于带来的影响来讲一点都不为过。项目已经运行使用了四年多,前期没有经过测试直接上线的,三年多的时间反反复复的修改,开发人员换了一批又一批,代码早已堆成屎山,而这个问题是项目里所有功能的基础,竟然才才才被发现,实在是伪装的太好了
测试反馈
BUG:先选择一个标签再滑动鼠标至此类型的底部选择一个标签,传人标签与页面选择不一致
步骤1,先选择两个标签(此时传入正确)
意思就是勾选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;
}
t1
和t2
是两个待比较的键。- 如果
t1
的长度大于t2
的长度(即,t1
更长,可能是t2
的子集),那么我们检查t1
是否以t2
为前缀(t1.indexOf(t2) === 0
)。 - 如果
t2
的长度大于或等于t1
的长度,那么我们检查t2
是否以t1
为前缀(t2.indexOf(t1) === 0
)。 - 该比较器的逻辑是:如果一个字符串是另一个字符串的前缀,则认为它们是“相等”的。
5. 整体逻辑
- 经过
sortBy
和reverse
处理后,较长的键排在前面,较短的键排在后面。 uniqWith
通过比较器过滤掉所有包含更短前缀的键。最终,结果数组只保留最深层次的“叶子节点”键值。
举个例子
假设 checkedKeys
是以下数组:
javascript
复制代码
const checkedKeys = ['1', '1-2', '1-2-3', '1-4', '2', '2-1'];
按照步骤,函数执行过程如下:
-
排序并反转:
['1-2-3', '1-2', '1-4', '2-1', '1', '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' 都会被过滤掉,因为它们是其他键的前缀。
- '1-2-3' 与 '1-2','1':
-
最终输出:
['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
加上'-'
为前缀,确保t1
是t2
的子节点而不是仅仅一个字符串前缀。 -
t2.startsWith(t1 + '-')
:确保t2
是t1
的子节点。
*
startsWith
是 JavaScript 字符串对象的一个方法,用于判断一个字符串是否以另一个指定的子字符串开头。它返回一个布尔值 (true
或false
)。
修改后代码
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