当 AI 编程助手已经能在几分钟内生成数百行代码时,Uncle Bob 关于 AI 时代软件工程基础的观点反而显得更有分量。Uncle Bob 是 Robert C. Martin 的常用称谓,也是《代码整洁之道》《架构整洁之道》的作者和敏捷宣言参与者。他在近年多次讨论中反复强调同一个判断:AI 并没有让软件开发变成一件更简单的事,它只是让“生成代码”的成本趋近于零,而让“确认代码正确”的成本变得更加突出。软件工程的基础——测试、架构约束、重构、代码评审和职业责任——不会因为工具变化而失效,反而会成为决定团队能否交付可靠系统的关键能力。这篇内容围绕他的这一视角展开,会先解释为什么 AI 时代反而更需要工程基础,再给出测试、SOLID 原则、工作流和排错清单这几个可以直接落地的方向。
1. 为什么 AI 时代反而要回头补软件工程基础
1.1 Uncle Bob 是谁,他的判断为什么值得被认真对待
Uncle Bob 在软件工程领域的地位,主要来自三件事:他参与起草敏捷宣言,写过《代码整洁之道》和《架构整洁之道》,并且在职业生涯中长期坚持测试驱动开发(TDD)和重构实践。这些经历让他对“软件开发中的纪律”有非常固执的立场。很多新工具刚出现时,他的回应都不是“要不要用”,而是“用了之后,你怎么保证系统仍然可控”。
他对 AI 编程工具的态度同样如此。当大量开发者开始用大模型生成函数、接口和配置时,他提醒的不是“AI 会取代程序员”,而是“AI 产生代码的速度越快,代码库中被人工确认过的比重就越低”。一旦这个比重低到某个临界点,团队就会失去对系统的理解能力和修改能力。换句话说,AI 提高了代码的“产量”,但没有提高代码的“可信度”。
这一点值得认真对待,不是因为 Uncle Bob 有权威,而是因为他指出的问题已经在许多项目中真实出现:AI 生成代码可以编译通过,可以跑通主流程,但一旦进入边界条件、错误处理、并发安全和长期维护阶段,问题就会集中爆发。这些问题的根源不在 AI,而在工程基础薄弱。
1.2 AI 改变了代码的生产方式,但没有改变代码的失效方式
传统开发中,代码由人手编写,Bug 来自人的疏忽或对需求的理解偏差。AI 辅助开发中,代码由模型生成,Bug 的来源多了一层:模型可能基于过时的训练数据,可能受到上下文窗口截断的影响,也可能在细节上产生“幻觉”,编造出不存在的函数或参数。
但代码失效的方式并没有本质变化。无论是手写还是 AI 生成,系统出问题仍然集中在几个方向:
- 需求理解错误:模型根据提示词生成功能,但提示词没有表达清楚业务规则。
- 边界条件遗漏:空值、超长输入、并发冲突、异常中断没有处理。
- 依赖版本不匹配:模型推荐的依赖版本与当前环境冲突。
- 安全问题:输入校验缺失、权限校验被绕过、敏感信息被写进日志。
- 架构腐化:代码能运行,但模块边界混乱,后续改动成本指数上升。
正因为失效方式没有变,软件工程的基本功才没有过时。测试仍然是验证行为的手段,架构仍然是控制复杂度的手段,代码评审仍然是防止质量问题进入主库的重要手段。AI 改变的是生产工具,不是质量保障机制。
1.3 工程师的职责从“编写代码”迁移到“验证与治理”
在 AI 时代,工程师对代码库的核心价值正在变化。以前,写出可运行代码本身就是主要贡献。现在,大量初稿代码可以由 AI 完成,工程师的核心工作变成四件事:拆解需求、设计约束、验证结果、修复偏差。
| 传统任务 | AI 时代的做法 | 依赖的工程基础 |
|---|---|---|
| 编写函数 | AI 生成初稿,人负责评审和修改 | 代码阅读能力、设计原则 |
| 补充测试 | 人先定义行为,AI 帮助生成测试初稿 | 测试思维、边界分析 |
| 排查问题 | AI 帮助定位线索,人确认根因 | 日志分析、系统链路理解 |
| 架构演进 | 人控制方向,AI 辅助重构 | 模块边界、依赖规则 |
这里也是“软件工程银弹是什么”这个问题最适合出现的场景。Fred Brooks 早就说过,软件开发没有银弹。AI 也不是银弹,它没有消灭复杂度,而是把复杂度从“生成代码”转移到了“验证和治理代码”。团队如果把工程基础忽略掉,只追求 AI 生成速度,短期内看起来效率很高,长期会积累大量难以理解和维护的代码。
2. 把测试当作 AI 生成代码的第一道防线
2.1 测试不是质量部门的事,而是 AI 时代的信任机制
在 AI 辅助开发中,测试的功能不再是“验证别人写的代码”,而是“给 AI 生成的代码建立信任”。模型不会因为给你的代码写了很多行就保证它正确,测试才是判断正确性的最小可执行证据。
很多团队在引入 AI 编程工具后会进入一个误区:让 AI 先写代码,再让人补测试。这个顺序的问题在于,人已经看到了实现,容易顺着实现的思路去补测试,结果测试验证的是“代码自己以为的行为”,而不是业务真正需要的行为。推荐顺序是反过来的:
- 先根据需求和边界条件编写测试。
- 让 AI 生成实现,目标是让测试通过。
- 人审查 AI 生成的实现,确认没有绕过测试的隐藏行为。
- 如果测试暴露问题,再迭代修改。
这里面最核心的是第 1 步。需求被表达成可执行断言后,AI 生成的代码就不再是“凭感觉的代码”,而是“被行为约束的代码”。
2.2 用 TDD 约束 AI 生成实现:一个最小可运行示例
下面用一个简单场景演示这个流程。需求是:计算订单折扣,订单金额满 100 元打 9 折,满 500 元打 8 折,否则不打折;折扣计算必须保留两位小数。
先写测试,用 Java 和 JUnit 5 的风格演示:
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; class DiscountCalculatorTest { private final DiscountCalculator calculator = new DiscountCalculator(); @Test @DisplayName("金额不足100不打折") void shouldNotDiscountBelowThreshold() { BigDecimal result = calculator.calculate(new BigDecimal("99.99")); assertEquals(new BigDecimal("99.99"), result); } @Test @DisplayName("金额满100打9折") void shouldApplyNinePercentDiscount() { BigDecimal result = calculator.calculate(new BigDecimal("100.00")); assertEquals(new BigDecimal("90.00"), result); } @Test @DisplayName("金额满500打8折") void shouldApplyEightPercentDiscount() { BigDecimal result = calculator.calculate(new BigDecimal("500.00")); assertEquals(new BigDecimal("400.00"), result); } @Test @DisplayName("折扣结果保留两位小数") void shouldRoundToTwoDecimalPlaces() { BigDecimal result = calculator.calculate(new BigDecimal("123.45")); assertEquals(new BigDecimal("111.11"), result); } @Test @DisplayName("金额为负时抛出异常") void shouldRejectNegativeAmount() { assertThrows(IllegalArgumentException.class, () -> calculator.calculate(new BigDecimal("-1"))); } }测试写完后,再把这段测试和需求一起交给 AI,要求它生成DiscountCalculator。AI 生成的实现只要满足上述断言,就说明主路径和边界路径都符合预期。这里要留意负数的边界条件,很多 AI 生成的代码只处理正数,因为训练数据里的常见写法没有异常分支,测试在这里就把缺陷挡住了。
测试通过后,还要人工检查实现是否真的使用了BigDecimal,而不是把金额转成double计算。否则测试可能因为精度问题偶尔通过,进入生产后却出现金额偏差。测试能证明行为,但架构和实现质量仍然需要人来判断。
代码块后的说明:
public class DiscountCalculator { public BigDecimal calculate(BigDecimal amount) { if (amount == null || amount.signum() < 0) { throw new IllegalArgumentException("amount must be positive"); } BigDecimal discount = BigDecimal.ONE; if (amount.compareTo(new BigDecimal("500")) >= 0) { discount = new BigDecimal("0.8"); } else if (amount.compareTo(new BigDecimal("100")) >= 0) { discount = new BigDecimal("0.9"); } return amount.multiply(discount) .setScale(2, RoundingMode.HALF_UP); } }这个实现的关键点是:比较金额用compareTo而不是equals,因为BigDecimal的equals会同时比较精度,new BigDecimal("100").equals(new BigDecimal("100.0"))返回 false,但compareTo不会;折扣比例优先处理高档位,避免出现“满 500 同时匹配两个折扣”的问题;最后用setScale(2, RoundingMode.HALF_UP)控制小数精度。
2.3 用测试金字塔给 AI 辅助开发设计质量层次
测试金字塔在传统项目里用于平衡不同层级测试的数量,在 AI 辅助开发中同样适用,而且更重要。AI 生成代码时,最容易生成大量“看起来正常”的单元级函数,却缺少跨模块的集成验证。
| 测试层级 | 覆盖目标 | AI 时代的使用方式 | 数量占比建议 |
|---|---|---|---|
| 单元测试 | 单个函数、类、算法 | 先写测试再生成实现,约束行为 | 约 70% |
| 集成测试 | 模块之间、数据库、外部 API | 验证 AI 生成的接口调用方式是否正确 | 约 20% |
| 端到端测试 | 核心业务链路 | 验证主流程和关键回退路径 | 约 10% |
这里要注意,单元测试数量多,不代表价值一定高。如果测试只是把实现内部逻辑原样抄一遍,断言没有业务意义,那 AI 生成代码时也会给出一堆“看起来在验证”的测试,实际上没有防止回归。评估测试质量的关键不是行数,而是这些测试能否区分“行为正确”和“行为错误”。
2.4 用状态图和活动图补齐 AI 容易漏掉的边界
状态图和活动图是软件工程里的经典工具,在 AI 时代仍然有不可替代的价值。很多人觉得画图浪费时间,但当 AI 生成代码的速度快过人工审读速度时,图和状态表反而是最便宜的需求描述方式。
以订单状态为例,先列出状态迁移:
| 当前状态 | 事件 | 目标状态 | 是否允许 |
|---|---|---|---|
| 新订单 | 支付成功 | 已支付 | 允许 |
| 新订单 | 取消 | 已取消 | 允许 |
| 已支付 | 发货 | 已发货 | 允许 |
| 已发货 | 确认收货 | 已完成 | 允许 |
| 已完成 | 取消 | 无 | 不允许 |
把这个表格给 AI,让它生成状态机代码时,它就不容易漏掉“已完成订单不允许取消”这类分支。活动图则适合用来表达复杂业务流的判断步骤,比如“满减与折扣同时存在时,先算哪个”。AI 生成代码前先给一张活动图或状态表,相当于把需求里的隐性规则变成了显式输入,这是减少 AI 幻觉的低成本手段。
3. 用 SOLID 原则审查 AI 生成代码
3.1 单职责原则:AI 最常犯的“万能函数”问题
SOLID 是一组面向对象设计原则,但它的思想并不局限于面向对象语言。AI 生成代码时,最常见的问题不是语法错误,而是“一个函数承担了太多职责”。下面这段 Python 示例很典型:
def send_notification(order, user, channel, message): # 查询订单信息 order_total = sum(item["price"] * item["count"] for item in order["items"]) discount = 0.9 if order_total >= 100 else 1.0 final_price = order_total * discount # 决定发送渠道 if channel == "sms": print(f"发送短信给 {user['phone']}: {message}") elif channel == "email": print(f"发送邮件给 {user['email']}: {message}") elif channel == "wechat": print(f"发送微信给 {user['wechat_id']}: {message}") # 记录日志 log_file = open("notification.log", "a") log_file.write(f"{order['id']} {channel} {final_price}\n") log_file.close()这个函数在 AI 生成场景中非常常见:输入结构灵活,内部逻辑丰富,但没有清晰的边界。它同时做了订单金额计算、渠道分发、消息发送和日志记录。如果后续要改折扣规则,必须进入整个函数修改;如果要新增一个渠道,也要进入函数添加分支;如果日志写失败,还可能影响正常发送流程。
重构的思路是按职责拆分:
def calculate_order_total(order): total = sum(item["price"] * item["count"] for item in order["items"]) return total * (0.9 if total >= 100 else 1.0) def create_message(channel, user, text): address = get_channel_address(channel, user) return {"channel": channel, "address": address, "text": text} def send_message(message): if message["channel"] == "sms": return send_sms(message["address"], message["text"]) if message["channel"] == "email": return send_email(message["address"], message["text"]) raise ValueError(f"unsupported channel: {message['channel']}")重构后,每个函数只有一个明确目的:算金额、组消息、发消息。测试可以分别针对这三个函数,折扣规则变化不会影响发送逻辑,新增渠道时也不需要修改金额计算。
3.2 依赖倒置与接口隔离:防止代码写死实现
AI 生成代码时,还有一个高频问题:直接new依赖对象。这在小型脚本中问题不大,但在需要测试和替换实现的项目里会造成严重不便。以 Python 为例:
# 错误写法:发送逻辑被绑定在具体邮件客户端上 class NotificationService: def send(self, message): smtp = SMTPClient("smtp.example.com", 25) smtp.send(message)这个类很难测试,因为每次调用都会真实连接 SMTP。推荐做法是依赖注入,让上层依赖抽象,而不是具体实现:
class NotificationService: def __init__(self, sender): self._sender = sender def send(self, message): self._sender.send(message)测试时可以注入一个内存中的假实现:
class FakeSender: def __init__(self): self.messages = [] def send(self, message): self.messages.append(message) def test_notification_service(): fake = FakeSender() service = NotificationService(fake) service.send({"channel": "email", "text": "hello"}) assert len(fake.messages) == 1这样,AI 生成代码是否容易测试,往往取决于它是否遵守了依赖倒置原则。生成代码时如果发现大量外部依赖被直接构造,就要在提示词里增加约束:所有外部依赖通过构造函数或参数传入。
3.3 把 SOLID 变成生成前约束和审查清单
与其等 AI 生成完代码再逐条检查 SOLID,不如在生成前就把约束写进提示词。下面的提示词模板可以在与 AI 交互时使用:
请实现订单通知发送功能,要求: 1. 单职责原则:每个函数只做一件事,不要同时处理金额计算、发送和日志。 2. 依赖倒置:外部依赖(邮件客户端、短信服务、日志记录)通过构造函数注入。 3. 接口隔离:消息对象只包含发送所需的字段,不要传入整个订单对象。 4. 错误处理:发送失败时抛出明确异常,不要吞掉错误。 5. 不输出测试代码,由我单独要求。审查时也可以按这个顺序快速过一遍:
| 审查点 | 检查方法 | 不合格信号 |
|---|---|---|
| 单职责 | 看函数名和函数体是否一致 | 一个函数超过 30 行且有多段无关逻辑 |
| 开闭原则 | 新增渠道或类型时是否要改已有函数 | 使用大量 if/elif 判断内部类型 |
| 依赖倒置 | 外部依赖是否通过构造或参数传入 | 代码内部直接 new 外部服务 |
| 接口隔离 | 函数参数是否暴露过多无关字段 | 整个实体对象被到处传递 |
| 最小知识 | 是否可以通过参数组合降低耦合 | 函数内部访问多层嵌套属性 |
这里要强调一点:SOLID 适合作为审查框架,但不要教条化。对于一次性脚本或简单 CRUD,过度设计反而降低可读性。判断标准是代码是否处于长期演进路径上。如果确定这段代码只在一个轻量项目里使用,单职责和依赖注入适度即可;如果是核心业务模块,就必须严格审查。
4. 搭建一套可复用的 AI 辅助编码工作流
4.1 区分学习环境、开发环境与生产环境的工程要求
AI 辅助开发很容易在“本地能跑”和“生产可用”之间产生错觉,所以先把环境要求分开。
| 维度 | 学习环境 | 开发/测试环境 | 生产环境 |
|---|---|---|---|
| 目标 | 验证思路、快速试验 | 集成测试、质量反馈 | 稳定交付、可回滚 |
| AI 代码审查 | 可以宽松 | 必须进入代码评审 | 必须有完整评审记录 |
| 测试要求 | 可写可不写 | 核心逻辑必须有测试 | 全链路测试和回归测试 |
| CI/CD | 不需要 | 必须有 | 必须有,且包含门禁 |
| 依赖管理 | 可以宽松 | 须锁版本 | 须锁版本并做安全扫描 |
| 日志监控 | 不要求 | 基础日志 | 完整日志、告警、追踪 |
学习环境里当然可以为了效率直接让 AI 生成代码并运行。但进入团队协作后,如果没有统一审查流程,AI 生成代码就会以不同风格和不同质量水平进入同一个代码库,后续维护成本会急剧上升。
4.2 用提示词模板约束 AI 输出结构
好的提示词不是越长越好,而是要让 AI 的输出结构可预期。一个面向工程落地的提示词模板通常包含五部分:目标、输入、约束、输出结构、自检要求。
任务:计算订单折扣。 目标:根据订单金额返回折扣后金额。 输入: - amount:BigDecimal,订单金额,不能为负数 - 折扣规则:满100打9折,满500打8折 输出结构: - 函数签名 - 业务逻辑实现 - 边界条件处理 - 关键参数说明 约束: - 使用 BigDecimal 避免浮点误差 - 金额比较使用 compareTo - 折扣结果保留两位小数,四舍五入 - 不生成测试代码 请先输出你的理解和边界假设,再输出实现。最后一句“请先输出理解和边界假设”很重要。它让 AI 在写代码前先暴露需求理解,方便人在代码生成前发现偏差。实际使用中,如果 AI 理解错了,直接调整提示词比事后修改代码更高效。
4.3 提交前检查清单:把人工评审变成固定流程
AI 生成的代码提交到主库前,团队成员应该逐项确认。下面是一份可以直接复制到 PR 描述里的检查清单:
- [ ] 是否理解这段代码的业务目的 - [ ] 是否包含核心路径与边界条件的测试 - [ ] 是否处理了空值、超长输入、异常中断 - [ ] 是否使用显式依赖注入,而不是内部 new 外部服务 - [ ] 是否避免使用不受控的全局变量 - [ ] 外部 API 或数据库访问是否有超时和重试 - [ ] 是否记录了关键日志,同时避免记录敏感信息 - [ ] 新增依赖是否与现有版本兼容 - [ ] 是否运行了完整测试套件 - [ ] 是否经过另一位工程师评审其中“是否经过另一位工程师评审”是 AI 时代最容易跳过的步骤。AI 生成代码后,开发者自己容易产生“它已经通过验证”的错觉,但模型没有上下文背景,无法判断这段代码是否符合团队架构,评审必须由人完成。
4.4 建设 CI 和代码评审设施,让 AI 代码进入主库前被校验
CI 的价值在 AI 时代被放大了。代码是 AI 生成的,但质量门禁必须交给自动化设施。一个最小可用的 GitHub Actions 配置可以这样组织:
name: ai-code-quality on: pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - name: Run tests run: mvn test - name: Check coverage run: mvn jacoco:report - name: Lint run: mvn spotless:check关键点是把测试、覆盖率检查和格式检查都设为 PR 通过的前置条件。这样即使开发者个人疏忽,AI 生成代码也要先过自动检查,再进入人工评审。生产环境还可以在此基础上增加依赖安全扫描、镜像构建、集成环境部署等阶段。
识别 AI 生成代码的另一个方法是关注 PR 的说明质量。要求开发者提交 PR 时补充“这段代码解决了什么问题、为什么选择这个方案、测试覆盖了哪些场景”。如果 AI 生成了大量代码但 PR 描述里只有一行“add feature”,评审者要主动追问。这里是在把“人审”变成团队规范,而不是依赖个人自觉。
5. 常见问题排查:AI 生成代码最容易在哪里翻车
5.1 能运行但无法维护的“面条代码”
现象:AI 生成的代码可以启动、可以跑通主流程,但代码库中出现大量长函数、重复代码、职责混杂的模块。改动一个需求时,发现牵一发而动全身。
可能原因:提示词只描述了功能,没有约束结构和边界;生成过程中没有做代码评审;团队没有统一的架构规范。
检查方式:
- 统计函数长度和圈复杂度。
- 审查是否存在大段复制粘贴代码。
- 查看一个模块被多少人同时修改。
处理建议:先不追求一次重写到位。把最长的函数拆成小函数,提取常量,再逐步消除重复代码。每拆一次就运行一次测试,确保没有改变行为。把“结构约束”写进提示词,例如“函数不超过 30 行”“每个函数只做一件事”。
预防建议:在 PR 评审中把“可维护性”作为通过条件,而不是只看功能是否可运行。
5.2 测试全绿,但改一个参数就崩溃
现象:CI 显示测试全部通过,但开发者改一个配置参数或调整业务规则后,系统立即出现问题。
可能原因:测试断言不够严格,只验证了“不报错”,没有验证“结果正确”;测试过度绑定实现,比如 mock 了内部方法,导致测试通过但真实行为没有被验证;AI 生成的测试代码使用了很多宽松匹配,比如assertEquals(1, result.size())但没检查元素内容。
检查方式:
- 检查测试是否包含真实输入输出断言。
- 检查是否大量使用
verify(mock, never())这类“没有发生也是正确”的断言。 - 临时修改业务逻辑,看测试是否能捕获变化。
- 使用变异测试工具评估测试质量。
处理建议:对核心业务逻辑补充参数化测试;把测试断言从“不报错”改为“返回预期值”;减少对内部实现细节的 mock,更多地验证公开行为。
5.3 依赖版本漂移与 API 幻觉
现象:AI 生成的代码使用了某个第三方库的 API,编译时通过,运行时却报NoSuchMethodError或ModuleNotFoundError;或者 AI 推荐了一个不存在的依赖版本。
可能原因:模型的训练数据滞后,推荐了旧版本 API;模型基于相似代码生成了“看起来合理”的函数名,但该版本并未提供该函数。
检查方式:
- 查看编译日志和运行时堆栈。
- 检查依赖配置文件里是否出现了与实际环境不匹配的版本。
- 使用
mvn dependency:tree或pip show确认实际加载的版本。 - 打开第三方库文档比对 API 签名。
处理建议:提示词中要求“所有依赖必须使用项目现有版本,不要假设存在某个函数”;引入新依赖后先在本地做最小验证;CI 中锁定依赖版本并添加依赖安全扫描。
5.4 一套可复用的排查顺序
AI 生成的代码有问题时,排查顺序和手写代码一样,从输入到验证逐层推进:
- 输入是否正确:调用是否传入了空值、错误类型或不完整对象。
- 路径与命名是否符合项目约定:文件是否放入正确目录,类名是否与配置一致。
- 依赖版本是否匹配:构建工具是否成功解析了所有依赖。
- 配置是否生效:环境变量、配置文件是否被正确加载。
- 权限、端口、网络是否正常:外部服务是否可达,连接是否超时。
- 日志和异常是否明确:是否记录了足够的上线文信息。
- 框架或语言版本是否存在限制:某些 API 只在特定版本可用。
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 能运行但无法维护 | 职责混杂、函数过长 | 统计圈复杂度、代码审查 | 小步拆分,逐步重构 |
| 测试全绿但改参崩溃 | 断言过弱、mock 过细 | 检查断言、做变异测试 | 补真实断言,减少实现 mock |
| 编译通过运行报错 | API 幻觉、版本漂移 | 查依赖树、比对文档 | 锁定版本,提示词约束依赖 |
| 配置修改后不生效 | 改错文件或未加载配置 | 检查环境、日志和配置来源 | 确认配置文件加载路径 |
6. AI 时代的软件工程基础:可执行的落地清单
6.1 个人开发者清单
如果你是一名使用 AI 编程工具的独立开发者,可以从这几条开始落地:
- 先用一句话写下业务规则,再写测试,最后让 AI 生成实现,不要跳过测试。
- 每次生成代码后,给自己留出 30 分钟重构时间,删除重复代码,缩小函数职责。
- 不提交没有经过运行验证的 AI 生成代码。
- 复杂逻辑先画状态表或流程图,再交给 AI,减少覆盖不到的边界。
- 记录提示词版本,像记录依赖版本一样记录“这个功能是用什么约束生成的”。
6.2 团队协作清单
团队引入 AI 编程工具时,比工具本身更需要统一的是协作规范:
- 统一 AI 工具和插件,避免每个人的生成代码风格不一致。
- 禁止把未经评审的 AI 生成代码直接提交主库。
- 建立提示词模板库,把团队常用的业务场景沉淀为标准提示词。
- CI 中增加测试门禁、覆盖率门禁和格式检查。
- 每次评审 AI 生成代码时,要求在 PR 描述中补充测试策略和架构说明。
- 定期抽查主要模块,确认 AI 引入的技术债在可控范围。
6.3 技术负责人需要关注的工程设施
技术负责人不需要逐行审查代码,但要保证工程设施可以兜住 AI 生成代码的偏差:
- 代码质量门禁:覆盖率、复杂度、格式检查必须自动化。
- 依赖治理:引入新依赖要有流程,不能由 AI 自动推荐后直接使用。
- 架构边界:通过架构测试或依赖约束,防止模块越界调用。
- 回滚能力:AI 生成代码引发线上问题时,可以快速回滚到上一个稳定版本。
- 学习机制:定期组织“AI 生成案例复盘”,把质量问题沉淀为团队知识。
6.4 学习路径与下一步扩展
AI 时代补软件工程基础,不一定要回到教科书,可以按这个路径学习:
- 重读《代码整洁之道》,重点看命名、函数、注释和测试章节。
- 练习 TDD:选一个小功能,强迫自己先写测试再写实现,持续一周。
- 学习画状态图和活动图,并用它们设计测试用例。
- 理解依赖注入和接口设计,在代码评审中主动检查这两个点。
- 自己搭一次 CI,把 lint、测试、覆盖率门禁串联起来。
- 把提示词工程当作“需求描述能力”来练习,学会把隐性规则写清楚。
AI 工具解决的是“快速得到一段候选实现”,而软件工程基础解决的是“这段代码能不能被信任、被修改、被长期维护”。Uncle Bob 反复强调的恰恰是这个判断:工具越强,工程师越需要理解自己在做什么,否则生成的代码越多,失控的风险就越大。对你来说,最有价值的练习不是学会更多 AI 快捷键,而是把测试、重构、评审和架构约束装进日常开发流程,让 AI 成为一个更快地写出初稿的助手,而不是一个绕过思考的替代品。