资讯专栏INFORMATION COLUMN

使用 jsinspect 检测前端代码库中的重复/近似代码

ZoomQuiet / 725人阅读

摘要:使用检测前端代码库中的重复近似代码从属于笔者的前端入门与工程实践,更多前端相关学习资料推荐阅读前端每周清单第期学习资源,发布,六问程序员如何成长泛前端知识图谱。利用对于或者代码构建语法树,根据不同的节点类型,譬如等标记相似结构的代码块。

使用 jsinspect 检测前端代码库中的重复/近似代码 从属于笔者的 Web 前端入门与工程实践,更多前端相关学习资料推荐阅读前端每周清单第6期:Angular 4.0学习资源,Egg.js 1.0发布,六问CTO程序员如何成长、泛前端知识图谱(Web/iOS/Android/RN)。

在开发的过程中我们往往会存在大量的复制粘贴代码的行为,这一点在项目的开发初期尤其显著;而在项目逐步稳定,功能需求逐步完善之后我们就需要考虑对代码库的优化与重构,尽量编写清晰可维护的代码。好的代码往往是在合理范围内尽可能地避免重复代码,遵循单一职责与 Single Source of Truth 等原则,本部分我们尝试使用 jsinspect 对于代码库进行自动检索,根据其反馈的重复或者近似的代码片进行合理的优化。当然,我们并不是单纯地追求公共代码地完全剥离化,过度的抽象反而会降低代码的可读性与可理解性。jsinspect 利用 babylon 对于 JavaScript 或者 JSX 代码构建 AST 语法树,根据不同的 AST 节点类型,譬如 BlockStatement、VariableDeclaration、ObjectExpression 等标记相似结构的代码块。我们可以使用 npm 全局安装 jsinspect 命令:

Usage: jsinspect [options] 


Detect copy-pasted and structurally similar JavaScript code
Example use: jsinspect -I -L -t 20 --ignore "test" ./path/to/src


Options:

  -h, --help                         output usage information
  -V, --version                      output the version number
  -t, --threshold            number of nodes (default: 30)
  -m, --min-instances        min instances for a match (default: 2)
  -c, --config                       path to config file (default: .jsinspectrc)
  -r, --reporter [default|json|pmd]  specify the reporter to use
  -I, --no-identifiers               do not match identifiers
  -L, --no-literals                  do not match literals
  -C, --no-color                     disable colors
  --ignore                  ignore paths matching a regex
  --truncate                 length to truncate lines (default: 100, off: 0)

我们也可以选择在项目目录下添加 .jsinspect 配置文件指明 jsinspect 运行配置:

{
  "threshold":     30,
  "identifiers":   true,
  "literals":      true,
  "ignore":        "test|spec|mock",
  "reporter":      "json",
  "truncate":      100,
}

在配置完毕之后,我们可以使用 jsinspect -t 50 --ignore "test" ./path/to/src 来对于代码库进行分析,以笔者找到的某个代码库为例,其检测出了上百个重复的代码片,其中典型的代表如下所示。可以看到在某个组件中重复编写了多次密码输入的元素,我们可以选择将其封装为函数式组件,将 labelhintText 等通用属性包裹在内,从而减少代码的重复率。

Match - 2 instances

./src/view/main/component/tabs/account/operation/login/forget_password.js:96,110
return 
{ this.setState({ userPwd: value }) }} />
./src/view/main/component/tabs/my/login/forget_password.js:111,125 return
{ this.setState({ userPwd: value }) }} />

笔者也对于 React 源码进行了简要分析,在 246 个文件中共发现 16 个近似代码片,并且其中的大部分重复源于目前基于 Stack 的调和算法与基于 Fiber 重构的调和算法之间的过渡时期带来的重复,譬如:

Match - 2 instances

./src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js:134,153
  var value = props.value;
  if (value != null) {
    // Cast `value` to a string to ensure the value is set correctly. While
    // browsers typically do this as necessary, jsdom doesn"t.
    var newValue = "" + value;

    // To avoid side effects (such as losing text selection), only set value if changed
    if (newValue !== node.value) {
      node.value = newValue;
    }
    if (props.defaultValue == null) {
      node.defaultValue = newValue;
    }
  }
  if (props.defaultValue != null) {
    node.defaultValue = props.defaultValue;
  }
},

postMountWrapper: function(element: Element, props: Object) {

./src/renderers/dom/stack/client/wrappers/ReactDOMTextarea.js:129,148
  var value = props.value;
  if (value != null) {
    // Cast `value` to a string to ensure the value is set correctly. While
    // browsers typically do this as necessary, jsdom doesn"t.
    var newValue = "" + value;

    // To avoid side effects (such as losing text selection), only set value if changed
    if (newValue !== node.value) {
      node.value = newValue;
    }
    if (props.defaultValue == null) {
      node.defaultValue = newValue;
    }
  }
  if (props.defaultValue != null) {
    node.defaultValue = props.defaultValue;
  }
},

postMountWrapper: function(inst) {

笔者认为在新特性的开发过程中我们不一定需要时刻地考虑代码重构,而是应该相对独立地开发新功能。最后我们再简单地讨论下 jsinspect 的工作原理,这样我们可以在项目需要时自定义类似的工具以进行特殊代码的匹配或者提取。jsinspect 的核心工作流可以反映在 inspector.js 文件中:

... 
this._filePaths.forEach((filePath) => {
  var src = fs.readFileSync(filePath, {encoding: "utf8"});
  this._fileContents[filePath] = src.split("
");
  var syntaxTree = parse(src, filePath);
  this._traversals[filePath] = nodeUtils.getDFSTraversal(syntaxTree);
  this._walk(syntaxTree, (nodes) => this._insert(nodes));
});

this._analyze();
...

上述流程还是较为清晰的,jsinspect 会遍历所有的有效源码文件,提取其源码内容然后通过 babylon 转化为 AST 语法树,某个文件的语法树格式如下:

Node {
  type: "Program",
  start: 0,
  end: 31,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 2, column: 15 },
     filename: "./__test__/a.js" },
  sourceType: "script",
  body:
   [ Node {
       type: "ExpressionStatement",
       start: 0,
       end: 15,
       loc: [Object],
       expression: [Object] },
     Node {
       type: "ExpressionStatement",
       start: 16,
       end: 31,
       loc: [Object],
       expression: [Object] } ],
  directives: [] }
{ "./__test__/a.js": [ "console.log(a);", "console.log(b);" ] }

其后我们通过深度优先遍历算法在 AST 语法树上构建所有节点的数组,然后遍历整个数组构建待比较对象。这里我们在运行时输入的 -t 参数就是用来指定分割的原子比较对象的维度,当我们将该参数指定为 2 时,经过遍历构建阶段形成的内部映射数组 _map 结构如下:

{ "uj3VAExwF5Avx0SGBDFu8beU+Lk=": [ [ [Object], [Object] ], [ [Object], [Object] ] ],
  "eMqg1hUXEFYNbKkbsd2QWECLiYU=": [ [ [Object], [Object] ], [ [Object], [Object] ] ],
  "gvSCaZfmhte6tfnpfmnTeH+eylw=": [ [ [Object], [Object] ], [ [Object], [Object] ] ],
  "eHqT9EuPomhWLlo9nwU0DWOkcXk=": [ [ [Object], [Object] ], [ [Object], [Object] ] ] }

如果有大规模代码数据的话我们可能形成很多有重叠的实例,这里使用了 _omitOverlappingInstances 函数来进行去重;譬如如果某个实例包含节点 abcd,另一个实例包含节点组 bcde,那么会选择将后者从数组中移除。另一个优化加速的方法就是在每次比较结束之后移除已经匹配到的代码片:

_prune(nodeArrays) {
  for (let i = 0; i < nodeArrays.length; i++) {
    let nodes = nodeArrays[i];
    for (let j = 0; j < nodes.length; j++) {
      this._removeNode(nodes[j]);
    }
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82229.html

相关文章

  • JavaScript权威指南笔记(上)-语言核心

    摘要:二进制浮点数和四舍五入错误在使用实数时,常常只是真实值的一个近似表示。作用域分类全局函数,块级作用域链变量对象用于存储执行上下文中的变量函数声明函数参数变量初始化阶段浏览器截图浏览器截图代码执行阶段类和模块 词法结构 字符集 使用Unicode编写 ES3 Unicode2.1+ ES5 Unicode3+ 区分大小写 注释 // 注释 /* 注释 */ 标识符和保留字 必须以字...

    skinner 评论0 收藏0
  • 咋做长文本去重

    摘要:新问题抛出有没有一种签名算法,如果文本非常相似,签名值也非常相似呢二文本相似性的签名算法上文提出的问题,可以用局部敏感哈希解决,局部敏感哈希是一类文本越相似,哈希值越相似的算法,有兴趣的同学自行百度,这里分享一下的思路。 缘起:(1)原创不易,互联网抄袭成风,很多原创内容在网上被抄来抄去,改来改去(2)百度的网页库非常大,爬虫如何判断一个新网页是否与网页库中已有的网页重复呢?这是本文要...

    coordinate35 评论0 收藏0
  • 算法分析 - Algorithms, Part I, week 1 ANALYSIS OF ALGO

    摘要:实际上这个情形中存在幂定律实际上绝大多数的计算机算法的运行时间满足幂定律。基于研究得知,原则上我们能够获得算法,程序或者操作的性能的精确数学模型。 前言 上一篇:并查集下一篇:栈和队列 在算法性能上我们常常面临的挑战是我们的程序能否求解实际中的大型输入:--为什么程序运行的慢?--为什么程序耗尽了内存? 没有理解算法的性能特征会导致客户端的性能很差,为了避免这种情况的出线,需要具备算法...

    Leo_chen 评论0 收藏0
  • JavaScript 工作原理之三-内存管理及如何处理 4 类常见的内存泄漏问题(译)

    摘要:这是因为我们访问了数组中不存在的数组元素它超过了最后一个实际分配到内存的数组元素字节,并且有可能会读取或者覆写的位。包含个元素的新数组由和数组元素所组成中的内存使用中使用分配的内存主要指的是内存读写。 原文请查阅这里,本文有进行删减,文后增了些经验总结。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第三章。 我们将会讨论日常使用中另一个被开发...

    weknow619 评论0 收藏0
  • JavaScript 工作原理之三-内存管理及如何处理 4 类常见的内存泄漏问题(译)

    摘要:这是因为我们访问了数组中不存在的数组元素它超过了最后一个实际分配到内存的数组元素字节,并且有可能会读取或者覆写的位。包含个元素的新数组由和数组元素所组成中的内存使用中使用分配的内存主要指的是内存读写。 原文请查阅这里,本文有进行删减,文后增了些经验总结。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第三章。 我们将会讨论日常使用中另一个被开发...

    isaced 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<