最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • GoGoCode协助清理代码中的「垃圾」

    正文概述 掘金(阿里妈妈前端快爆)   2021-04-21   439

    本文来自 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 === falsefalse与其他值的||操作,那就要忽略这个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值不确定,那就清理掉确定的部分,比如上面的下线场景,我们删除确认为falseTryout.TRYOUT_SID_123 就可以了,其他不变。

    再回头看看三元运算和条件赋值,其实都是逻辑运算。

    逻辑运算简化

    看到这里,我们的核心问题就变成了逻辑运算的简化问题,这里用一张简单的图给大家分享一下处理的过程,当然这里也要借助 GoGoCode 的强大的查找功能:

    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 亲自试试,代码地址点这里

    成果展示

    有了上述理论基础,我们就可以尝试进行真实业务代码的处理了:

    GoGoCode协助清理代码中的「垃圾」

    可以看到,代码转换之后,代码被大大的简化了,逻辑也变得足够清晰,在简洁的代码基础之上再去做迭代,是不是感觉有信心多了。

    总结

    到这里,再回过头来整理一下我们清理js文件的思路:

    1. 拿到已经全量或者下线的权限码
    2. 获取含有特定权限码标记的文件
    3. 找到包含权限码的代码块
    4. 处理逻辑运算的结果
    5. 根据逻辑运算的结果操作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/


    起源地下载网 » GoGoCode协助清理代码中的「垃圾」

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元