我们永远看不到我们不理解的选择。–甲骨文(黑客帝国)副标题:“Acode-maat,git,Python,D3.jsAcasestudyforcodequalitymeasurement”。大概是2017年6月,在工作中负责一些小项目的时候,接到了同事的汇报。主要内容是通过jsinspect检测到几个项目代码重复率比较高。Reminder我是否有一些项目需要优化或部分重构。那时我是第一次作为周期性项目诞生,代码的质量应该量化为一个有迭代的指标来指导后续的项目迭代,虽然当时听说过sonar。后来参与了集中式代码质量评估系统一期的设计和开发(主要是javascript代码规范和质量检测与持续集成的耦合),但当时系统并没有解决被调用的问题能够判断我的代码的哪个特定部分需要以更高的优先级进行重构。半年过去了,现在我试着回答这个问题,并举一个与编程语言无关的常见且可行的例子,并通过这个例子揭示一个事实:“代码的历史是对未来的预测代码的未来。”如果把项目代码比作一座城市,那么系统中复杂、维护成本高的部分就如同潜伏在城市中的罪犯。我们在搜捕罪犯的时候,显然不能全城搜查。相反,我们应该找到一些有前科的区块并将它们划分为关键点。检查区域。我们将这些区域称为热点,在代码中也是如此。在这些热点中,隐藏着优先级最高的对象,需要进行调整、优化和重构。CodeCity生成的“代码城”中,每块都是一个包,每一个类都是一栋楼,楼的高度就是类中方法的个数(这个工具是OOP语言专用的,比如java,我们后面不讲了)我们的目的是确认一个项目中哪里存在热点。首先,我们不以代码复杂度作为确认热点的唯一维度。代码复杂度是很有用的,但是如果仅仅以复杂度作为热点衡量的指标,就会出现一些问题。主要问题是一段复杂的代码。只有当我们真正需要关注它并改变它时,它才是问题所在。如果没有人需要阅读或修改这段复杂的代码,那么复杂或不复杂又有什么关系呢?即便有人觉得这种代码是定时炸弹,但是具有一定规模的系统,有复杂的模块也是很正常的。将这些复杂的模块一次性标记为热点是不合理的。我们也存在热点调整优化的风险。重要的是要有一个策略来优化真正需要优化的东西。选择维度我们使用代码更改的频率作为花费开发人员和时间的第一个维度。以React为例,看看它在15.0.0(2016年4月9日发布)和16.0.0(2017年9月27日发布)之间做了哪些改变。为了保证时间的一致性,我们先把项目时间缩短到2017年9月27日:gitcheckout`gitrev-list-n1--before="2017-09-27"master`接下来我们看一下201642017年9月9日至9月27日的结构化git日志:gitlog--pretty=format:'[%h]%an%ad%s'--date=short--numstat--before=2017-09-27--after=2016-04-09>react_evo.loggit提交日志在我们自己重定向的git的日志文件react_evo.log中,我们使用code-maat获取代码变化频率的数据,code-maat是用Clojure写的,它用于挖掘和分析版本控制软件中的项目代码变更数据。我们把它clone下来,通过leiningen编译它的jar包执行,也可以放到系统路径下做一个系统命令,我也做了一个code-maat的Docker镜像,地址是code-maat。下面我们来尝试使用code-maat,先看看React在两个版本之间的一些总结数据java-jarcode-maat/target/code-maat-1.1-SNAPSHOT-standalone.jar-lreact_evo.log-cgit-asummary,你可以看到两个版本之间的提交次数,涉及的文件信息和开发者的数量。让我们看看代码更改的频率并生成一个csv文件:java-jar../../code-maat/target/code-maat-1.1-SNAPSHOT-standalone.jar-lreact_evo.log-cgit-arevisions>react_freqs.csv文件的修改次数排序。有了这些数据,候选热点的范围就缩小了。接下来我们再增加一个判断模块大小的维度,即代码行数。代码行数这个维度简单粗暴,有两个好处,一是可以方便快捷的查找;另一个是它是不同编程语言的中性指标。我们使用cloc作为按行数分析项目的工具,它是用Perl编写的(谁说Perl已死?),并获得非常直观的编程语言、文件、空格、注释和代码本身的输出,我们在一个React项目:cloc./--by-file--csv--quiet--report-file=react_lines.csv在反映代码行数的react_lines.csv中,我们可以看到一个新的维度:Th??emergingof代码行数的排序维度和单一维度的可视化都不足以让我们判断某个热点可能存在。接下来,我们将合并维度。我们将代码修改频率和代码行数结合起来,形成代码修改次数+代码行数的新的综合维度,其中代码修改次数比代码行数具有更大的权重。我们通过用Python编写脚本来完成此操作,Python脚本的代码位于hotspots-helper中。我们看一下新维度生成的数据:pythonmerge_comp_freqs.py../complexity/data/react_freqs.csv../complexity/data/react_lines.csv>react_hotspot_candidates.csv代码修改次数+代码行排序这个维度非常清楚的表达了React从15.0.0到16.0.0的主要代码变化,来自Reactfiber,带来了很多代码和多处修正,前三名中有两个是渲染过程中使用的fiber部分安排任务。排名靠前的代码行数将近万行。这么多代码的源码,一个一个看完,还是很耗体力的。但是,通过这些数据,我们可以使用D3.js做一些可视化,通过圈包算法得到一个圈地图。图中每个圆的直径越大,模块/文件的代码行数越多,颜色越深,模块/文件被修改的次数越多,变化越频繁:Reactcodefrom15.0.0to16.0.0可视化热点候选文件的具体可视化页面,请参考ComplexityStudy。我还在youngleehua/complexity-study做了一个Docker镜像。文件命名和重新引入的复杂性分析既然我们试图在这些潜在的热点中找到真正的问题,我们就增加一个维度,文件命名。文件、类、函数的命名可以很好地体现开发者的设计意图。通常,我们在阅读代码时,可以通过文件、类、函数的命名来了解前人的设计和思路。命名不仅可以区分哪些是配置文件,哪些是代码本身,还可以区分文件的职责。好的文件命名可以明确模块的代码职责,使代码具有内聚性,尽可能减少后续的其他职责。添加代码可以防止逻辑变得过于复杂,从而增加修订和错误的数量。例如,ReactDOMComponent.js是一个比ReactFiberBeginWork.js更好的名字。如果不看ReactFiberCommitWork.js和ReactFiberCompleteWork.js就得出Fiber本身有调度周期的结论,ReactFiberBeginWork.js就没那么友好和清晰(谁知道一个模块需要一个初始代码),以及通过文件命名,我们也可以很明显地排除一些文件,比如package.项目的Makefile)。接下来,我们对命名不友好,综合维度排名靠前的文件进行代码复杂度分析,根据复杂度分析,判断历史提交中代码复杂度的变化,为后续代码修改提供指导和建议。目前复杂度分析的手段很多,比如圈复杂度,也有针对语言的复杂度分析工具,比如针对Javascript的es-analysis/plato,但是为了实现语言中立,形成了一个通用的解决方案,我使用代码的缩进来标记代码文件的复杂性。对于大多数语言(尤其是类C语言)来说,代码缩进代表更深层次的逻辑。逻辑层次越深,需要控制的逻辑就越复杂,可能出现的问题也就越多。您要维护以下两个文件中的哪个?我比较喜欢维护左边的代码:)我们还是用Python来分析代码的缩进。我们把一个制表符和2个空格(为了符合React代码的风格)作为逻辑缩进,忽略空行,每个缩进作为一个复杂度分数(1分),看整个的总复杂度文件,平均复杂度,复杂度的方差和最大复杂度。pythoncomplexity_analysis.py./react/src/renderers/shared/fiber/ReactFiberBeginWork.js不包括空行,共766行,复杂度总分1981,平均分2.59,方差1.29,最高分是8分。n代表一共不计空行,总行数为766;total代表文件复杂度的总分1981分,已经很高了(如果和其他文件横向比较更明显);mean代表平均得分表现为2.59分;sd表示方差得分较高Low表示行数较多,复杂度得分接近平均得分。1.29的分数比较好,但是最大行复杂度达到了8分,是一个比较大的数值。这个后续文件的职责是否可以进一步简化,剥离更多的逻辑?这需要React开发人员来衡量。综合维度排名第一的ReactFiberScheduler.js的评分,我们还可以通过版本控制得到一个代码复杂度变化的信息(从最初的react_evo.log中获取第一个和最后一个版本):python../hotspots-helper/git_complexity_trend.py--start95fed0163--end9ce135f86--file./src/renderers/shared/fiber/ReactFiberBeginWork.js>complexity_trend.csv通过excel看一下复杂度的趋势:ReactFiberBeginWork.js复杂度从15.0.0版本到16.0。版本0之间的变化趋势有了这些数据和视觉体验,我们不仅可以在不熟悉一个项目的情况下了解代码的结构,找到可能的热点,还可以通过版本控制系统得到具体文件质量的变化趋势,所以以指导codereview,也为后续代码重构指明了方向。结论对于软件质量,仅分析代码中的热点是不够的。除了代码层面的隐藏缺陷外,我们还没有分析宏观架构的演化和开发者与代码之间的社会学关系。这篇文章之后,我会做一个预览。在下一篇关于代码和项目质量分析的文章中,我会尝试从更宏观的角度来阐述如何量化代码架构,通过分析来指导一个项目在架构上的迭代和重构。祝大家在2018年编码愉快。
