背景:重复率是 oasis(衡量前端工程质量平台)的检测指标之一,对项目定期进行代码重复率检测是一个很有意义的事,可以帮助开发人员发现冗余代码,进行代码抽象和重构。
概念
代码重复(英文:duplicate code,也叫代码克隆)在程序设计中表示一段源代码在一个程序,或者一个团体所维护的不同程序中重复出现,是不希望出现的现象。为避免巧合,只有一定数量的代码完全相同才能判定为代码重复。 – 维基百科
代码重复一般分为两大类:
- 句法重复
两个函数的代码片段基本一致,只是参数名、函数名、字符等做了修改,或是多/少了某一行多代码,这种改动只是文本层面的改动,也被称为。 - 语义重复
两个函数的代码实现不同,从文本层面看区别较大,但实现的都是同一个功能。
基于句法重复、语义重复两大类,然后被划分为四小类:
- 完全一致的代码或者只修改了空格和评论(句法克隆)。
- 结构上和句法上一致的代码,例如只是修改了变量名 (句法克隆)。
- 插入和删除了部分代码(句法克隆)。
- 功能和逻辑上一致的代码,语义上的拷贝(语义克隆)。
其中前三种为句法克隆,第四种为语义克隆。检测难度也是一次递增,目前对前三种代码重复的检测已颇为成熟; 而对第四种的检测准确率仍不高,无法达到应用标准。
重复代码未必就是 copy-paste 产生的,可能就是不同人重复写的,就算是 copy-paste 产生的,在 paste 之后可能代码也会发生变化。
前端重复检测
检测代码重复的手段
- Textual:代码片段以文本/字符串/词法的形式相互比较,并且只有在两个代码片段在文本内容方面确实相同时才被发现被克隆。
- Token:在编译器的词法分析阶段,所有源代码行都被划分为一系列 Token。 然后将所有 Token 转换回 Token 序列。
- Syntactic(句法分析):
- 基于树: 提取的 AST 用于子树比较以识别相似区域。
- 基于度量:通过源代码收集度量,然后使用这些度量为每个代码片段生成向量。然后使用向量对代码的相似度进行对比。
- Semantic(语义克隆):主要检测代码片段不同,但功能相同的函数。
- Learning : 通过机器学习和统计分析的方式来进行克隆检测。
检测代码重复的流程
- 将源码拆分为对比单元(comparison units, 如 class,function,block,statement)。
- 将对比单元转化为中间表达(IR, Intermediate Representation,如 token,AST, PDG)。
- 再对这些对比单元的 IR 进行 match detection(对比),通常是将对比单元组成 clone pair 的形式:一次对比两个(c1, c2)或是多个(c1, c2, c3)。
检测工具
- jsinspect
利用 babylon 对于 JavaScript 或者 JSX 代码构建 AST 语法树,根据不同的 AST 节点类型,标记相似结构的代码块,检测效果比较好。 - jscpd:其重复率判定依据为一定长度标识符的 MD5 值是否相同,虽然结果没有 jsinspect 好,但是支持文件格式广泛,如 java、oc、js、jsx、vue、ts、less 等。
- PMD:支持 js 文件检测,也可以自己开发扩展包来解析指定的语言(需要安装 java 环境,npm 不支持,无法匹配前端脚手架)。
由于前端文件类型众多所以 oasis 最终选择了 jscpd 作为代码重复率检测工具。
重复率标准
重复率标准的制定需要参考的因素有很多,例如 tokens、项目、架构、时间等等,目前 oasis 平台是 10%,具体数值还会在运行一段时间后观察调整。
代码重复率 = 重复的行数 / 扫描的文件总行数。
降低重复率
应用中,可以参考《重构》(Refactoring)中说的三次原则(Rule of three),即同样的代码将要出现第三次前,考虑抽象它,复用它。具备小而美的工程思想,随着前端生态的完善,前端的组件化开发效率已经有了很大的提升。
总结
代码重复会让项目失控,重复代码不仅让代码量大增,造成编译速度慢,占用大量存储空间,造成了代码可维护性差,代码质量下降。通过重构从而降低代码的耦合性,这样不仅提高代码的灵活性、健壮性以及可读性,也方便后期的维护。我们也不是单纯地追求公共代码地完全剥离化,过度的抽象反而会降低代码的可读性与可理解性。
附
oasisV0.1 beta 版本目前已经完成大文件、重复代码块,圈复杂度、最佳实践、基础库落地、 npm 依赖库的安全分析功能,接入 0 成本目前已经上线欢迎大家体验、建议、吐槽。