一、凌晨两点的"收银事故"
版本控制上了 Git,六个人的代码终于能有序合并了。但新的问题来了。
那天,大刘在库存模块加了个"自动补货"功能。他改了一个函数——他觉得只是"顺手优化"了一下 calculate_stock() 的写法。第二天,门店收银机集体报错:所有订单的库存扣减都失效了,卖出 100 本,库存纹丝不动。
凌晨两点,六个人被叫到公司。大刘很委屈:"我就是重构了一下函数啊!"
小陈看着报错:"这……你把 stock -= quantity 改成了 stock = stock - quantity,逻辑一样啊?等等,你把这个函数改成了返回新值,但调用方还在用旧接口……"
老周脸色铁青:"这不是大刘一个人的错。是我们团队没有'质量防线',让一个低级错误直接穿透到了线上。今天,我给你们上第四课——怎么让代码的质量不再靠'某个人小心'。"
二、第三层:代码质量——把"靠谱"变成"规矩"
2.1 编码规范:让六个人写得像一个人
"第一个问题:六个人六种风格。"老周说,"大刘喜欢缩写变量名,小雯喜欢超长命名,阿杰代码里全是魔法数字。风格不统一,互相看代码的成本就高,出错概率就大。"
# 大刘的风格
def c(s, n):
if n > s: return -1
return s - n
# 小雯的风格
def calculate_remaining_stock_after_sale(current_stock, quantity_sold):
if quantity_sold > current_stock:
return -1
return current_stock - quantity_sold
# 老周的规范建议
def sell_book(stock, quantity):
"""扣减库存;库存不足返回错误码 -1。"""
if quantity > stock:
return -1
return stock - quantity
"所以第一条规矩:编码规范(Coding Standard)。命名规则、缩进、注释格式、禁止魔法数字……定成文档,大家照着写。现在业界有现成的规范(如 Python 的 PEP 8、Google 代码规范),拿一套来用,别自己发明。"
"规范的价值:六个人写的代码,看起来像一个人写的。 读代码的成本直线下降,写错的可能直线下降。"
2.2 静态分析:让机器检查"低级错误"
"人检查规范,会累、会漏。让机器检查!"老周说,"这叫静态分析(Static Analysis)——不运行代码,光读代码就发现潜在问题。"
# Python 的静态检查工具
ruff check 结算页.py
# 输出:
# 结算页.py:42:5 F401 'os' 导入了但未使用
# 结算页.py:58:9 F821 未定义名称 'discout'(拼写错误!)
"还有更狠的——类型检查。mypy 能提前发现'你把它当字符串用了,但它其实是数字'这种运行时才炸的错。"
# mypy 能抓到这种错
def total_price(price: float, qty: int) -> float:
return price * qty
total_price("29.9", 2) # mypy 报错:期望 float,却给了 str
"低级错误交给机器,高级判断留给人类——这就是规范和静态分析的分工。"
2.3 代码评审:上一章的闸门,这里再强调一次
"评审不是走过场。"老周说,"我们技术部规定:评审必须真读代码,发现问题要具体指出。评审的质量,决定了合并进主干的代码质量。一条铁律:评审没过,不许合并,没有例外。"
2.4 重构:改代码的"手术规范"
"回到大刘的事故——他确实是'重构',但重构错了姿势。"老周说,"重构(Refactoring)的定义很严格:在不改变外部行为的前提下,改善内部结构。"
重构安全三步:
1. 改之前:确保有测试兜底(没测试先补测试)
2. 小步改:一次只改一小块,改完立刻跑测试
3. 改之后:跑全部测试,绿灯才叫重构完成
大刘错在哪:没测试就动手,还顺手改了接口(外部行为变了),
那不是重构,那是'重写',还是裸奔重写。
"记住一句话:没有测试的重构,就像没有安全绳的攀岩。"
三、第四层:测试——给代码织一张安全网
3.1 为什么需要测试:改代码的"勇气来源"
"大刘之所以敢'顺手优化',是因为他不知道改坏了会怎样。"老周说,"测试的终极价值不是'证明代码对',而是给你改代码的勇气——有测试兜底,你才敢放心重构、放心加功能。"
3.2 测试金字塔:三层测试
老周画了个金字塔:
▲ E2E 测试(端到端) 少而慢,模拟真实用户操作
▲▲▲ 集成测试 中量,验证模块之间协作
▲▲▲▲▲ 单元测试 多而快,验证单个函数/类
"单元测试(Unit Test):测一个函数、一个类,不依赖外部。跑得飞快,几毫秒一个。它是金字塔的底座,数量最多。"
# 单元测试:只测 sell_book 这个函数
def test_sell_book_正常扣减():
assert sell_book(100, 3) == 97
def test_sell_book_库存不足返回-1():
assert sell_book(2, 5) == -1
def test_sell_book_恰好卖完():
assert sell_book(3, 3) == 0
"集成测试(Integration Test):测多个模块协作。比如'订单模块调用库存模块扣减'——验证两个模块之间的'接口契约'对不对。"
# 集成测试:订单 + 库存协作
def test_下单后库存减少():
order = create_order(book_id="b001", qty=2)
assert query_stock("b001") == 98 # 100 - 2
"E2E 测试(End-to-End):模拟真实用户操作——打开浏览器,点击、下单、付款、看结果。最接近真实,但最慢、最贵,只覆盖关键路径。"
# E2E:模拟顾客完整下单流程
def test_顾客完整下单流程():
browser.open("https://shop.yunjian.com")
browser.click("《三体》加入购物车")
browser.click("去结算")
browser.fill("地址", "杭州市...")
browser.click("提交订单")
assert browser.find("下单成功") is not None
"测试金字塔的原则:底层多、上层少。 用大量快而廉价的单元测试兜住逻辑,用少量慢而贵的 E2E 守住关键流程。不要一上来就全写 E2E——又慢又脆,跑一次十分钟,没人愿意跑,测试就废了。"
3.3 测试覆盖率:别迷信数字
"覆盖率(Coverage)是'被测试执行到的代码行数 / 总行数'。"老周说,"覆盖率低一定不好,但覆盖率高也不一定好——它只能说明'这些行跑过',不能说明'跑对了'。我们定个底线:核心业务模块覆盖率 ≥ 80%,但更重要的是把关键逻辑(打折、库存、支付)都测到。"
3.4 TDD:先写测试,再写代码
"最后讲一个流派:TDD(Test-Driven Development,测试驱动开发)。顺序反着来——先写测试,再写代码让测试通过。"
TDD 循环(红-绿-重构):
1. 红:先写一个会失败的测试(还没实现功能)
2. 绿:写最少的代码让测试通过
3. 重构:整理代码,保持测试全绿
循环,直到功能完成。
# 1. 红:先写测试(功能还没写)
def test_满减活动_满99减20():
assert apply_promotion(100) == 80
# 2. 绿:写最小实现
def apply_promotion(total):
if total >= 99:
return total - 20
return total
# 3. 跑测试 → 绿 → 重构
"TDD 的好处:测试永远存在(不是事后补的),代码永远有验证。坏处:上手有门槛,很多场景不适用。我们不强制全团队 TDD,但核心业务逻辑必须'先有测试再动代码'。"
四、章末:老周的第三、四层总结
第三层:代码质量
├── 编码规范 → 六个人写得像一个人
├── 静态分析 → 低级错误交给机器(ruff / mypy)
├── 代码评审 → 评审没过,不许合并
└── 重构 → 没测试的重构是裸奔攀岩
第四层:测试(安全网)
├── 测试金字塔 → 单元多而快 / 集成中等 / E2E 少而慢
├── 覆盖率 → 底线 80%,别迷信数字
└── TDD → 先写测试再写代码(红-绿-重构)
"从这一章开始,云间书店技术部有了'质量防线':规范约束写法,机器检查低级错误,评审把住合并关,测试兜住改动风险。"
"师傅,那现在我们的代码质量稳了吗?"小陈问。
"还差最后一步,也是最关键的一步——这些测试,谁来跑?什么时候跑?"老周说,"如果测试只在每个人电脑上手动跑,那又是靠自觉。我们要让机器自动跑——每次代码一提交,就自动构建、自动测试,有问题当场报警。这叫持续集成(CI),是下一章的事。而它,直接连着更刺激的——上线和运维。"