1. 一个真实场景:AI 改完代码,你敢直接合并吗

2026年8月27日,我在维护一个 Python 数据处理服务时遇到一个典型问题:需求很简单,给订单导入接口增加一个「按客户 ID 去重」的逻辑。我打开 ChatGPT Plus,把需求贴给 Codex,它很快给出了修改方案,涉及 order_importer.pycustomer_service.py 和两个测试文件。

改动看起来合理,但我心里很清楚:AI 生成的代码不能直接信任。它可能改对了目标函数,也可能顺手动了无关的配置;它可能补了测试,也可能测试根本没覆盖边界条件。如果直接合并,回归风险完全不可控。

这篇文章要解决的核心问题就是:如何用 Git 分支 + pytest + 人工审查,给 Codex 的每一次代码修改建立一条可验证、可回退、可审查的流水线。我会用一个真实的 Python 项目案例,从需求说明到合并代码,完整走一遍。

2. 项目背景与目录结构

先看这个项目的结构。它是一个基于 FastAPI 的订单导入服务,使用 pytest 做单元测试,Git 做版本管理。

order-service/
├── app/
│   ├── __init__.py
│   ├── main.py              # FastAPI 入口
│   ├── models.py            # 数据模型
│   ├── order_importer.py    # 订单导入核心逻辑
│   └── customer_service.py  # 客户查询服务
├── tests/
│   ├── __init__.py
│   ├── test_order_importer.py
│   └── test_customer_service.py
├── requirements.txt
└── README.md

当前 order_importer.py 的核心逻辑如下:

# app/order_importer.py
from typing import List, Dict


class OrderImporter:
    """订单导入器:负责把外部订单数据写入系统。"""

    def __init__(self, customer_service):
        self.customer_service = customer_service

    def import_orders(self, orders: List[Dict]) -> Dict[str, int]:
        """导入订单列表,返回统计结果。"""
        imported = 0
        skipped = 0
        for order in orders:
            customer_id = order.get("customer_id")
            if not customer_id:
                skipped += 1
                continue
            # 当前实现:每个客户可以重复导入
            self.customer_service.save_order(order)
            imported += 1
        return {"imported": imported, "skipped": skipped}

需求是:同一个客户 ID 的订单在本次导入中只能出现一次,重复的跳过。这个改动看似简单,但涉及「去重状态放在哪里」「是否影响已有测试」「边界条件怎么覆盖」三个问题,正好适合让 Codex 先出方案,再由我们验证。

3. 第一步:让 Codex 先读项目,不急着改代码

很多开发者拿到需求就直接让 AI 改代码,这是最容易出问题的地方。Codex 对项目上下文理解越充分,改动越精准。我建议第一步先让 Codex 只读不改,输出一份修改计划。

在 ChatGPT Plus 的 Codex 界面中,我使用了下面这个提示词:

请阅读 order-service 项目,重点理解 app/order_importer.py 和
app/customer_service.py 的现有实现,以及 tests/ 下已有测试的覆盖情况。

任务目标:为订单导入增加「同一客户 ID 在本批导入中只处理一次」的去重逻辑。

允许修改范围:仅 app/order_importer.py 及其对应测试文件。
禁止修改:app/main.py、app/models.py、app/customer_service.py、
requirements.txt、配置文件。

请先不要修改任何代码。只输出:
1. 当前实现的问题点
2. 去重逻辑应该放在哪一层
3. 需要新增哪些测试用例
4. 预计会改动哪些文件

这个提示词的关键在于:明确任务目标、划定允许修改范围、禁止修改内容、要求先出计划。Codex 返回的计划里,它建议在 OrderImporter 内部维护一个 seen_customer_ids 集合,而不是改 customer_service,理由是去重是本次导入的临时状态,不应该污染持久层。这个判断是合理的。

4. 第二步:创建 Git 分支,隔离 AI 的改动

在让 Codex 动手之前,先创建一个独立的分支。这样无论 AI 改出什么问题,都不会影响主分支,随时可以丢弃重来。

# 进入项目目录
cd order-service

# 确保主分支是干净的
git status
git checkout main
git pull origin main

# 创建功能分支
git checkout -b feature/codex-deduplicate-orders

# 确认当前分支
git branch --show-current

这里有一个容易被忽略的细节:在创建分支之前,先确认工作区是干净的。如果本地有未提交的改动,AI 修改时会把你的改动和它的改动混在一起,后面审查 git diff 时很难分清哪些是 AI 写的、哪些是你写的。

创建分支后,我再次让 Codex 开始实现,提示词如下:

现在开始实现去重逻辑。请严格遵循你刚才输出的修改计划。

要求:
1. 只修改 app/order_importer.py 和 tests/test_order_importer.py
2. 保持现有函数签名不变
3. 新增测试必须覆盖:正常去重、空列表、缺少 customer_id、重复 customer_id
4. 修改完成后运行 pytest,确保全部测试通过
5. 不要修改任何其他文件

5. 第三步:Codex 的修改结果与测试

Codex 完成修改后,order_importer.py 变成了这样:

# app/order_importer.py
from typing import List, Dict, Set


class OrderImporter:
    """订单导入器:负责把外部订单数据写入系统。"""

    def __init__(self, customer_service):
        self.customer_service = customer_service

    def import_orders(self, orders: List[Dict]) -> Dict[str, int]:
        """导入订单列表,返回统计结果。"""
        imported = 0
        skipped = 0
        seen_customer_ids: Set[str] = set()
        for order in orders:
            customer_id = order.get("customer_id")
            if not customer_id:
                skipped += 1
                continue
            if customer_id in seen_customer_ids:
                skipped += 1
                continue
            seen_customer_ids.add(customer_id)
            self.customer_service.save_order(order)
            imported += 1
        return {"imported": imported, "skipped": skipped}

对应的测试文件新增了去重用例:

# tests/test_order_importer.py
from app.order_importer import OrderImporter


class FakeCustomerService:
    """测试用假服务,记录保存过的订单。"""

    def __init__(self):
        self.saved_orders = []

    def save_order(self, order):
        self.saved_orders.append(order)


def test_duplicate_customer_id_skipped():
    service = FakeCustomerService()
    importer = OrderImporter(service)
    orders = [
        {"customer_id": "C001", "amount": 100},
        {"customer_id": "C001", "amount": 200},
        {"customer_id": "C002", "amount": 300},
    ]
    result = importer.import_orders(orders)
    assert result == {"imported": 2, "skipped": 1}
    assert len(service.saved_orders) == 2


def test_empty_order_list():
    service = FakeCustomerService()
    importer = OrderImporter(service)
    result = importer.import_orders([])
    assert result == {"imported": 0, "skipped": 0}


def test_missing_customer_id_skipped():
    service = FakeCustomerService()
    importer = OrderImporter(service)
    orders = [{"amount": 100}, {"customer_id": "C001", "amount": 200}]
    result = importer.import_orders(orders)
    assert result == {"imported": 1, "skipped": 1}

Codex 在修改完成后自动运行了 pytest,输出显示 3 个测试全部通过。但这里我要强调:AI 说测试通过,不等于真的通过。我们必须自己在终端里再跑一遍,并且要看测试是否真的覆盖了需求。

6. 第四步:人工验证 pytest 与 git diff

在终端里手动运行测试:

cd order-service
python -m pytest tests/ -v

预期输出:

tests/test_order_importer.py::test_duplicate_customer_id_skipped PASSED
tests/test_order_importer.py::test_empty_order_list PASSED
tests/test_order_importer.py::test_missing_customer_id_skipped PASSED
tests/test_customer_service.py::test_save_order PASSED

测试通过后,接下来是最关键的一步:检查 git diff。这一步的目的是确认 AI 只改了它应该改的文件,没有顺手动其他东西。

git diff --stat
git diff app/order_importer.py
git diff tests/test_order_importer.py

git diff --stat 的输出应该只包含两个文件:

app/order_importer.py       | 8 +++++++-
tests/test_order_importer.py | 30 ++++++++++++++++++++++++++++++

如果看到 app/main.pyrequirements.txt 出现在 diff 里,就要警惕了。这说明 Codex 越界修改了无关文件,应该用 git checkout -- <文件> 还原,或者直接丢弃整个分支重来。

7. 第五步:让 Codex 做一次代码审查

除了人工检查,我还会让 Codex 以「审查者」身份再读一遍自己的改动。这一步不是为了让它自我表扬,而是让它从边界条件和异常处理的角度找问题。

请以资深代码审查者的身份,审查当前 Git 分支上的改动。

审查重点:
1. 去重逻辑是否有并发安全隐患
2. 是否有未覆盖的边界条件
3. 异常处理是否完整
4. 是否引入了与需求无关的改动
5. 测试用例是否真正验证了需求

请列出每个问题的严重级别(高/中/低),并给出修改建议。
不要直接修改代码,只输出审查报告。

Codex 的审查报告指出一个中等级别的问题:如果 customer_service.save_order() 抛出异常,seen_customer_ids 里已经记录了该客户 ID,但订单实际没有保存成功。这会导致后续相同客户 ID 的订单被错误跳过。这是一个真实存在的边界问题,值得修复。

修复方案是在 save_order 成功之后再加入 seen_customer_ids,或者捕获异常后回滚。这里我选择让 Codex 修复:

请修复 save_order 抛异常时 seen_customer_ids 状态不一致的问题。
要求:异常时该客户 ID 不应被标记为已处理,且异常应继续向上抛出。
只修改 app/order_importer.py,并补充对应测试。

修复后的核心逻辑:

for order in orders:
    customer_id = order.get("customer_id")
    if not customer_id:
        skipped += 1
        continue
    if customer_id in seen_customer_ids:
        skipped += 1
        continue
    self.customer_service.save_order(order)
    seen_customer_ids.add(customer_id)
    imported += 1

同时新增一个测试,模拟 save_order 抛异常的场景:

def test_save_order_exception_not_marked_seen():
    class FlakyService(FakeCustomerService):
        def save_order(self, order):
            if order["customer_id"] == "C001":
                raise RuntimeError("db down")
            super().save_order(order)

    service = FlakyService()
    importer = OrderImporter(service)
    orders = [
        {"customer_id": "C001", "amount": 100},
        {"customer_id": "C001", "amount": 200},
    ]
    try:
        importer.import_orders(orders)
    except RuntimeError:
        pass
    # 第一次失败后,C001 不应被标记为已处理
    assert "C001" not in importer.seen_customer_ids

这里有一个细节:为了让测试能检查内部状态,需要把 seen_customer_ids 从局部变量改为实例属性。这也是 Codex 在修复时主动做的调整,属于合理的重构。

8. 完整工作流:从需求到合并的 Mermaid 流程图

把整个过程画成流程图,方便团队理解这套 AI 辅助开发的标准流程:

需求说明

Codex 阅读项目

输出修改计划

人工确认计划

创建 Git 分支

Codex 修改代码

Codex 运行 pytest

人工运行 pytest

检查 git diff

改动是否越界

还原文件或丢弃分支

Codex 代码审查

人工代码审查

是否发现新问题

合并到主分支

这个流程的核心思想是:AI 负责生成和迭代,人负责把关和决策。每一步都有一个明确的验证动作,而不是盲目信任 AI 的输出。

9. 常见错误与解决方法

在实际使用 Codex 的过程中,我遇到过几类高频问题,这里整理出来供参考。

问题一:Codex 修改了无关文件

表现:git diff --stat 里出现与需求无关的文件。

解决方法:用 git checkout -- <文件> 还原该文件,然后在提示词里更严格地限定修改范围,例如「只允许修改 app/order_importer.py 和 tests/test_order_importer.py,其他文件一律禁止改动」。

问题二:测试通过但需求没实现

表现:pytest 全绿,但手动验证发现去重逻辑根本没生效。

原因:AI 写的测试用例本身有缺陷,比如断言写错、或者测试数据没有覆盖真实场景。

解决方法:人工审查测试用例,确认断言与需求一致。不要只看「测试通过」这个结果。

问题三:AI 修改了函数签名,导致调用方报错

表现:order_importer.py 编译通过,但 main.py 调用时报 TypeError

解决方法:在提示词中明确「保持现有函数签名不变」,并在修改后运行全量测试,而不只是运行单个测试文件。

问题四:敏感信息被写进代码

表现:Codex 在测试代码里硬编码了数据库连接串或 API Key。

解决方法:在提示词中明确「禁止在代码中写入任何密钥、Token 或密码,一律使用环境变量」,并在代码审查时用 grep 检查:

grep -rn "api_key\|password\|secret\|token" app/ tests/

10. 安全注意事项

使用 Codex 辅助开发时,安全底线不能放松。以下几点必须遵守:

第一,不要把 API Key、密码和 Token 写进代码。无论是 AI 生成的代码还是你自己写的代码,敏感配置一律通过环境变量注入。

第二,不向 AI 提交生产环境密码。在提示词和对话中,不要粘贴生产环境的数据库连接串、云服务密钥或任何敏感凭据。

第三,限制 AI 可以修改的文件范围。每次任务都在提示词中明确允许修改和禁止修改的文件,从源头减少越界风险。

第四,AI 生成代码必须经过测试和安全检查。pytest 通过只是最低要求,还要检查是否有 SQL 注入、路径穿越、越权访问等常见安全问题。

第五,不要直接让 AI 修改生产环境。所有改动先在本地分支完成,经过测试和审查后再走正常的发布流程。

11. FAQ

问:ChatGPT Plus 和 ChatGPT Pro 的 Codex 有什么区别?

答:具体功能和可用范围可能随版本、账号和地区变化,请以 OpenAI 当前官方页面为准。一般来说,不同订阅层级在可用额度、模型能力和功能范围上可能有差异,建议以官方说明为准。

问:Codex 生成的测试一定可靠吗?

答:不一定。AI 生成的测试可能存在断言不严谨、覆盖不全、甚至测试本身有 bug 的情况。人工审查测试用例是必须的步骤。

问:每次都要让 Codex 先出计划再改代码吗?

答:对于涉及多个文件或核心逻辑的改动,强烈建议先出计划。对于单行修复这类简单任务,可以适当简化流程,但 Git 分支和测试验证不能省。

问:Codex 修改后测试失败怎么办?

答:把失败信息贴回给 Codex,让它分析原因并修复。同时人工查看失败堆栈,确认是 AI 引入的回归还是原有问题。

问:如何防止 Codex 修改无关代码?

答:在提示词中明确允许和禁止修改的文件列表,修改后用 git diff --stat 核对改动范围,必要时用 git checkout 还原越界文件。

12. 总结

Codex 是一个强大的辅助开发工具,但它不能替代人的判断。这篇文章通过一个订单去重的真实案例,展示了如何用 Git 分支隔离 AI 改动、用 pytest 验证功能正确性、用 git diff 检查改动范围、用双重代码审查发现边界问题。

核心要点可以归纳为四条:

第一,先计划后动手。让 Codex 先读项目、输出修改计划,人工确认后再实施。

第二,分支隔离。所有 AI 改动都在独立分支上进行,随时可以丢弃重来。

第三,测试与 diff 双重验证。pytest 通过不代表一切正常,必须人工检查 git diff 确认没有越界修改。

第四,人工审查不可替代。AI 可以辅助发现边界问题,但最终的安全、质量和业务正确性,仍然需要开发者自己把关。

AI 编程的正确姿势,不是把代码交给 AI 然后祈祷它写对,而是建立一套「AI 生成 → 自动验证 → 人工审查 → 安全合并」的工程化流程。这套流程,才是 AI 辅助开发真正可靠的基础。

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。

更多推荐