思码逸产品和指标常见问题回答 FAQ

代码当量

Q: 为什么Playground里展示的AST和我在其他地方看到过的不太一样?

因为不同语言的不同语法分析器(Parser)有自己定义的具体语法树(Concrete Syntax Tree),语法节点的定义(Schema)各不相同。为了我们的算法能比较方便的支持多种语言,我们会将各语言Parse出的CST转写成我们自己定义的一套UAST(Unified Abstract Syntax Tree)表示。 这样便只需基于UAST的Schema实现代码当量算法,同时也能消除各语言Parser中冗余的中间节点。

Q: 项目的初始提交的当量通常比较大,但不一定全是该提交者的工作,如何处理?

我们不会对初始提交进行特殊处理,如果需要排除初始提交,可以用手动设置提交号的方式排除。

Q: 为什么有些厉害的开发人员他的代码当量并不高?

代码当量不是万能的,它反映的只是落实到代码中的工作量。 而我们知道,对于开发者来说,写代码虽然是他工作中非常重要的一部分,但并非工作的全部。 高级开发者或者架构师等,他们很大一部分的工作内容可能是培训指导、方案讨论、设计架构、任务和进度管理等等,这些工作不会直接产出项目中的代码,因此不属于代码当量衡量的范畴。 思码逸并没有试图完全取代经理的评价,也没有声称要衡量或捕获构成完整工程师的定性指标,但是代码当量确实能为经理和团队提供了更为客观的方法,帮助团队更好地管理研发效能。

Q: 对于自动生成的代码文件,会计算代码当量吗?

不会。 由开发工具自动生成的代码文件不属于开发者实际编写的代码, 不应该计算代码当量。我们会自动识别出由开发工具自动生成的代码文件,并在计算代码当量时将其忽略掉。 我们识别自动生成的代码时,使用的是启发式算法,因此可能会有一些情况没有被覆盖。欢迎将没有被覆盖的案例反馈给我们,帮助我们改进识别算法。

Q: 对于项目中属于第三方库的源代码,会计算代码当量吗?

不会。 在代码当量的计算过程中,我们会识别出第三方库的代码,将它排除掉。 我们识别第三方库的代码时,使用的是启发式算法,因此可能会有一些情况没有被覆盖。欢迎将没有被覆盖的案例反馈给我们,帮助我们改进识别算法。

Q: 代码当量识别第三方库代码的方法是什么?

我们有三种方法识别第三方库:

  1. 我们会过滤一些通常用来放第三方库文件的目录,如vendor。
  2. 对于JavaScript项目,我们收集了一些常见的JS库名(来源于cdnjs、bootcdn托管的js库以及npm里top400k的库 ),实现了一个布隆过滤器。通过过滤器,根据文件名排除掉属于第三方库的JavaScript文件。注意,对这些第三方库名的识别,大小写不敏感。
  3. 我们正在构建一个索引开源项目的数据库,里面包含了开源项目里所有文件内容的索引。之后会通过与数据库中的文件内容进行对比,排除属于开源项目、第三方库的文件。

Q: 如何计算不同语言的代码当量?

与抽象语法树相对的,不同语言的不同语法分析器(Parser)有自己定义的具体语法树(Concrete Syntax Tree),语法节点的定义(Schema)各不相同。 为了处理这个问题,我们定义了一套统一的抽象语法树(Unified Abstract Syntax Tree)。计算代码当量时会将各语言分析出的具体语法树转写成我们的UAST,消除了不同语言之间一些细微的语法差异,让代码当量算法能在统一的语法树结构上进行计算。根据我们目前的分析统计,不同语言的分析结果不会有显著的统计差异。

Q: Merge Commit的代码当量是如何处理的?

Merge Commit 目前不计算代码当量。 在代码没有冲突时,Merge操作由Git自动完成,不需要开发者介入。 而当遇到冲突时,虽然需要开发者花费一定精力去解决冲突,但是Git并没有把解决冲突的修改单独记录下来,只保留了冲突的处理结果。这使我们很难分辨哪些部分是开发者的修改,哪些部分是Git自动处理的内容。 因此,我们目前对所有的Merge Commit都做统一处理,即不计算代码当量。

Q: 反复修改代码会增加代码当量吗?

每个开发者习惯不同, 有的开发者一次到位,有的开发者会反复修改一处代码多次才能到位,在假设最终成果相同的情况下,后者的代码当量的累计值确实会更高。不过我们正在开发“代码搅动Code Churn” 这个指标,会衡量出不同开发者反复修改代码的量。另一方面,如果反复的修改没有改变抽象语法树的复杂度,那么代码当量的绝对值不会变,可以把这两个指标放到一起对比分析。

Q: 简洁高效代码的代码当量小于复杂代码的代码当量,对于开发人员来说是不是不公平?

有经验的开发人员以更简洁的方式实现相同功能所产出的代码当量,可能少于经验较少用复杂代码实现的开发人员。代码当量用来评估开发人员的产出、效率,它本身不会反映代码质量。这也是为什么我们将代码当量与函数的调用关系,代码质量指标以及完成的故事数量综合起来做评估的原因,以便更好地了解开发过程和价值。代码当量只反映产出,需要结合生产率、稳定性、开发价值、质量指标(代码不重复度、圈复杂度、问题数、千当量缺陷率、测试/注释覆盖度)和需求完成情况来综合评价。

Q: 一次提交大量的代码是否会计入代码当量?

对于这种一次性增加或删除大量代码的异常提交,我们都会从分析中排除。这种提交通常是从其他地方复制粘贴过来的代码。

Q: 如何提高代码当量?

开发人员可以通过小步提交,持续的代码产出来提高代码当量,这样也更为合理。请参考思码逸指标用户使用规范

Q: 编写新功能和代码重构工作的价值如何横向对比,是否能用代码当量比较?

重构涉及的代码增减都会体现在抽象语法树的diff和最小编辑距离中,所以都会计入代码当量。两者可以用代码当量进行对比。同时也可以结合复杂函数数量的减小或者未来新的模块性的提升来说明重构的价值。

Q: 代码当量:编辑类型加权、节点类型加权、节点重复加权(重复代码),这3者权重各组占比是怎样的?是否有节点类型、节点重复加权对应的算法说明?

这三组权重没有占比关系,是通过相乘结合起来的。

Q:什么是节点类型的加权?它的权重是如何计算的?

抽象语法树的节点类型对应代码语法结构的类型,例如If节点表示一个If语句,Call节点表示一次函数调用,String节点表示一个字符串字面量。 我们认为生产或者修改不同类型的节点所需的工作量也是不同的,例如在代码中添加一个字符串比添加一个If条件语句更“轻松”。基于这个假设,我们为不同的节点类型预设了不同权重。

Q: 重复惩罚的调整系数是如何计算的?

我们的重复惩罚分为两个层级

  • 一层是对一个函数内重复代码段的惩罚
  • 一层是对两两相似的函数之间的惩罚 这两个层级的调整系数都是基于相似度的,系数为 1-相似度。但两个层级的相似度计算方式有所不同。

对于函数内,重复代码的相似度检测是基于AST的。两段代码的相似度计算如下:

  • 假设代码段a对应的AST_a中节点总数为N_a,代码段b对应的AST_b的节点总数为N_b
  • AST_a和AST_b中相同的节点的数目为N_shared
  • 则代码段之间的相似度为: N_shared * 2 / (N_a + N_b)

对于函数间的相似,基于AST的计算量会比较大,因此我们计算的是函数代码的文本相似度。使用的MinHash(最小哈希)和LSH(局部敏感哈希)。这部分算法可以参考网上的资料。

Q: 假设有一个开发人员写了1000行代码,但是他恶意注水,通过复制粘贴把这1000行变成了10000行,他的代码当量会怎样变化?

重复惩罚分函数间重复,还是函数内重复。考虑相似度和重复次数,相似度越高、次数越多,惩罚越重,最高惩罚至0。 如果是函数间重复,当量直接归0。也就是说如果1000行是函数,然后把这个函数原封不动地复制粘贴了10次,那么后面10个当量为0。 如果是函数内部,那注水后的10000行只会比纯粹的1000行多很少很少的当量。

Q: 开发人员故意game代码当量指标,怎么办?

思码逸已经建立了很完善的惩罚机制,game的成本很高,比如增加空行、频繁换行、复制代码、引用第三方库、插入脚手架自动生成的代码等,都会有想应的惩罚机制。对于反复增删代码,我们也有即将上线的code churn指标。对于代码当量异常高的人员,我们也要结合code review分析合理性。所以与其花时间Game不如认真写代码。

代码不重复度

Q: 统计代码不重复度时,函数相似度的最小边际距离是多少?

最小边际距离是0.5,可以理解为相似度50%。

Q: 不重复度 是根据产品里 代码重复页面的数据计算的吗?

不重复度的计算方法应该是“不重复度=(1-重复函数个数/总函数个数)%”,是【函数间】的重复度。 不重复度值越大代表代码重复度越低,反之重复度越高。重复代码行数不参与重复度的计算。 简单说就是 重复度 和 代码重复页面展示的数据没有关系,代码重复页面主要用于展示【函数间】哪些地方有重复,便于客户做code review和抽象。 虽然页面只展示了【函数间】重复,计算当量做重复惩罚的时候,既有 函数间,也有函数内,还有文件和commit级别的。

测试覆盖度

Q: 什么是动态测试覆盖度?什么是静态测试覆盖度?Merico的测试覆盖属于哪一种?

动态和静态的主要区别在于会不会实际运行测试。Merico的测试覆盖度是静态测试覆盖度。

动态的测试覆盖度计算,是基于实际运行了各个测试之后计算出的,因此能准确地知道哪一行代码在测试时执行了,哪一行没有执行。他的值通常为 “测试中被执行(覆盖)的代码行数 / 代码总行数”。例如比较常见的 Codecov 就是这样的算法。

而静态测试覆盖,则没有运行测试,只根据静态分析,找出测试函数中调用到的函数,认为这些函数就是被测试覆盖到的。因此他的值为 “测试函数调用到的函数数目/项目中非测试函数的数目”。

同样可以看到,常见的动态测试覆盖是基于行的测试覆盖,算的是行数的百分比。 而Merico的静态测试覆盖是基于函数的测试覆盖,算的是函数的百分比。

Q: 统计测试覆盖度时,如何识别测试函数?

对于各个编程语言,我们收集了一些主流测试框架的路径要求或者命名规范,根据文件路径判断某文件是否是测试文件。如果是测试文件的话,那么里面定义的函数就归到测试函数的类别。 主流测试框架:Python-Pytest, JS-Jtest, Java-Gradle, Go-go help test, RUBY-rspec/minitest等。 一些不常用的测试框架可能没有覆盖到的,可以提供给我们去增加覆盖。后续还会考虑判断函数里是否有断言。

Q: 统计测试覆盖度时,测试框架JUnit我们支持吗?

支持。

Q: 为什么有的文件里的函数实际没有被测试,但Merico的指标却显示它的函数已被测试覆盖?

出现这个情况,是因为我们目前的静态分析调用关系的算法还有待改进。 目前的静态调用关系是以被调用的函数名来建立联系的,因此对同名函数的处理有瑕疵,可能会导致计算出的函数测试覆盖度高于实际的函数测试覆盖度。 改进静态分析调用关系已列入我们将来的工作计划。

注释覆盖度

Q: 注释覆盖率是sonar的基本功能的数据加工出来的吗,是别的竞品无法给的数据吗?

我们的注释覆盖度是自研的指标,不是通过sonar加工的。 Sonar的注释覆盖度是 Density of comment lines = Comment lines / (Lines of code + Comment lines) 100,以“行”为考察对象,可以理解为“行注释覆盖度”。 我们的注释覆盖度是 有注释的函数个数/所有函数个数 100,以函数为考察对象,可以理解为“函数注释覆盖度”。 注释覆盖度虽然不是我们的核心竞争力,但它提供了不同于其他竞品的新视角。

本页导航