4.3

4.3 代码变动量与热点分析

概述与动机

代码变动量衡量一个文件或模块随时间变化的频率,在连续的提交中新增、修改和删除的行数。单独来看,变动量是一个相当弱的信号:一些文件变化频繁,是因为它们正处于活跃、健康的开发之中,而一些文件很少变化,是因为它们稳定而正确,而不是因为它们被忽视了。本主题方法真正的诊断力量,来自把变动量与复杂度(主题 4.1)结合起来:一个既频繁变更又高度复杂的文件,一个热点,格外可能是缺陷的来源,也格外可能拖累团队速度,实证研究在许多代码库和组织中都一贯地证实了这一点。

热点分析,因Adam Tornhill在软件分析学方面的工作而得到普及,之所以尤其有价值,是因为它找出目标不需要任何人工调查或主观判断。版本控制历史中已经包含了计算变动量、以及结合静态分析工具计算复杂度所需的全部信息,可以自动为代码库中的每个文件完成。这让一个团队或组织能够用真实的证据,而不是轶事或回顾会议上最响亮的抱怨,来准确识别出代码库中哪一小部分最值得优先获得重构关注。

对大团队而言,热点分析解决了一个真实的资源配置问题:一个拥有数十万行代码的代码库,其代码量远超任何团队能够负担得起全面重构的规模,而关于最严重问题藏在哪里的直觉,常常是错误的,受到最近抱怨过的人,或某位资深工程师恰好不喜欢的某个文件的左右。管理着庞大、长寿命代码库的企业和政府组织,依赖这种数据驱动的优先排序,把真正稀缺的重构预算引导到最能产生回报的代码上。

核心原则

  • 单独的变动量是一个弱信号;变动量结合复杂度是一个强信号。 是两者的结合,而不是任何一项指标单独,识别出了一个真正的热点。
  • 热点分析不需要任何人工调查。 版本控制历史已经包含了自动计算它所需的一切。
  • 一个热点是一个优先排序信号,而不是一个自动裁决。 决定一个具体热点需要采取什么行动,仍然需要人的判断。
  • 频繁变更本身并不必然是坏事。 一些变动量反映的是健康、活跃的开发,而不是质量问题。
  • 这种分析恰恰在直觉失效的地方发挥作用:在一个大到任何个人都无法单凭感觉调查和排序的代码库中。

建议

一并计算变动量和复杂度,按两者的结合来排序

从版本控制历史中提取一段有意义时期(通常是六个月到一年)内每个文件的变更频率,并把它与相同文件的复杂度度量(主题 4.1)配对。按两者的结合,通常是变动量与复杂度的乘积,而不是按任何一项指标单独,对文件进行排序,因为底层研究一贯地把这种结合与更高的缺陷率和维护成本关联在一起。

在采取行动之前,用人的判断调查排名靠前的热点

一份排序好的热点清单,识别出的是值得关注的候选对象,而不是一份自动的行动清单。对你排名靠前的每一个热点,用人的眼光去调查:这是否真的是需要重构的设计糟糕的代码,还是一个因为处于活跃、不断演进的业务逻辑中心而合理地需要频繁变更的文件,在后一种情况下,优先事项可能是更好的测试或更清晰的文档,而不是结构性重写。这呼应了主题 4.1 本质复杂度与偶然复杂度的区分,只是把它应用在了这个变动量-复杂度的组合信号上。

把热点与事件和缺陷数据交叉引证

在可能的情况下,检查你识别出的热点是否与实际的生产事件(主题 6.2)或缺陷逃逸数据(主题 5.1)相关联。一个强相关性验证了热点分析对你的具体代码库确实具有预测性,并强化了据此采取行动的商业理由;一个微弱或缺失的相关性,则暗示要么存在数据质量问题,要么在你的具体情境中,变动量和复杂度并不是用来确定优先级的正确信号组合。

跨连续多次分析跟踪热点趋势,而不只是单一快照

定期重新运行热点分析,常见的是按季度,并跟踪此前识别出的热点是在改善、恶化,还是已经解决,以及是否有新的热点正在出现。一个尽管被反复标记、却在多个分析周期中持续存在的热点,要么意味着补救工作实际上从未被落实,要么意味着此前的补救尝试没有解决真正的底层问题。

用热点数据来指导,而不是取代团队层面的优先排序对话

把热点分析当作优先排序讨论中的证据来呈现,而不是当作一个凌驾于团队自身对当下什么最重要的情境判断之上的自动指令。一个团队可能有充分、合理的理由暂时降低一个已知热点的优先级,比如一次即将到来的计划内重写,会让增量式重构变成浪费的精力,而这项分析应当为那场对话提供依据,而不是取代它。

权衡取舍:利与弊

方案优点缺点
基于直觉的优先排序快速,不需要工具,利用团队的情境知识受近因效应、个人偏好和抱怨最响亮的人所左右
单独的变动量计算简单单独来看是一个弱信号;频繁变更本身并不必然是坏事
变动量结合复杂度(热点分析)强有力、基于证据、可从现有数据自动获得需要组合两种数据来源,并用判断力来解读结果
与事件数据交叉引证的热点分析经过验证,是优先排序最有力的证据需要可靠的事件与代码之间的关联,并非每个组织都有

核心张力是证据与情境之间的张力。热点分析提供了客观的、可扩展的证据,这是基于直觉的优先排序在一个庞大、陌生或长寿命的代码库规模上无法匹敌的,但它缺乏团队对为什么一个特定热点现在重要或不重要所拥有的情境判断力。解决这种张力的办法,是把热点分析当作优先排序对话的证据基础,与团队自身对时机和权衡的情境判断相结合,而绝不取代它。

与团队讨论的问题

  1. 按变动量和复杂度结合排序,我们排名前五的热点是什么,这份排名会与我们团队对最严重问题藏在哪里的直觉相匹配吗? 运行这项分析,把结果与你的团队在看到数据之前会猜测的情况进行比较;差异往往是最有价值的发现。

  2. 我们识别出的热点,是否与实际的生产事件或缺陷逃逸数据相关联? 如果你有数据可以检查这一点,直接去做;如果没有,这个缺口本身就值得作为一个需要努力弥补的方向被点明。

  3. 对我们当前排名靠前的热点而言,底层问题是合理需要频繁变更的本质复杂性,还是一次重构真正能够修复的偶然复杂性? 一起过一遍这个文件,明确做出这个判断,而不要假定任何一个答案。

  4. 一个此前被识别出的热点,是否尽管被标记过,仍然在多个分析周期中持续存在? 如果是这样,诚实地调查原因:补救从未真正被尝试过,还是此前的尝试没有解决真正的底层原因。

  5. 我们目前是基于证据来确定重构工作的优先级,还是基于最近或最响亮的抱怨? 诚实地面对你团队当前实际的优先排序流程,以及它与一份基于证据的热点分析所建议的内容相比如何。

  6. 如果我们把当前排名靠前的热点再放任一年不处理,会在缺陷率或交付放缓上付出什么代价? 这个问题迫使我们做出一个具体的成本估算,可以为一项优先排序决策提供锚点,而不是让这个热点停留在一个抽象的、容易被降低优先级的关切之中。

行业视角

初创公司。 对一个整个团队仍然集体把代码库装在脑子里的小型、年轻代码库而言,正式的热点分析通常没有必要。这项技术恰恰在代码库成长超过任何个人都能单凭记忆可靠地识别出最糟糕区域的规模之后,才变得有价值,这通常出现在持续成长的头一两年内的某个时刻。

小型企业。 免费或低成本的工具可以以最低的设置成本,直接从你现有的版本控制历史中提取变动量数据;把它与你现有的代码检查器或静态分析工具已经报告的任何复杂度数据结合起来,而不是在这个规模上投资于专门的商业热点分析软件。

企业。 热点分析正是基于证据的优先排序发挥最大回报的地方,因为在一个横跨数百个服务、数千个文件的代码库规模上,直觉确实会失效。投资于在整个代码库上定期运行这项分析,并与事件数据交叉引证,为重构投资建立一个经过验证、站得住脚的论证。

政府。 有时长达数十年的长寿命系统,天然适合热点分析,因为积累下来的版本控制历史,提供了一个丰富的、长期的信号,说明系统的哪些部分随时间推移真正被证明是麻烦的。这种基于证据的方法,也是向那些需要比一位工程师的非正式意见更多依据才能批准资金的利益相关方,论证现代化投资正当性的一个有说服力、具体的工具。

案例

企业。 一家保险公司的理赔处理平台,横跨数十个服务,代码超过两百万行,多年来一直积累着”理赔验证模块”很麻烦这类非正式抱怨,但这些抱怨从未带来任何正式的优先排序。一次结合了六个月变动量数据与复杂度分数的热点分析,识别出一个完全不同的文件,一个深藏在很少被讨论的依赖中的共享货币转换工具,才是真正排名第一的热点,它从未在任何回顾性抱怨中被提起过。与事件数据的交叉引证证实,这个工具在过去一年中牵涉到了不成比例份额的财务计算缺陷,而针对这个具体工具(而不是人人非正式指责的那个模块)的一次有针对性的重构,在随后一个季度内产生了相关事件的可衡量下降。

政府。 一家州机动车管理机构一个已有数十年历史的许可系统,作为一项现代化商业论证的一部分,接受了一次热点分析。这项分析识别出一小簇文件,占总代码库不到3%,却对不成比例份额的变动量和复杂度负责,与该机构的事件日志交叉引证显示,同一簇文件在过去三年中占了所有报告系统缺陷的近40%。这个具体的、基于证据的发现,远比一句”系统老旧需要现代化”的笼统说法更有说服力,成为了一项成功预算请求的核心,这项请求专门针对那一簇文件推动一次有针对性的增量式现代化工作,而不是一次成本高昂得多的全面系统替换。

商业理由:动机、投资回报与总拥有成本

热点分析的回报,是有针对性的、基于证据的投资:上面两个案例都展示了一个正式分析把重构关注从非正式抱怨所聚焦的地方,重新引导到数据实际显示问题所在之处的情况,产生了比无针对性或由直觉驱动的投资可衡量地更好的回报。

总拥有成本很低,因为变动量数据直接来自现有的版本控制历史,复杂度数据通常已经可以从静态分析工具(主题 4.4)中获得;主要的投入是定期的分析工作,以及解读结果、决定每个被识别出的热点应当采取什么行动所需的人工判断时间。

反模式与陷阱

  • 只用变动量而不结合复杂度: 单独来看是一个弱信号,可能把健康、正在积极开发的代码错误地标记为假阳性。
  • 把热点排名当作一份没有人工判断参与的自动行动清单: 错过了决定正确应对方式的本质与偶然之分。
  • 基于最响亮的抱怨而不是证据来确定重构优先级: 常常把精力从数据实际显示问题所在的地方错误地引开。
  • 从不把热点与事件或缺陷数据交叉引证: 错过了能强化据此采取行动的理由的验证步骤。
  • 只运行一次分析,从不重复: 错过了判断补救工作是否随时间真正见效的机会。
  • 忽视一个持续被标记的热点,而不调查为什么补救没有见效: 浪费了重复分析的诊断价值。

成熟度模型

  • 第一级,启动: 重构优先级由直觉或抱怨量决定,没有变动量或复杂度数据为决策提供依据。
  • 第二级,发展: 一些团队非正式地检查变动量或复杂度数据,但没有一贯的、全组织范围的热点分析实践。
  • 第三级,标准化: 结合变动量和复杂度的热点分析定期运行,并在全组织范围内一贯地为重构优先排序提供依据。
  • 第四级,管理: 热点与事件和缺陷数据交叉引证以验证分析,连续多个周期的趋势被主动跟踪。
  • 第五级,协奏: 组织能够指出具体的、可衡量的缺陷率或交付改善,这些改善来自热点指导的重构投资,这项分析是工程投资决策中一项常规、可信赖的输入。

讨论思路

  1. 如果我们今天运行这项分析,我们排名前列的热点清单会是什么样子?
  2. 这份清单会与我们团队当前对最严重问题区域的非正式认知相符,还是相悖?
  3. 我们是否拥有把热点与实际事件交叉引证的数据?
  4. 一个已知的问题区域,是否尽管之前有过修复尝试仍然持续存在,为什么?
  5. 把我们当前排名第一的热点再放任一年不处理,会让我们付出什么代价?

要点回顾

  • 变动量结合复杂度,比任何一项指标单独,都能远更可靠地识别出真正的热点。
  • 热点分析不需要任何人工调查;它可以从现有的版本控制和静态分析数据中自动计算得出。
  • 把一份热点排名当作优先排序的证据,而不是一个自动裁决;仍然需要人的判断。
  • 把热点与事件和缺陷数据交叉引证,以验证分析,强化据此采取行动的理由。
  • 跨连续多个分析周期跟踪热点,以确认补救工作真正见效,而不只是把它当作单一的快照。

参考文献与延伸阅读

  • Your Code as a Crime Scene,Adam Tornhill著(结合版本控制数据中的变动量与复杂度进行热点分析的奠基性著作)。
  • Software Design X-Rays,Adam Tornhill著(利用版本控制历史进行行为性代码分析的进一步技术)。
  • Nagappan, Nachiappan, and Thomas Ball, “Use of Relative Code Churn Measures to Predict System Defect Density,” ICSE (2005):关于变动量与缺陷密度之间关系的实证研究。
  • Refactoring: Improving the Design of Existing Code,Martin Fowler著(一旦识别出偶然复杂性之后,处理它的技术)。