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% 流量:先放 1% 的用户,观察 10 分钟。主要看有没有明显报错。
  2. 5% 流量:没问题就扩大到 5%,观察 30 分钟。这时候要监控业务指标,比如成交率、延迟。
  3. 20% 流量:再扩大到 20%,观察 1 小时。这时候可以看一些细粒度的指标。
  4. 100% 流量:全部放开。但保留回滚能力。

关键点:每个灰度步骤都要有「回滚按钮」。而且回滚要快,最好在 1 分钟内完成。我见过一个团队,灰度到 50% 发现有问题,结果回滚花了 10 分钟,损失已经造成了。

回滚策略,我建议用 数据库回滚 + 代码回滚 双保险。代码回滚用 Git 的 revert,数据库回滚用 Flyway 的 undo 脚本。注意,数据库回滚脚本一定要提前写好,别等到出事再写。

-- 数据库回滚脚本示例
-- 假设这次上线新增了一个字段
-- 回滚时删除这个字段

ALTER TABLE orders DROP COLUMN IF EXISTS risk_level;

我的经验:灰度发布时,一定要有「人工观察」环节。自动化监控能发现 80% 的问题,但有些业务逻辑上的问题,只有人眼才能看出来。我习惯在灰度期间,亲自跑一遍核心交易流程。

最后,送你一句话:代码质量不是测出来的,是设计出来的。规范、测试、防御、灰度,这四件事做好了,交易系统的稳定性就有了基本保障。

代码质量保障四道防线 代码规范 命名、格式、注释 测试保障 单元/集成/端到端 防御性编程 校验、断言、降级 灰度 回滚 各防线核心要点: 1. 代码规范:统一风格,强制Review,关注异常路径 2. 测试保障:覆盖率80%+,关键路径全覆盖,集成测试用真实环境 3. 防御性编程:参数校验前置,后置断言,外部依赖降级 4. 灰度发布:1-5-20-100策略,快速回滚,人工观察 核心原则:代码质量不是测出来的,是设计出来的