本文来自 GoGoCode 用户涛哥投稿
什么是「垃圾」代码?
权限管理场景中,我们经常会根据权限码的不同来调用不同的接口,或者展现不同的UI界面,以实现功能的灰度测试,在新功能稳定运行之后,全量开放给更多的用户。
比如我们的业务代码中,用全局对象Tryout挂载不同的权限属性,比如某个功能权限码sid=123,那我们的全局对象在经过初始化之后,会以固定的格式Tryout.TRYOUT_SID_123来标识当前用户是否有当前权限。
if(Tryout.TRYOUT_SID_123) {
doSomething()
}
else {
doOtherthing()
}
或者:
let modelUrl = Tryout.TRYOUT_SID_123 ? 'url_A' : 'url_B';
或者:
let obj = {
proA: Tryout.TRYOUT_SID_123 ? 'aaa' : 'bbb'
}
在模板代码中,我们以mustache模板为例,我们的模板代码可能会有如下代码:
{{if Tryout.TRYOUT_SID_123}}
<templateA>
{{else}}
<templateB>
{{/if}}
以上场景的代码会因为一次功能的迭代,存在我们的业务代码的js文件和html文件中。
那么随着时间的推移,项目的不断迭代,业务逻辑不断增加,这些代码会变得越来越多,相互堆叠在一起,使得我们的代码看起来越来越复杂。
最后代码看起来可能是这样:
if(Tryout.TRYOUT_SID_123 && Tryout.TRYOUT_SID_234) {
doSomething()
}
else if(Tryout.TRYOUT_SID_345 || Tryout.TRYOUT_SID_456){
doOtherthing()
}
...
else {
...
}
当我们再次回到这些页面开发新的功能的时候,这些逻辑堆叠在一起,我们就需要去了解之前的代码到底做了什么,为什么会有这些逻辑,梳理完这些老的代码,我们才能去做新功能的开发,上面的示例看起来比较简单,但是实际上的业务比上述例子要复杂的多,这样在梳理这些逻辑的时候,会消耗掉我们很多的开发时间。
实际上,随着业务权限的放开,很多的功能其实在经过一段时间的灰度测试之后,已经开放给了所有的客户,也可能因为功能的使用率比较低,而做了下线处理,也就是说,很多的条件判断逻辑在代码中已经不需要了,那这些代码仍然存在在业务逻辑中,这些代码除了增加我们的开发成本,毫无其他用处。
如何清理“垃圾”代码
既然这些代码已经是过去时了,我们整理一下这些代码,不就好了吗,仍然以最开始的示例代码为例:
if(Tryout.TRYOUT_SID_123) {
doSomething()
}
else {
doOtherthing()
}
在 Tryout.TRYOUT_SID_123 === true
时,我们此时就不需要关注 else 里面的内容了,上面的代码就可以简化为:
doSomething()
在 Tryout.TRYOUT_SID_123 === false
时,就可以简化为:
doOtherthing()
嗯,看起来还是可以手动去做的。
但是我在我们的业务代码中,我们的权限码的数量已经快要达到四位数了,而每一个权限吗对应的代码逻辑模块可能达到几十上百处,然后再想想,还有欲望去手动整理吗,我想谁也不会想去浪费时间整理这些“垃圾”。
做完了,看不到任何效果,做错了,哪怕是多删了一个字符,也会让业务代码崩掉,这个风险和收益是完全不成正比的,就这么放着的话,下次功能迭代的时候,遇到功能特别复杂的页面,看到各种权限代码穿插,完全没有开发的欲望。
那怎么办呢?我们能否有一种自动化的方式去帮我们做这件事,只要告诉工具哪个权限码全量了或者下线了,就能自动帮我们清理掉这些逻辑呢?
场景整理
我们先从js文件的转换看起,经过梳理这些权限码所在的代码片段,我们发现 99% 的场景都是以下三种场景
变量对象赋值场景
var a = Tryout.TRYOUT_SID_sid
let b = Tryout.TRYOUT_SID_sid
const c = Tryout.TRYOUT_SID_sid
let d = {
obj: Tryout.TRYOUT_SID_sid
}
三元运算场景
Tryout.TRYOUT_SID_ ? 'aaa' : 'bbb'
!Tryout.TRYOUT_SID_ ? 'aaa' : 'bbb'
条件判断场景 if...else
if(Tryout.TRYOUT_SID_123 && aaa || bbb) {
doSomething()
}
else if(Tryout.TRYOUT_SID_234) {
doOtherthing()
}
else {
doElse()
}
现在我们基本整理出了上面三种需要处理的场景,因为我们的权限码是固定的格式:Tryout_TRYOUT_SID_xxx
,看起来是复合正则匹配的场景的,那我们是否可以通过正则匹配的方式来处理代码呢,思考一下。
代码中的条件判断写法千变万化,运算的优先级,各种嵌套,一句话,代码的写法多种多样,没有固定的规则去适配这些规则。
看到这里,自然而然就想到,如果用 AST 来处理这些代码,是不是会变得很简单呢?
看过这篇文章:0成本上手AST,用GoGoCode解决Vue2迁移Vue3难题 后,感觉这是个操作AST的神器,我决定用它来试一试!
代码处理
我们总结一下上述三个需要处理的场景,假设我们要处理的权限码已经全量(=== true
),比如针对赋值场景,我们的转换目标如下:
var a = Tryout.TRYOUT_SID_sid
let b = Tryout.TRYOUT_SID_sid
const c = Tryout.TRYOUT_SID_sid
let d = {
obj: Tryout.TRYOUT_SID_sid
}
转换后 =>
var a = true
let b = true
const c = true
let d = {
obj: true
}
GoGoCode 的转换代码:
// 变量赋值场景
result = AST.replace([
`var $_$ = Tryout.TRYOUT_SID_${sid}`,
`let $_$ = Tryout.TRYOUT_SID_${sid}`,
`const $_$ = Tryout.TRYOUT_SID_${sid}`
], 'let $_$ = true;')
三元运算场景:
let test = Tryout.TRYOUT_SID_sid ? 'aaa' : 'bbb'
let test = !Tryout.TRYOUT_SID_sid ? 'aaa' : 'bbb'
转换后 =>
let test = 'aaa'
let test = 'bbb'
GoGoCode 的转换代码:
// 三元运算符为true场景
result = AST.replace(`Tryout.TRYOUT_SID_${sid} ? $_$1 : $_$2`, '$_$1')
// 三元运算符为false场景
result = AST.replace(`!Tryout.TRYOUT_SID_${sid} ? $_$1 : $_$2`, '$_$2')
条件判断if
:
嗯?这里的 GoGoCode 转换怎么写呢,因为 if 里面的场景实在是太多了, if 语句里面可以有表达式,有逻辑运算,看起来简单的 replace 方案已经无法处理这种场景了。
然后我们回过头去看看上面的赋值语句以及三元运算的转换,我们写的规则是不是太简单了,如果代码逻辑再稍微复杂一点,比如三元运算:
let test = Tryout.TRYOUT_SID_sid ? 'aaa' : (isA ? 'bbb' : 'ddd');
上述是最简单的代码转换,要覆盖更多场景,我们还要下更多的功夫。
逻辑运算处理
我们举个更复杂的例子,试用功能变量 Tryout.TRYOUT_SID_123
参与了复杂的逻辑运算
if(Tryout.TRYOUT_SID_123 || (aa && bb) && cc || (a == b)) {
doSomething()
}
else {
doOtherthing()
}
if 语句里面包含的内容,在AST结构里面叫做 test,其实我们知道 Tryout.TRYOUT_SID_123 === true
,那么true
与任何值的||
运算,最终结果都是true
,我们根本不关心这个test的构成。
反过来,如果 Tryout.TRYOUT_SID_123 === false
,false
与其他值的||
操作,那就要忽略这个false
,继续执行后面的判断,两种不同的场景,转换后的代码也是完全不同的,我们希望得到的转换结果如下:
Tryout.TRYOUT_SID_123 === true
时
doSomething()
Tryout.TRYOUT_SID_123 === false
时
if((aa && bb) && cc || (a == b)) {
doSomething()
}
else {
doOtherthing()
}
总结一下,在这个条件判断的运算中,这里我们只需要去计算 If 语句的值,就可以得到我们希望转换的代码结果:
如果值为true
,我们就把 if 语句里面的内容拿出来;
如果值为false
,我们就把 else 语句里面的内容拿出来;
如果test值不确定,那就清理掉确定的部分,比如上面的下线场景,我们删除确认为false
的 Tryout.TRYOUT_SID_123
就可以了,其他不变。
再回头看看三元运算和条件赋值,其实都是逻辑运算。
逻辑运算简化
看到这里,我们的核心问题就变成了逻辑运算的简化问题,这里用一张简单的图给大家分享一下处理的过程,当然这里也要借助 GoGoCode 的强大的查找功能:
我们把逻辑运算的处理封装成了一个工具函数excuteIF,这个工具函数接受一个条件判断的语句,接受一个对象作为参数,这个参数用来标识已知的条件
excuteIF(
'(Tryout.TRYOUT_SID_123 || (aa && bb) && cc || (a == b)',
{
Tryout.TRYOUT_SID_123: true
}
)
这个函数的返回简化后的结果,比如上面 Tryout.TRYOUT_SID_123===false
的例子,那将返回
(aa && bb) && cc || (a == b)
我们来尝试实现一下这个函数:
const $ = require('gogocode')
excuteIF = function (caseStr, options) {
// 生成gogocodeAST
let g_ast = $(caseStr)
// 循环处理入参中传入的值
for (let o in options) {
// 查找目标代码
let node = g_ast.find(o)
// 标识查找的目标值是true还是false,以执行不同的操作
let excuteType = options[o]
let parent = node.parent()
let nodePath = node[0].nodePath
let parentNode = parent[0].nodePath.node
// 如果父级为逻辑运算
if (parentNode.type === 'LogicalExpression') {
// true场景
if(excuteType) {
// 与true「或」操作
if (parentNode.operator === '||') {
parent.replaceBy($('true'))
}
// 与true「与操作」
else if (parentNode.operator === '&&') {
// 替换掉目标代码
if (nodePath.name === 'left') {
parent.replaceBy(parentNode.right)
} else if (nodePath.name === 'right') {
parent.replaceBy(parentNode.left)
}
}
}
// false场景
else {
// 与false「与」操作
if (parentNode.operator === '&&') {
parent.replaceBy($('false'))
}
// 与false「或」操作」
else if (parentNode.operator === '||') {
// 替换掉目标代码
if (nodePath.name === 'left') {
parent.replaceBy(parentNode.right)
} else if (nodePath.name === 'right') {
parent.replaceBy(parentNode.left)
}
}
}
}
}
// 重新生成代码
return g_ast.generate()
}
let result = excuteIF('Tryout.TRYOUT_SID_123 || aa && bb', {
'Tryout.TRYOUT_SID_123': false
})
// result这里被处理成了aa && bb
return result
以上代码的执行效果,大家可以到 playground 亲自试试,代码地址点这里
成果展示
有了上述理论基础,我们就可以尝试进行真实业务代码的处理了:
可以看到,代码转换之后,代码被大大的简化了,逻辑也变得足够清晰,在简洁的代码基础之上再去做迭代,是不是感觉有信心多了。
总结
到这里,再回过头来整理一下我们清理js文件的思路:
- 拿到已经全量或者下线的权限码
- 获取含有特定权限码标记的文件
- 找到包含权限码的代码块
- 处理逻辑运算的结果
- 根据逻辑运算的结果操作AST树,替换相应的代码,生成新的代码
然后我们再把上述步骤封装成一个命令行工具,这样我们现在敲入npm run sid -- 123
,就可以完成单个的权限码123的清理工作了。
安利时间
大家还可能会问我,为什么是 GoGoCode 呢,为什么不是 Babel 或者 jscodeshift 呢,当然可以是,但是我只能告诉你,相比较其他两个工具,GoGoCode 能更快的让你上手,而且代码量非常少,API 相当丰富,playground 也已上线,如果你还不了解,那么现在就开始吧:
GoGoCode的Github仓库(新项目求star ^_^) github.com/thx/gogocod…
GoGoCode的官网 gogocode.io/zh
可以来 playground 快速体验一下 play.gogocode.io/
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!