1. 从一个真实问题说起

上周我在维护一个 FastAPI 项目时,遇到一个典型场景:需求方要求给订单接口增加一个“按状态筛选”的参数。这个改动本身不大,但项目里订单查询逻辑分散在 service 层和 repository 层,还牵扯到几个历史遗留的边界条件。我决定用 Codex 来辅助完成这次修改,同时给自己定了一条纪律:AI 改代码可以,但必须走完整的 Git 分支 + 自动化测试流程,改完必须人工审查 git diff

这篇文章就围绕这个真实案例展开,记录我如何使用 Codex 完成“需求分析 → 定位代码 → 制定计划 → 创建分支 → 修改代码 → 补充测试 → 运行测试 → 检查 diff → 人工审查 → 合并”的完整闭环。如果你也在用 Codex 辅助日常开发,希望这套流程能帮你减少“AI 改坏了但没发现”的风险。

先说明一点:Codex 是 OpenAI 提供的 AI 编程助手,具体功能、可用范围和套餐权益可能随版本、账号和地区变化,请以 OpenAI 当前官方页面为准。本文重点不是介绍 Codex 的功能清单,而是分享一套可复制的工程实践。

2. 项目背景与需求描述

为了演示,我准备了一个精简但完整的 FastAPI 项目,目录结构如下:

order-service/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   ├── schemas.py
│   ├── repository.py
│   └── service.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   └── test_order_service.py
├── requirements.txt
└── README.md

核心需求是:给 GET /orders 接口增加一个可选的 status 查询参数,支持 pendingpaidshippedcancelled 四种状态,不传时返回全部订单。

这个需求看似简单,但有几个隐藏的坑:

  • repository 层目前只有一个 list_all() 方法,没有按条件过滤的能力;
  • service 层直接透传 repository 结果,没有参数校验;
  • 现有测试只覆盖了“返回全部订单”的场景,没有覆盖过滤逻辑;
  • 如果直接让 AI 改,它可能会顺手重构其他无关代码,这正是我们最需要防范的。

3. 用 Codex 分析项目,先不急着改代码

很多人在使用 Codex 时犯的第一个错误,就是上来就让它“帮我加个功能”。Codex 确实能直接改,但如果没有先让它理解项目结构,它可能会改错文件、改错逻辑,甚至引入无关改动。

我的做法是:第一步只让 Codex 读代码、输出分析,不修改任何文件。

下面是我实际使用的提示词:

请分析当前项目 order-service 的代码结构,重点回答以下问题,不要修改任何文件:

1. GET /orders 接口从入口到数据返回的完整调用链是什么?
2. repository 层目前提供了哪些查询方法?是否支持按状态过滤?
3. service 层是否对查询参数做了校验?
4. 现有测试覆盖了哪些场景?缺少哪些场景?
5. 如果要增加 status 查询参数,涉及哪些文件、哪些函数?

输出要求:
- 列出每个相关文件的路径和关键函数名
- 指出当前实现中可能影响新需求的边界条件
- 不要给出修改建议,只做现状分析

这个提示词的关键在于:明确“不要修改任何文件”,并且要求 Codex 输出文件路径和函数名,方便我后续核对。

Codex 返回的分析结果大致如下(我做了精简):

调用链:main.py 中 GET /orders 路由 → service.py 的 list_orders() → repository.py 的 list_all()
repository.list_all() 当前直接返回内存列表,无过滤参数
service.list_orders() 未对 query 参数做任何校验
tests/test_order_service.py 仅覆盖 list_all 返回全部订单的场景
涉及文件:app/main.py、app/service.py、app/repository.py、tests/test_order_service.py

这一步的价值在于:在动手之前,先确认 AI 对项目的理解是否准确。 如果 Codex 的分析结果和实际代码不符,说明它没有正确理解项目,这时候就不应该继续让它改代码。

4. 让 Codex 制定修改计划,再创建 Git 分支

分析完成后,我让 Codex 输出一份具体的修改计划,仍然不修改代码:

基于你刚才的分析,请为“增加 status 查询参数”这个需求制定一份修改计划,包含:

1. 每个需要修改的文件,以及具体的修改点(函数名、改动内容)
2. 新增测试用例的清单,包括正常场景和边界场景
3. 明确禁止修改的文件和代码范围
4. 修改完成后需要运行的测试命令

注意:
- 只允许修改 app/main.py、app/service.py、app/repository.py、tests/test_order_service.py
- 禁止修改 models.py、schemas.py、requirements.txt
- 不要重构与 status 过滤无关的代码
- 不要改动现有测试的断言逻辑

Codex 给出的计划大致如下:

1. app/repository.py:给 list_all() 增加可选参数 status,内部按状态过滤
2. app/service.py:list_orders() 增加 status 参数,并校验取值合法性
3. app/main.py:路由中读取 status 查询参数,传给 service
4. tests/test_order_service.py:新增 4 个测试用例(每种状态各一个)+ 1 个非法状态用例
5. 运行 pytest tests/ -v 验证

拿到计划后,我先人工确认这份计划没有越界,然后创建 Git 分支:

git checkout -b feature/order-status-filter

这里有一个重要的工程原则:AI 修改代码之前,一定要先创建独立的分支。 这样即使 AI 改坏了,也不会影响主分支,随时可以丢弃重来。

5. 让 Codex 实现功能并补充测试

计划确认后,我让 Codex 开始实现。这一步的提示词要明确“允许修改范围”和“禁止修改内容”,并且要求它同时补充测试:

请按照刚才确认的计划实现 status 查询参数功能,并同步补充测试。

允许修改的文件:
- app/repository.py
- app/service.py
- app/main.py
- tests/test_order_service.py

禁止修改的文件:
- app/models.py
- app/schemas.py
- requirements.txt
- README.md

实现要求:
1. repository.list_all() 增加 status 参数,status 为 None 时返回全部
2. service.list_orders() 校验 status 取值,非法值返回 400
3. main.py 路由读取 status 查询参数
4. 测试覆盖:四种合法状态各一个用例、不传 status 返回全部、非法 status 返回 400
5. 不要修改与本次需求无关的代码

完成后运行 pytest tests/ -v,确保所有测试通过。

Codex 修改后的核心代码大致如下。

app/repository.py

from typing import List, Optional
from app.models import Order

VALID_STATUSES = {"pending", "paid", "shipped", "cancelled"}


class OrderRepository:
    def __init__(self) -> None:
        self._orders: List[Order] = [
            Order(id=1, customer="张三", status="pending"),
            Order(id=2, customer="李四", status="paid"),
            Order(id=3, customer="王五", status="shipped"),
            Order(id=4, customer="赵六", status="cancelled"),
        ]

    def list_all(self, status: Optional[str] = None) -> List[Order]:
        if status is None:
            return self._orders
        return [order for order in self._orders if order.status == status]

app/service.py

from typing import List, Optional
from fastapi import HTTPException
from app.repository import OrderRepository, VALID_STATUSES


class OrderService:
    def __init__(self) -> None:
        self._repo = OrderRepository()

    def list_orders(self, status: Optional[str] = None) -> List[dict]:
        if status is not None and status not in VALID_STATUSES:
            raise HTTPException(status_code=400, detail=f"非法状态值: {status}")
        orders = self._repo.list_all(status=status)
        return [order.dict() for order in orders]

app/main.py

from typing import Optional
from fastapi import FastAPI
from app.service import OrderService

app = FastAPI()
service = OrderService()


@app.get("/orders")
def list_orders(status: Optional[str] = None):
    return service.list_orders(status=status)

tests/test_order_service.py 中新增的测试:

import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)


def test_list_orders_without_status_returns_all():
    resp = client.get("/orders")
    assert resp.status_code == 200
    assert len(resp.json()) == 4


@pytest.mark.parametrize("status", ["pending", "paid", "shipped", "cancelled"])
def test_list_orders_with_valid_status(status):
    resp = client.get("/orders", params={"status": status})
    assert resp.status_code == 200
    assert len(resp.json()) == 1
    assert resp.json()[0]["status"] == status


def test_list_orders_with_invalid_status_returns_400():
    resp = client.get("/orders", params={"status": "unknown"})
    assert resp.status_code == 400

这里需要说明几点:

  • VALID_STATUSES 定义在 repository 层,service 层引用它做校验,避免两处维护同一份状态列表;
  • 非法状态返回 400 而不是静默忽略,这样前端能明确感知参数错误;
  • 测试用 parametrize 覆盖四种合法状态,避免写四个重复的测试函数。

6. 运行测试并检查 git diff

Codex 修改完成后,我并没有直接信任结果,而是先运行测试:

cd order-service
pytest tests/ -v

预期输出:

tests/test_order_service.py::test_list_orders_without_status_returns_all PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[pending] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[paid] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[shipped] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[cancelled] PASSED
tests/test_order_service.py::test_list_orders_with_invalid_status_returns_400 PASSED

全部通过后,接下来是最关键的一步:检查 git diff,确认 AI 没有改无关代码。

git diff --stat
git diff app/

git diff --stat 会列出所有被修改的文件和改动行数。如果发现计划之外的文件被改动,比如 models.pyrequirements.txt,就要立即警惕。

我实际检查时发现,Codex 确实只改了计划内的四个文件,没有越界。但我也遇到过 Codex 顺手“优化”了其他代码的情况,所以这一步绝对不能省。

7. 用 Codex 做代码审查,再人工复核

测试通过、diff 干净之后,我还会让 Codex 以“审查者”身份再检查一遍自己的修改。这一步的目的是让 AI 从另一个角度发现问题:

请以资深代码审查者的身份,审查当前分支上 git diff 的改动,重点检查:

1. 是否有逻辑错误或边界条件遗漏?
2. 是否有安全风险(如参数注入、敏感信息泄露)?
3. 是否有与本次需求无关的改动?
4. 测试是否覆盖了关键场景?有没有遗漏?
5. 代码风格是否与项目现有代码一致?

输出格式:
- 每个问题标注严重程度(高/中/低)
- 给出具体文件和行号
- 只报告问题,不要直接修改代码

Codex 的审查结果通常能发现一些我忽略的细节。比如有一次它指出:service 层直接暴露了 VALID_STATUSES 的引用,如果后续状态列表变化,可能导致 service 和 repository 耦合过紧。虽然这个建议不一定需要立即采纳,但至少提醒我关注设计层面的问题。

但请注意:AI 的审查结果不能替代人工审查。 我会逐行阅读 git diff,确认每个改动都符合预期,尤其是:

  • 是否有硬编码的敏感信息(API Key、密码、Token);
  • 是否有异常处理被吞掉;
  • 是否有日志打印了不该打印的数据;
  • 是否有性能隐患(比如在循环里查数据库)。

8. 合并分支与后续验证

人工审查通过后,合并分支:

git checkout main
git pull origin main
git merge feature/order-status-filter
git push origin main

合并后,我还会在本地再跑一次完整测试,确认合并没有引入冲突:

pytest tests/ -v

如果项目配置了 GitHub Actions,可以在推送后自动触发 CI,进一步验证。下面是一个简单的 CI 配置示例:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest tests/ -v

这个配置会在每次推送或 PR 时自动安装依赖并运行测试。如果测试失败,CI 会直接标红,避免坏代码合入主分支。

9. 常见错误与解决方法

在使用 Codex 配合 Git 和 pytest 的过程中,我总结了几类常见问题。

问题一:Codex 修改了计划外的文件。

解决方法:在提示词中明确列出“允许修改的文件”和“禁止修改的文件”,修改后必须用 git diff --stat 核对。如果发现越界改动,用 git checkout -- <file> 还原该文件。

问题二:测试通过但功能实际不符合需求。

解决方法:测试只能验证“代码按测试的预期运行”,不能验证“需求理解是否正确”。建议在让 Codex 实现之前,先让它输出对需求的理解,人工确认后再动手。

问题三:Codex 生成的测试覆盖不足。

解决方法:在提示词中明确要求覆盖边界场景,比如非法参数、空数据、超长字符串等。修改后人工检查测试用例,必要时自己补充。

问题四:AI 修改破坏了原有功能。

解决方法:确保修改前已有完整的测试基线。如果项目测试覆盖不足,先让 Codex 补测试,再让它改功能。

10. 安全注意事项

无论 Codex 多强大,安全底线必须由开发者自己守住:

  • 不要把 API Key、密码和 Token 写进代码,使用环境变量保存敏感配置;
  • 不向 AI 提交生产环境密码,提示词中不要粘贴真实凭据;
  • 检查日志和配置文件中的敏感信息,AI 生成的日志代码可能无意中打印敏感数据;
  • 限制 AI 可以修改的文件范围,在提示词中明确禁止修改配置文件、密钥文件;
  • 使用最小权限原则,本地开发账号不要使用生产权限;
  • AI 生成代码必须经过测试和安全检查,不能因为测试通过就放松警惕;
  • 不要直接让 AI 修改生产环境,所有改动先走分支、测试、审查、合并流程。

11. FAQ

Q1:Codex 和 ChatGPT Plus / ChatGPT Pro 是什么关系?

Codex 是 OpenAI 的 AI 编程助手,ChatGPT Plus 和 ChatGPT Pro 是 ChatGPT 的订阅套餐。具体哪些套餐包含 Codex 访问权限、额度如何计算,请以 OpenAI 当前官方页面为准。

Q2:每次让 Codex 改代码都要走完整流程吗?

小改动可以简化,但涉及核心逻辑、数据层或生产代码时,建议至少走“分支 + 测试 + diff 检查”三步。

Q3:Codex 生成的测试可以直接信任吗?

不能。AI 生成的测试可能只覆盖了“让它通过”的场景,而不是“覆盖需求”的场景。人工审查测试用例是必须的。

Q4:如果 Codex 改坏了代码怎么办?

只要在独立分支上操作,直接丢弃分支即可:git checkout main && git branch -D feature/xxx

12. 总结

Codex 是一个强大的辅助工具,但它不能替代开发者的判断。通过“需求说明 → AI 阅读项目 → 定位相关文件 → 制定修改计划 → 创建 Git 分支 → 修改代码 → 编写测试 → 运行测试 → 检查 git diff → 人工代码审查 → 合并代码”这套流程,我可以在享受 AI 效率的同时,把风险控制在可控范围内。

核心经验可以总结为三点:

  1. 先分析、后动手:让 Codex 先输出对项目的理解和修改计划,人工确认后再实现;
  2. 用分支隔离风险:AI 的所有改动都放在独立分支上,随时可以丢弃;
  3. 测试和审查不可省:pytest 验证功能正确性,git diff 和人工审查验证改动范围。

AI 是辅助开发工具,最终代码仍然需要测试和人工审查。希望这篇文章能帮你建立一套更可靠的 Codex 工作流。

Logo

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

更多推荐