⌨️ 第五章 从设计到测试
📄 本篇摘要

不是所有问题都要跑起来才能发现。检查(评审)靠人读,静态分析靠机器扫——不执行程序就找毛病;软件指标再把【代码质量】变成数字,圈复杂度是其中最出名的一个。

📝 关系

上一节:Bug、调试与 BTS | 下一节:软件生命周期与 DevOps

一、检查/评审:人是最强的静态分析器

代码评审(code review):写完不直接合入,先由同事读一遍——逻辑对不对、有没有更简单的写法、命名达意吗。研究表明评审能抓住大量测试抓不住的【设计层面】问题,还能传播知识(谁都能看懂谁那块)。

教材区分三个词:检查(inspection,正式流程、带检查单、逐条过)、走查(walkthrough,作者主讲、大家听)、评审(review,统称)。日常开发里最常用的是轻量级 pull request 评审。

二、静态分析:机器扫,不执行

静态分析=分析代码文本/语法树而不运行它。能抓什么:

Python 生态:ruff/flake8/mypy(类型检查)。优势是快、便宜、全覆盖——CI 里每提交一次就全库扫一遍(5-10 的流水线里它是常驻岗)。

三、软件指标:给质量一个刻度

常用指标:

四、动手 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?** 答:快且全自动,每次提交全库扫一遍,问题在合入前就被拦下。

相关笔记