4、代码质量保障:代码规范与Review机制、单元测试与集成测试、防御性编程、灰度发布与回滚策略
代码质量这事儿,我做了十几年交易系统,吃过太多亏了。你想想看,一行代码写错了,可能几百万就没了。所以这一章,我把自己踩过的坑、总结的经验,全掏出来给你。
核心观点:代码质量不是靠「自觉」保障的,而是靠机制。规范、测试、防御、灰度,四道防线缺一不可。
4.1 代码规范与Review机制
代码规范,说白了就是团队里的「交通规则」。没有规则,每个人按自己习惯写,那代码就是一团乱麻。我个人习惯用 Google Java Style 作为基础,再结合交易系统的特殊要求做定制。
举个例子,交易系统里时间处理特别敏感。我要求所有时间变量必须带上时区后缀:
// 不推荐
long tradeTime = 1234567890L;
// 推荐
long tradeTimeUtc8 = 1234567890L;
这种小细节,Review 的时候一眼就能看出来。说到 Review,我建议你们搞 双人Review + 随机抽查 机制。我在项目中遇到过,有一次一个同事把订单金额的单位搞错了,从「分」写成了「元」。要是没 Review 出来,上线后就是几百万的损失。
我的小技巧:Review 时别只看逻辑,还要看异常处理。交易系统里,正常路径往往没问题,出事的都是异常路径。
4.2 单元测试与集成测试
测试这块,我把它分成三层:
| 测试类型 | 覆盖范围 | 执行频率 | 我踩过的坑 |
|---|---|---|---|
| 单元测试 | 单个函数/方法 | 每次提交 | Mock 太多,测了个寂寞 |
| 集成测试 | 模块间交互 | 每日构建 | 环境不一致,测试全绿上线全红 |
| 端到端测试 | 全链路 | 每次发布前 | 数据准备太耗时,没人愿意写 |
单元测试,我要求覆盖率不低于 80%。但注意,覆盖率不是目标,测到关键路径才是。比如订单状态机,每个状态转换都要测到。我曾经因为漏测了一个「已撤单」状态,导致系统在极端情况下重复成交。
集成测试,我建议用 Testcontainers 这种工具,直接在测试里启动真实的数据库、消息队列。别用 H2 内存数据库替代 MySQL,环境差异会坑死你。
// 一个典型的集成测试示例
@Test
void testOrderCreation() {
// 启动真实MySQL容器
try (MySQLContainer mysql = new MySQLContainer("mysql:8.0")) {
mysql.start();
// 初始化数据源
DataSource ds = createDataSource(mysql.getJdbcUrl());
// 执行测试
OrderService service = new OrderService(ds);
Order order = service.createOrder("BUY", 100, 10.5);
assertNotNull(order.getId());
}
}
注意:集成测试别跑太久。我见过一个项目,集成测试跑 2 小时,开发人员都不愿意本地跑,结果上线全是问题。控制在 15 分钟内比较合理。
4.3 防御性编程
防御性编程,说白了就是「假设所有输入都是坏的」。交易系统里,数据来源复杂,上游系统可能发来各种奇怪的数据。
我常用的几个原则:
- 参数校验前置:所有对外接口,第一件事就是校验参数。别等到业务逻辑里才发现数据不对。
- 断言检查:关键业务逻辑后加断言,比如「订单金额必须大于0」。
- 降级处理:外部依赖挂了,系统不能崩。给个默认值或者缓存数据。
// 防御性编程示例
public Order createOrder(String userId, String symbol, int quantity, double price) {
// 前置校验
if (userId == null || userId.isEmpty()) {
throw new IllegalArgumentException("userId不能为空");
}
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}
if (price <= 0) {
throw new IllegalArgumentException("价格必须大于0");
}
// 业务逻辑
Order order = doCreateOrder(userId, symbol, quantity, price);
// 后置断言
assert order.getAmount() > 0 : "订单金额必须大于0";
assert order.getStatus() == OrderStatus.NEW : "新订单状态必须为NEW";
return order;
}
我曾经因为没做防御性编程,吃过一次大亏。上游系统传了一个负数价格过来,系统直接扣了用户的钱还给了用户股票。嗯,那次事故让我记住了:永远不要相信外部输入。
4.4 灰度发布与回滚策略
灰度发布,是交易系统上线的最后一道防线。我建议你们搞 「1-5-20-100」 的灰度策略:
- 1% 流量:先放 1% 的用户,观察 10 分钟。主要看有没有明显报错。
- 5% 流量:没问题就扩大到 5%,观察 30 分钟。这时候要监控业务指标,比如成交率、延迟。
- 20% 流量:再扩大到 20%,观察 1 小时。这时候可以看一些细粒度的指标。
- 100% 流量:全部放开。但保留回滚能力。
关键点:每个灰度步骤都要有「回滚按钮」。而且回滚要快,最好在 1 分钟内完成。我见过一个团队,灰度到 50% 发现有问题,结果回滚花了 10 分钟,损失已经造成了。
回滚策略,我建议用 数据库回滚 + 代码回滚 双保险。代码回滚用 Git 的 revert,数据库回滚用 Flyway 的 undo 脚本。注意,数据库回滚脚本一定要提前写好,别等到出事再写。
-- 数据库回滚脚本示例
-- 假设这次上线新增了一个字段
-- 回滚时删除这个字段
ALTER TABLE orders DROP COLUMN IF EXISTS risk_level;
我的经验:灰度发布时,一定要有「人工观察」环节。自动化监控能发现 80% 的问题,但有些业务逻辑上的问题,只有人眼才能看出来。我习惯在灰度期间,亲自跑一遍核心交易流程。
最后,送你一句话:代码质量不是测出来的,是设计出来的。规范、测试、防御、灰度,这四件事做好了,交易系统的稳定性就有了基本保障。