📄 本篇摘要
不是所有问题都要跑起来才能发现。检查(评审)靠人读,静态分析靠机器扫——不执行程序就找毛病;软件指标再把【代码质量】变成数字,圈复杂度是其中最出名的一个。
📝 关系
上一节:Bug、调试与 BTS | 下一节:软件生命周期与 DevOps
一、检查/评审:人是最强的静态分析器
代码评审(code review):写完不直接合入,先由同事读一遍——逻辑对不对、有没有更简单的写法、命名达意吗。研究表明评审能抓住大量测试抓不住的【设计层面】问题,还能传播知识(谁都能看懂谁那块)。
教材区分三个词:检查(inspection,正式流程、带检查单、逐条过)、走查(walkthrough,作者主讲、大家听)、评审(review,统称)。日常开发里最常用的是轻量级 pull request 评审。
二、静态分析:机器扫,不执行
静态分析=分析代码文本/语法树而不运行它。能抓什么:
- 用了没定义的变量、写错的名字(拼错就报);
- 永远走不到的死代码、忘写的 return;
- 类型不匹配、可疑的比较(
if x = 5——C 语言的经典自杀式赋值); - 风格违规(5-2 的 linter 就是静态分析的一种)。
Python 生态:ruff/flake8/mypy(类型检查)。优势是快、便宜、全覆盖——CI 里每提交一次就全库扫一遍(5-10 的流水线里它是常驻岗)。
三、软件指标:给质量一个刻度
常用指标:
- 代码行数(LOC):粗糙但直观——太大=该拆了;
- 圈复杂度(cyclomatic complexity):数一个函数里【独立判断路径】的数量(if、while、and、or 各加一条);10 以上就该警惕——它等于最少要写的测试用例数(5-6 的用例数从此有了理论下限);
- 重复率:复制粘贴的百分比——重复是 bug 繁殖场,改一处忘三处。
四、动手 Demo:亲手数一次圈复杂度
def ship_fee(weight, is_vip, distance):
"""运费:VIP 减半;满 50 元免运费再按距离加价"""
if weight > 10: # +1
fee = 20
else:
fee = 10
if is_vip: # +1
fee = fee * 0.5
if fee < 50 and distance > 100: # +2 (if 一个 + and 一个)
fee += 5
return fee
# 圈复杂度 = 1(基础路径)+ 判断节点数 4 = 5
# 意味着:至少 5 个测试用例才能盖住所有独立路径
def flat_fee(weight, is_vip, distance):
fee = 20 if weight > 10 else 10
fee = fee * 0.5 if is_vip else fee
fee += 5 if (fee < 50 and distance > 100) else 0
return fee
import random
random.seed(7)
cases = [(w, vip, d) for w in range(5, 25, 5) for vip in (True, False) for d in (50, 150)]
for w, vip, d in cases:
assert abs(ship_fee(w, vip, d) - flat_fee(w, vip, d)) < 1e-9
print("两版结果一致,用例数 =", len(cases))
沙箱实跑输出:
两版结果一致,用例数 = 12
看到什么算成功:两个版本在 12 组输入下结果全部一致。
读结果:ship_fee 圈复杂度 5、flat_fee 用三元表达式写成但判断节点一个没少——写法变了复杂度不变。真正降复杂度要拆函数(每段判断独立成小函数),不是换语法糖。这也解释了 5-1 的嵌套税为什么是真的:嵌套一层加一条路径。
五、30 秒实验
给 flat_fee 再加一个条件 or weight > 100(超重强制加价)——圈复杂度从 5 变 6,测试用例也得跟着补一条。指标一动,测试联动,这就是指标的价值。
六、常见误区
| 误区 | 真相 |
|---|---|
| 静态分析通过=没有 bug | 它抓静态可判的问题;逻辑错误、并发问题照样漏网 |
| 指标是给领导看的 KPI | 圈复杂度的真正用途是提醒【这里该拆函数、该加测试】 |
| 评审走过场 | 评审是最便宜的找错方式——错误越早被抓,修复成本越低 |
自测三问(点击展开)
1. **检查、走查、评审的共同点?** 答:都不执行程序,靠人读代码找问题——都属于静态验证。 2. **圈复杂度怎么数?等于什么?** 答:1+判断节点数(if/while/and/or 各计一条);等于覆盖全部独立路径所需的最少测试用例数。 3. **为什么静态分析要进 CI?** 答:快且全自动,每次提交全库扫一遍,问题在合入前就被拦下。- ☐ 5-8 学习完成
相关笔记
- 注释、缩进、嵌套 —— 嵌套层数就是复杂度的前身
- CI 与 CD —— 静态分析的常驻岗位
- ⌨️ 编程原理 · 索引 —— 全课路线图