1. 引言

2026 年,ChatGPT Plus / Pro 开通后,Codex 已经不再是「帮你补全代码」的玩具,而是一个能接管完整开发任务的 AI 工程师。对于已经写过几年代码、熟悉 Git、CI/CD 和测试的开发者来说,真正的问题不是「Codex 能做什么」,而是「怎么把它嵌入到自己的工程流程里,让它成为团队里最靠谱的结对程序员」。

本文不讨论订阅价格与充值方式,而是面向有经验的开发者,聚焦 Codex 的工程化用法:如何设计任务上下文、如何让它理解大型代码库、如何用测试驱动 Codex 迭代、如何把 AI 生成的代码纳入严格的代码审查流程。全文约 3000 字,包含可运行的代码示例与真实工作流拆解。

2. Codex 的工程化定位:不是补全器,是协作者

很多开发者第一次打开 Codex 时,习惯性地把它当成一个「超级 Tab 键」——输入半行代码,等它补全。这是对 Codex 能力的严重低估。Codex 的核心价值在于它能够理解整个项目的上下文,并在沙箱环境中自主执行多步任务。

2.1 与 Copilot 的本质区别

  • 上下文范围:Copilot 主要基于当前文件与打开的标签页;Codex 会读取项目结构、配置文件、测试代码,形成对代码库的整体理解。
  • 执行能力:Copilot 只生成代码建议;Codex 可以在沙箱中运行命令、执行测试、安装依赖,甚至自主修复编译错误。
  • 任务粒度:Copilot 适合「补全函数」;Codex 适合「实现这个 feature,并保证测试通过」。

2.2 Plus 与 Pro 的工程差异

从工程实践角度看,Plus 与 Pro 的差异主要体现在:

  • 沙箱资源配额:Pro 提供更充裕的 CPU/内存与更长的任务执行时间,适合大型构建、数据库迁移、长时间测试套件。
  • 并发任务数:Pro 支持同时运行多个 Codex 会话,适合并行处理多个独立模块。
  • 上下文窗口:Pro 在长对话中能保持更稳定的项目上下文,减少「忘记前面需求」的情况。

对于日常小项目,Plus 足够;对于需要跑完整测试套件、做跨模块重构的工程,Pro 的体验明显更顺滑。

3. 让 Codex 理解你的项目:上下文工程

资深开发者都知道,给 Codex 的任务描述质量,直接决定输出质量。这不是「写清楚需求」那么简单,而是上下文工程——你要主动告诉 Codex 项目的技术栈、目录结构、编码规范,甚至已知的坑。

3.1 项目级上下文文件

在项目根目录维护一个 CODEX.md,作为 Codex 的「项目手册」:

# CODEX.md

## 技术栈
- Python 3.12 + FastAPI 0.115
- SQLAlchemy 2.0 + Alembic 迁移
- pytest + httpx 测试

## 目录结构
- app/:业务代码
- app/models/:SQLAlchemy 模型
- app/api/:路由层
- tests/:测试文件,命名 test_*.py

## 编码规范
- 类型注解必须完整
- 所有数据库操作走 repository 模式
- 错误处理统一抛 HTTPException,禁止裸 raise

## 已知问题
- SQLite 的并发写入有锁问题,测试用内存数据库
- 不要用 @app.on_event,改用 lifespan

在 Codex 对话开头附上这个文件,或直接让它读取:

请先阅读项目根目录的 CODEX.md,然后基于其中的规范完成以下任务。

3.2 用 git diff 提供精准上下文

对于已有代码库的修改,与其让 Codex 读整个项目,不如直接告诉它「改了什么、为什么改」:

git diff HEAD~1 --stat
git diff HEAD~1 -- app/api/books.py

然后把 diff 粘贴给 Codex,并说明目标:

这是最近一次提交的改动。请基于这个 diff 补充对应的单元测试,
覆盖新增的边界条件,并确保测试风格与 tests/ 目录下现有文件一致。

3.3 让 Codex 先「复述」再动手

对于复杂任务,先让 Codex 输出它的理解与实施计划,确认无误后再让它写代码:

在动手之前,请先:
1. 复述你对这个任务的理解。
2. 列出你计划修改的文件与每个文件的改动点。
3. 指出可能影响到的现有测试。

确认后再开始实现。

这一步能显著减少「Codex 理解偏差导致的大规模返工」。

4. 实战:用 Codex 实现一个带数据库的 REST API

下面通过一个完整的实战案例,演示如何用 Codex 完成一个带 SQLite 持久化的图书管理 API,并让测试驱动整个迭代过程。

4.1 需求描述

我们需要一个图书管理服务,支持以下接口:

  • GET /books:获取图书列表,支持 ?author= 过滤
  • POST /books:新增图书
  • GET /books/{id}:获取单本图书详情
  • PUT /books/{id}:更新图书信息
  • DELETE /books/{id}:删除图书

数据使用 SQLite 持久化,通过 SQLAlchemy 2.0 管理,使用 lifespan 管理数据库生命周期。

4.2 向 Codex 下达任务

在 Codex 界面中,输入以下提示词:

请在我的项目中实现图书管理 API,要求:
1. 使用 SQLAlchemy 2.0 的 DeclarativeBase 定义 Book 模型,字段包括 id、title、author、year。
2. 使用 FastAPI 的 lifespan 管理数据库初始化,不要用 @app.on_event。
3. 实现上述 5 个接口,支持 author 过滤。
4. 使用 Pydantic 做输入校验,year 必须在 1900 到当前年份之间。
5. 为每个接口编写 pytest 测试,使用内存 SQLite 数据库。
6. 遵循项目根目录 CODEX.md 中的编码规范。

4.3 Codex 生成的代码

Codex 会自动创建 app/models.pyapp/schemas.pyapp/api/books.py 等文件。核心代码如下:

# app/models.py
from datetime import datetime

from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


class Book(Base):
    __tablename__ = "books"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    title: str = mapped_column(String(200), nullable=False)
    author: str = mapped_column(String(100), nullable=False, index=True)
    year: int = mapped_column(Integer, nullable=False)
# app/schemas.py
from datetime import datetime

from pydantic import BaseModel, Field


class BookCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=200)
    author: str = Field(..., min_length=1, max_length=100)
    year: int = Field(..., ge=1900, le=datetime.now().year)


class BookUpdate(BaseModel):
    title: str | None = Field(None, min_length=1, max_length=200)
    author: str | None = Field(None, min_length=1, max_length=100)
    year: int | None = Field(None, ge=1900, le=datetime.now().year)


class BookOut(BaseModel):
    id: int
    title: str
    author: str
    year: int

    model_config = {"from_attributes": True}
# app/api/books.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models import Book
from app.schemas import BookCreate, BookOut, BookUpdate
from app.database import get_session

router = APIRouter(prefix="/books", tags=["books"])


@router.get("", response_model=list[BookOut])
def list_books(author: str | None = None, session: Session = Depends(get_session)):
    stmt = select(Book)
    if author:
        stmt = stmt.where(Book.author == author)
    return session.scalars(stmt.order_by(Book.id)).all()


@router.post("", response_model=BookOut, status_code=status.HTTP_201_CREATED)
def create_book(payload: BookCreate, session: Session = Depends(get_session)):
    book = Book(**payload.model_dump())
    session.add(book)
    session.commit()
    session.refresh(book)
    return book


@router.get("/{book_id}", response_model=BookOut)
def get_book(book_id: int, session: Session = Depends(get_session)):
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="Book not found")
    return book


@router.put("/{book_id}", response_model=BookOut)
def update_book(book_id: int, payload: BookUpdate, session: Session = Depends(get_session)):
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="Book not found")
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(book, field, value)
    session.commit()
    session.refresh(book)
    return book


@router.delete("/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_book(book_id: int, session: Session = Depends(get_session)):
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="Book not found")
    session.delete(book)
    session.commit()

4.4 运行与验证

启动服务:

uvicorn app.main:app --reload --port 8000

测试接口:

curl -X POST http://localhost:8000/books \
  -H "Content-Type: application/json" \
  -d '{"title": "Clean Code", "author": "Robert C. Martin", "year": 2008}'

curl "http://localhost:8000/books?author=Robert%20C.%20Martin"

curl -X PUT http://localhost:8000/books/1 \
  -H "Content-Type: application/json" \
  -d '{"year": 2009}'

预期输出:

[{"id":1,"title":"Clean Code","author":"Robert C. Martin","year":2009}]

5. 测试驱动:让 Codex 自己证明代码是对的

对于资深开发者,Codex 生成的代码必须经过测试验证才能合入。Codex 的杀手级能力在于:它能自己写测试、跑测试、根据失败信息迭代修复,形成一个闭环。

5.1 生成的测试文件

Codex 会自动创建 tests/test_books.py

import pytest
from fastapi.testclient import TestClient

from app.database import Base, engine
from app.main import app


@pytest.fixture()
def client():
    Base.metadata.create_all(engine)
    with TestClient(app) as c:
        yield c
    Base.metadata.drop_all(engine)


def test_create_book(client):
    response = client.post("/books", json={
        "title": "The Pragmatic Programmer",
        "author": "Andrew Hunt",
        "year": 1999,
    })
    assert response.status_code == 201
    data = response.json()
    assert data["id"] == 1
    assert data["title"] == "The Pragmatic Programmer"


def test_filter_by_author(client):
    client.post("/books", json={
        "title": "Clean Code", "author": "Robert C. Martin", "year": 2008,
    })
    client.post("/books", json={
        "title": "Refactoring", "author": "Martin Fowler", "year": 1999,
    })
    response = client.get("/books", params={"author": "Martin Fowler"})
    assert response.status_code == 200
    data = response.json()
    assert len(data) == 1
    assert data[0]["title"] == "Refactoring"


def test_update_book(client):
    created = client.post("/books", json={
        "title": "Clean Code", "author": "Robert C. Martin", "year": 2008,
    }).json()
    response = client.put(f"/books/{created['id']}", json={"year": 2009})
    assert response.status_code == 200
    assert response.json()["year"] == 2009


def test_get_book_not_found(client):
    response = client.get("/books/999")
    assert response.status_code == 404


def test_create_book_invalid_year(client):
    response = client.post("/books", json={
        "title": "Invalid", "author": "Test", "year": 1800,
    })
    assert response.status_code == 422

5.2 让 Codex 自己跑测试并修复

运行测试:

pytest tests/ -v

如果测试失败,直接把失败信息丢回给 Codex:

pytest 输出如下,请分析失败原因并修复:

________________________ test_filter_by_author ________________________
E   AssertionError: assert 2 == 1
E    +  where 2 = len(data)

请修复过滤逻辑,并重新运行测试确认通过。

Codex 会分析失败堆栈,定位到查询逻辑,修复后重新运行测试,直到全部通过。这个「写测试 → 跑测试 → 修代码」的闭环,是 Codex 最值得依赖的工作方式。

6. 进阶:Codex 驱动的代码重构

Codex 的另一个强大能力是跨文件重构。假设我们需要将数据存储从内存列表迁移到 SQLite 数据库,只需向 Codex 描述目标:

请将图书数据存储从内存列表迁移到 SQLite。
要求:
1. 使用 sqlite3 标准库,不要引入 ORM。
2. 创建 database.py 模块,封装数据库连接与初始化。
3. 修改 main.py,让所有接口操作数据库。
4. 保持现有 API 接口不变。
5. 更新测试,使用临时数据库。

6.1 生成的 database.py

import sqlite3
from contextlib import contextmanager
from pathlib import Path

DB_PATH = Path("books.db")


def init_db() -> None:
    with get_conn() as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS books (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                author TEXT NOT NULL,
                year INTEGER NOT NULL
            )
        """)


@contextmanager
def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
        conn.commit()
    finally:
        conn.close()

6.2 修改后的 main.py 核心逻辑

from database import get_conn, init_db

app = FastAPI(title="Book Management API")


@app.on_event("startup")
def on_startup() -> None:
    init_db()


@app.get("/books", response_model=List[Book])
def list_books() -> List[Book]:
    with get_conn() as conn:
        rows = conn.execute("SELECT * FROM books ORDER BY id").fetchall()
    return [Book(**dict(row)) for row in rows]


@app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED)
def create_book(book: BookCreate) -> Book:
    with get_conn() as conn:
        cursor = conn.execute(
            "INSERT INTO books (title, author, year) VALUES (?, ?, ?)",
            (book.title, book.author, book.year),
        )
        new_id = cursor.lastrowid
    return Book(id=new_id, **book.model_dump())

6.3 重构验证

rm -f books.db
pytest test_main.py -v

Codex 会自动更新测试文件,使用 tmp_path fixture 创建临时数据库,确保测试之间互不干扰。

7. 工作流建议与最佳实践

基于实际使用经验,以下是在 ChatGPT Plus / Pro 中高效使用 Codex 的建议:

7.1 任务描述技巧

  • 明确输入与输出:描述清楚输入数据格式与期望的输出结果。
  • 提供约束条件:指定技术栈、性能要求、代码风格等限制。
  • 分步下达:将大任务拆分为多个小任务,逐步确认结果。

7.2 代码审查流程

编写需求描述

Codex 生成代码

人工审查关键逻辑

测试是否通过

反馈错误信息给 Codex

代码合并与提交

7.3 注意事项

  • 敏感信息保护:不要在对话中粘贴 API 密钥、数据库密码等敏感信息。
  • 版本控制:每次重大修改前先提交 Git,便于回滚。
  • 人工复核:Codex 生成的代码仍需人工审查,尤其是安全相关逻辑。

8. 常见问题排查

8.1 Codex 无法读取项目文件

确保项目目录已正确挂载到 Codex 沙箱。检查文件权限:

ls -la
chmod -R u+rwx .

8.2 依赖安装失败

如果沙箱中网络受限,可以手动下载依赖包并上传:

pip download -r requirements.txt -d ./packages

然后在 Codex 中指定本地安装:

pip install --no-index --find-links=./packages -r requirements.txt

8.3 测试环境不一致

本地测试通过但沙箱失败时,检查 Python 版本:

python --version

requirements.txt 中固定版本,并在项目根目录添加 runtime.txt 指定 Python 版本。

9. 总结

ChatGPT Plus / Pro 中的 Codex 已经从一个简单的代码生成器,进化为能够理解项目上下文、自主执行多步开发任务的 AI 编程智能体。通过本文的实战案例,我们完成了:

  • 使用 Codex 从零构建 FastAPI 图书管理服务
  • 自动生成并运行 pytest 测试
  • 跨文件重构,将内存存储迁移到 SQLite
  • 掌握高效的任务描述与代码审查工作流

建议读者从一个小型项目开始,逐步探索 Codex 的边界。随着使用经验的积累,你会发现它不仅能加速编码,还能帮助你建立更规范的工程实践。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐