4.4 静态分析与代码异味指标
概述与动机
静态分析工具在不执行源代码的情况下扫描它,标记出已知与缺陷、安全漏洞或可维护性问题相关联的模式:不可达代码、未关闭的资源、可疑的类型强制转换、重复的逻辑,以及更广泛的一类代码异味,一些不一定是错误、但往往让代码更难理解、更难测试或更难安全变更的结构性模式。静态分析是这一部分其他主题中更有针对性的指标之下的自动化、持续运行的那一层,在每一次提交时运行,在问题被引入的那一刻就把它揭示出来,而不是等待一次周期性的审计。
本主题的核心关切,是静态分析工具报告的内容与真正重要的内容之间的差距。一个工具可以在一个大型代码库上标记出成千上万条发现,而发现的数量本身是一个糟糕的指标,因为它把琐碎的风格偏好与真正严重的风险混为一谈,而且它可以像通过真正的修复一样,容易地通过压制来降低。静态分析的价值,不在于原始的发现数量,而在于一个组织把严重程度分诊得有多好,防止倒退得有多好,以及能否抵制把工具的判断当作人工评审的替代品、而不是补充的诱惑。
对大团队而言,静态分析是在一个大到任何团队都无法全部人工评审的代码库中,强制执行代码质量和安全卫生基准的唯一实用方式。常常面临安全编码实践方面合规要求的企业和政府组织,依赖静态分析作为有文档记录、可审计的证据,证明一个基准水平的审查被一贯地应用了,而不只是在一位人工评审者碰巧注意到问题的时候才被应用。
核心原则
- 原始发现数量本身是一个糟糕的指标。 它把琐碎问题和严重问题混为一谈,而且可以通过压制而不是真正的修复来操纵。
- 严重程度分诊比数量更重要。 少量关键发现比大量琐碎发现更值得关注。
- 静态分析补充人工评审,而不是取代它。 工具捕捉的是模式;它们不理解意图或业务背景。
- “新引入问题”的趋势,比总积压数量更具可操作性。 它告诉你当前的实践是在改善还是在退步。
- 误报会侵蚀人们对这个工具的信任。 一个未被管理的误报率,会导致团队整体忽视这些发现,包括其中真实的那些。
建议
跟踪按严重程度加权的发现,而不是原始数量
配置你的静态分析工具,按严重程度(关键、高、中、低,或一个等效的量表)对发现进行分类,跟踪一个按严重程度加权的趋势,而不是一个扁平的总数。一个拥有零个关键发现和五百个低严重程度风格建议的代码库,与一个拥有五十个关键发现、却没有任何风格问题的代码库,处于非常不同的状态,而原始数量会把这两者大致等同看待,实际上它们并不等同。
在新引入的发现上设关卡,而不是在整个历史积压上
大多数成熟的代码库都带有一份早于当前实践的遗留发现积压,一次性全部修复的代价会高到令人望而却步。与其在整个积压被清空之前阻挡所有工作,不如让CI关卡设在一项具体变更是否引入了超过约定严重程度阈值的新发现上,让积压通过常规维护逐渐缩小,同时防止进一步累积。这种区分呼应了主题 4.2 关于覆盖率下限的建议:防止倒退,而不是要求一次不切实际的、一次性的全面修复。
主动管理误报率
定期审查一批发现样本,尤其是任何数量庞大的类别,检查其中有多少是真正的误报,也就是工具标记出的模式实际上在该情境下并不构成问题的情况。专门调整规则配置,压制那些真正嘈杂、低价值的规则类别,而不是任由团队养成因为输出中太多噪声而整体忽视工具输出的习惯。一个高企、未被管理的误报率,是摧毁一个静态分析项目可信度最快的方式。
把静态分析的发现当作评审的提示,而不是自动裁决
即使一个合法的、非误报的发现,也不总是要求一次自动的、强制的修复;一些被标记的模式,考虑到工具无法看到的具体背景,是可以接受的。建立一个轻量级的流程,让人来评审,然后要么修复,要么带着一个记录在案的理由明确地、可见地豁免一项发现,而不是盲目地把每一项发现都当作强制性要求来执行,也不要任由无声的、未被记录的压制随时间推移侵蚀这个工具的价值。
把静态分析与这一部分的其他代码质量指标结合起来
静态分析的发现、复杂度分数(主题 4.1)和热点数据(主题 4.3)是互补的证据,而不是相互竞争的指标。一个未解决静态分析发现高度集中、同时又是一个变动量-复杂度热点的文件,是一个尤其值得优先关注的候选对象,因为多个独立的信号正在共同指向同一个结论。
权衡取舍:利与弊
| 方案 | 优点 | 缺点 |
|---|---|---|
| 用原始发现数量作为指标 | 报告起来简单 | 把琐碎和严重问题混为一谈;容易通过压制来操纵 |
| 按严重程度加权的趋势 | 更准确地反映真实风险 | 需要持续维护严重程度分类 |
| 在整个历史积压上设关卡 | 最大化最终的代码整洁度 | 对成熟代码库往往不切实际;可能让所有工作停摆 |
| 只在新发现上设关卡 | 实用,防止倒退,让积压逐渐缩小 | 遗留问题在没有刻意补救计划的情况下持续存在更久 |
核心张力是彻底性与实用性之间的张力。一项要求在任何新工作推进之前先解决全部历史积压的静态分析政策是彻底的,但对任何拥有真实历史的代码库来说通常不切实际,处于这种压力之下的团队,往往会整体压制发现,而不是真正修复它们。解决这种张力的办法,是严格地在新发现上设关卡,同时针对遗留积压运行一个单独的、刻意有节奏的补救工作,用本主题和主题 4.3 所建议的严重程度和交叉引证技术来确定优先级。
与团队讨论的问题
我们跟踪的是一个按严重程度加权的趋势,还是只是一个原始的发现总数? 拉出你实际的仪表盘并检查;在许多工具中,原始数量默认很常见,往往需要刻意的配置才能改为恰当地呈现严重程度。
我们当前未解决的遗留发现积压有多大,我们是否有一个刻意、有节奏的计划来减少它,还是它只是在无限期地累积? 一份未被处理、悄悄增长的积压很常见,值得诚实地点明,而不是放任它不被审视。
我们数量最多的发现类别,估计误报率是多少,我们是否据此调整过规则配置? 如果你从未检查过这一点,从你最嘈杂的类别中抽取一批发现样本,诚实地评估其中有多少是真正可操作的。
我们团队的工程师是信任静态分析的发现,还是已经学会了因为输出中太多噪声而对其充耳不闻? 这是一个直接、诚实的、值得问团队的直觉检验问题,因为一个被忽视的工具,无论其理论能力如何,都不提供任何真实价值。
我们目前如何处理一个团队认为鉴于具体背景应当被豁免的合法发现? 检查你的流程是否把这变成一个可见的、有文档记录的决定,还是它是通过无声的、未被记录的压制发生的,而这种压制会随时间推移侵蚀这个工具的信号。
静态分析的发现、复杂度分数和热点数据,在哪个文件或模块上出现了汇聚? 明确地交叉引证这三个信号;多个独立指标上的汇聚,是一个比任何单一指标本身都更强的优先排序信号。
行业视角
初创公司。 一个从一开始就集成到CI中的轻量级、免费静态分析工具,是廉价的保险,能在遗留积压有任何机会累积之前,尽早捕捉到真正的问题。让规则集专注于真正高价值、低噪声的类别,而不是立即启用所有可用的规则。
小型企业。 大多数现代语言生态系统都包含有能力的免费静态分析工具;在CI中以一套合理的默认规则集启用它,所需的投入很少。专注于在新发现上设关卡,而不是试图一次性解决任何既存的积压。
企业。 在这个规模上,刻意管理误报率和严重程度分诊变得至关重要,因为一个调校不佳、在数十个团队中产生过多噪声的工具会在全组织范围内被忽视。为静态分析工具本身的配置投资一位专门的负责人,把规则调校当作一项持续的纪律,而不是一次性的设置任务。
政府。 静态分析的发现,尤其是与安全相关的发现,常常与合规和审计要求直接相关。维护一个有文档记录、可审计的流程,说明发现是如何被分诊、修复,或带着记录在案的理由被正式豁免的,因为这份文档本身,往往正是外部审计员想要看到的东西。
案例
企业。 一家软件公司的静态分析仪表盘,在数年没有进行按严重程度分诊的情况下,在其代码库中积累了超过四万条未解决的发现,这个数字大到工程师们基本上已经完全不再查看这个仪表盘了。一种修订后的方法按严重程度对发现进行分类,发现其中只有不到两百条是真正关键的,并专门在新引入的关键和高严重程度发现上设置了CI关卡,而让低严重程度的积压通过常规代码维护逐渐缩小。六个月之内,关键发现降到了个位数,更重要的是,工程师调查数据显示,人们对这个工具输出的信任重新建立了起来,因为它现在呈现的是一个可管理、真正可操作的信号,而不是一堆令人望而生畏、被忽视的积压。
政府。 一家国防机构的软件供应链安全政策曾要求在任何发布之前,静态分析扫描必须做到零未解决发现,这项政策实际上导致开发团队为了在一道不切实际的全有或全无关卡下赶上发布期限,压制了大量发现,其中包括一些真实的安全问题。一项修订后的政策改为要求任何一次发布都不得引入新的关键或高严重程度发现,并配合一份有文档记录、被跟踪的遗留积压补救计划和时间表,由一个安全治理委员会按季度审查。这种实用的、分阶段的方法,既恢复了对新代码真正的安全审查,也在十八个月内针对遗留积压取得了真实的、可衡量的进展,不同于此前那项不切实际的政策,它更多地只是产生了压制,而不是真正的修复。
商业理由:动机、投资回报与总拥有成本
管理良好的静态分析的回报,是在缺陷和安全漏洞抵达生产环境之前捕捉到它们,所需成本远低于用同等覆盖面所需的等效人工评审努力。上面的国防机构案例展示了把这件事做错的代价:一项不切实际的全有或全无政策,实际上通过驱使压制而降低了真正的安全审查,恰恰与它的初衷相反。
总拥有成本包括工具本身,对常见语言生态系统来说往往是免费或低成本的,以及严重程度分诊、误报管理和遗留积压补救规划的持续纪律。比起工具本身,正是这种持续的纪律,决定了一个静态分析项目能否提供真正、可信赖的价值,还是退化成被忽视的噪声。
反模式与陷阱
- 把原始发现数量当作指标: 把琐碎和严重的问题混为一谈,而且容易通过压制来操纵。
- 要求在任何新工作推进之前解决全部历史积压: 通常不切实际,会驱使压制而不是真正的修复。
- 忽视误报率: 一个未被管理的噪声水平,会导致团队完全对工具的输出充耳不闻,包括其中真实的发现。
- 对合法发现进行无声的、未被记录的压制: 侵蚀这个工具的信号,也没有为合规目的留下任何审计痕迹。
- 把一项静态分析发现当作一个没有人工评审的自动裁决: 错过了工具无法看到的背景。
- 从不把发现与复杂度和热点数据交叉引证: 错过了汇聚证据所提供的更强的优先排序信号。
成熟度模型
- 第一级,启动: 静态分析不被运行,或者发现在没有严重程度分诊或趋势跟踪的情况下不受管理地累积。
- 第二级,发展: 一些静态分析在CI中运行,但严重程度分诊不一致,误报率也不受管理。
- 第三级,标准化: 发现被按严重程度加权,CI在全组织范围内针对新的关键和高严重程度发现设置关卡。
- 第四级,管理: 误报率被主动调校,遗留积压拥有一份有文档记录、有节奏的补救计划,豁免是可见且有文档记录的。
- 第五级,协奏: 静态分析的发现、复杂度数据和热点数据被常规地交叉引证以确定投资优先级,组织能够指出可以追溯到这个项目的具体、可衡量的缺陷或安全改善。
讨论思路
- 我们当前按严重程度加权的趋势是什么,它是在改善还是在恶化?
- 我们遗留的发现积压有多大,我们是否有一个刻意的计划来减少它?
- 我们最嘈杂的发现类别,估计误报率是多少?
- 我们团队的工程师目前是信任还是忽视我们的静态分析输出?
- 在我们的代码库中,静态分析的发现在哪里与复杂度或热点数据汇聚?
要点回顾
- 跟踪一个按严重程度加权的趋势,而不是把琐碎和严重问题混为一谈的原始发现数量。
- 让CI在新引入的发现上设关卡,而不是在整个历史积压上,以防止倒退,同时不要求一次不切实际的一次性修复。
- 主动管理误报率;未被管理的噪声会摧毁人们对这个工具的信任,导致发现被整体忽视。
- 把发现当作人工评审的提示,配以可见、有文档记录的豁免,而不是一个自动裁决或无声的压制。
- 把静态分析与复杂度和热点数据(主题 4.1、主题 4.3)交叉引证,以获得汇聚的、更强的优先排序证据。
参考文献与延伸阅读
- Static Program Analysis,Anders Møller、Michael I. Schwartzbach著(静态分析技术的理论与实践基础)。
- OWASP关于静态应用安全测试(SAST)的指导,属于OWASP基金会关于安全软件开发实践的更广泛资源的一部分。
- Refactoring: Improving the Design of Existing Code,Martin Fowler著(许多静态分析工具所依据的代码异味目录)。
- Working Effectively with Legacy Code,Michael Feathers著(在一个成熟的代码库中管理遗留质量问题积压)。