上一篇 下一篇 分享链接 返回 返回顶部

我用一个 FastAPI 小项目实测 AI 编程:能省事,也会埋坑(附完整命令)

发布人:慈云数据-客服中心 发布时间:22小时前 阅读量:5

AI编程 测评报告|附完整命令

一、前言:为什么要做一次“AI编程”测评?

过去一年,AI 编程工具从“代码补全助手”迅速进化成了“半自动开发协作伙伴”。从最早的函数级补全,到现在能够理解项目结构、生成单元测试、解释报错、重构模块、编写脚本甚至协助完成完整需求,AI 编程已经不再是一个新鲜概念,而是正在改变开发流程的现实工具。

但是,AI 编程工具到底能不能真正提升效率?它适合什么场景?在哪些任务上表现稳定,在哪些地方容易出错?如果把它放进真实项目中,它是“生产力工具”,还是“制造隐患的黑盒”?这些问题不能只靠宣传文案判断,必须通过实际测评来验证。

本文将围绕一次较完整的 AI 编程测评展开,内容包括:

  • 测评目标与方法;
  • 测试环境搭建;
  • 常见开发任务设计;
  • AI 编程在需求理解、代码生成、调试、重构、测试等环节的表现;
  • 实测过程中使用的完整命令;
  • 最终结论与使用建议。

本文不是单纯吹捧 AI,也不是刻意否定 AI,而是从实际开发角度,尽可能客观地观察 AI 编程工具在当前阶段的能力边界。


二、测评目标

本次测评主要关注以下几个问题:

1. AI 是否能准确理解需求?

很多 AI 编程工具看似可以快速生成代码,但实际项目中最重要的不是“能写代码”,而是“能不能写对代码”。如果需求理解有偏差,即使代码语法正确,也可能产生业务逻辑错误。

2. AI 生成代码的可运行性如何?

代码能否直接运行,是最基础的衡量指标。本次测评会重点观察:

  • 是否存在语法错误;
  • 是否缺少依赖;
  • 是否引入不存在的方法;
  • 是否能够通过测试;
  • 是否符合常见工程规范。

3. AI 是否能辅助调试?

真实开发中,代码出错是常态。一个优秀的 AI 编程助手不应该只会“从零生成”,还应该能够阅读错误日志、定位问题、给出修改方案。

4. AI 是否适合重构和维护?

很多项目的难点不在于新建一个 Demo,而在于维护已有代码。AI 是否能理解已有项目结构、减少重复代码、优化命名、拆分模块,是衡量其实用价值的重要标准。

5. AI 是否能生成可靠测试?

测试代码是 AI 编程的一个高价值场景。因为测试往往逻辑清晰、边界明确,如果 AI 能快速生成有效测试,将大幅提升开发效率。


三、测评环境

本次测评选择一个轻量但接近真实业务的小型后端项目作为样本,技术栈如下:

项目 说明
操作系统 Ubuntu 22.04 / macOS 均可
编程语言 Python 3.11
Web 框架 FastAPI
数据库 SQLite
ORM SQLAlchemy
测试框架 pytest
包管理工具 pip / venv
代码规范 ruff
类型检查 mypy

测评项目是一个简单的任务管理系统,包含以下功能:

  • 用户创建任务;
  • 查询任务列表;
  • 更新任务状态;
  • 删除任务;
  • 按状态过滤任务;
  • 自动生成基础单元测试;
  • 修复已知 Bug;
  • 对服务层代码进行重构。

选择这个项目的原因是:它足够简单,便于观察 AI 的基础能力;同时又包含数据库、API、测试、重构等常见开发环节,能够覆盖实际工作中的多个场景。


四、项目初始化命令

下面是完整的项目初始化命令。读者可以直接复制执行,搭建与本文类似的测评环境。

mkdir ai-coding-evaluation
cd ai-coding-evaluation

python3 -m venv .venv
source .venv/bin/activate

python -m pip install --upgrade pip

pip install fastapi uvicorn sqlalchemy pydantic pytest httpx ruff mypy

创建项目目录:

mkdir app
mkdir tests

touch app/__init__.py
touch app/main.py
touch app/database.py
touch app/models.py
touch app/schemas.py
touch app/crud.py
touch tests/test_tasks.py

查看项目结构:

tree

如果系统未安装 tree,可以使用:

find . -maxdepth 3 -type f

五、基础代码准备

在正式让 AI 参与开发之前,先准备一个最小可运行项目。

1. 数据库配置

app/database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

DATABASE_URL = "sqlite:///./tasks.db"

engine = create_engine(
    DATABASE_URL,
    connect_args={"check_same_thread": False},
)

SessionLocal = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine,
)

Base = declarative_base()


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

2. 数据模型

app/models.py

from sqlalchemy import Boolean, Column, Integer, String

from app.database import Base


class Task(Base):
    __tablename__ = "tasks"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, nullable=False, index=True)
    description = Column(String, nullable=True)
    completed = Column(Boolean, default=False)

3. Pydantic Schema

app/schemas.py

from pydantic import BaseModel


class TaskBase(BaseModel):
    title: str
    description: str | None = None


class TaskCreate(TaskBase):
    pass


class TaskUpdate(BaseModel):
    title: str | None = None
    description: str | None = None
    completed: bool | None = None


class TaskOut(TaskBase):
    id: int
    completed: bool

    class Config:
        from_attributes = True

4. CRUD 逻辑

app/crud.py

from sqlalchemy.orm import Session

from app import models, schemas


def create_task(db: Session, task: schemas.TaskCreate):
    db_task = models.Task(
        title=task.title,
        description=task.description,
    )
    db.add(db_task)
    db.commit()
    db.refresh(db_task)
    return db_task


def list_tasks(db: Session, completed: bool | None = None):
    query = db.query(models.Task)
    if completed is not None:
        query = query.filter(models.Task.completed == completed)
    return query.all()


def get_task(db: Session, task_id: int):
    return db.query(models.Task).filter(models.Task.id == task_id).first()


def update_task(db: Session, task_id: int, task: schemas.TaskUpdate):
    db_task = get_task(db, task_id)
    if db_task is None:
        return None

    update_data = task.model_dump(exclude_unset=True)
    for key, value in update_data.items():
        setattr(db_task, key, value)

    db.commit()
    db.refresh(db_task)
    return db_task


def delete_task(db: Session, task_id: int):
    db_task = get_task(db, task_id)
    if db_task is None:
        return False

    db.delete(db_task)
    db.commit()
    return True

5. FastAPI 入口

app/main.py

from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session

from app import crud, models, schemas
from app.database import Base, engine, get_db

Base.metadata.create_all(bind=engine)

app = FastAPI(title="AI Coding Evaluation API")


@app.post("/tasks", response_model=schemas.TaskOut)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
    return crud.create_task(db, task)


@app.get("/tasks", response_model=list[schemas.TaskOut])
def list_tasks(
    completed: bool | None = None,
    db: Session = Depends(get_db),
):
    return crud.list_tasks(db, completed)


@app.get("/tasks/{task_id}", response_model=schemas.TaskOut)
def get_task(task_id: int, db: Session = Depends(get_db)):
    db_task = crud.get_task(db, task_id)
    if db_task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return db_task


@app.patch("/tasks/{task_id}", response_model=schemas.TaskOut)
def update_task(
    task_id: int,
    task: schemas.TaskUpdate,
    db: Session = Depends(get_db),
):
    db_task = crud.update_task(db, task_id, task)
    if db_task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return db_task


@app.delete("/tasks/{task_id}")
def delete_task(task_id: int, db: Session = Depends(get_db)):
    ok = crud.delete_task(db, task_id)
    if not ok:
        raise HTTPException(status_code=404, detail="Task not found")
    return {"deleted": True}

六、运行项目命令

启动服务:

uvicorn app.main:app --reload

浏览器打开:

http://127.0.0.1:8000/docs

使用 curl 创建任务:

curl -X POST "http://127.0.0.1:8000/tasks" \
  -H "Content-Type: application/json" \
  -d '{"title":"测试 AI 编程","description":"验证 AI 生成代码是否可用"}'

查询任务:

curl "http://127.0.0.1:8000/tasks"

更新任务:

curl -X PATCH "http://127.0.0.1:8000/tasks/1" \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

按状态过滤:

curl "http://127.0.0.1:8000/tasks?completed=true"

删除任务:

curl -X DELETE "http://127.0.0.1:8000/tasks/1"

七、测评任务设计

为了尽可能模拟真实开发场景,本次测评设计了六类任务。

任务一:根据需求生成接口

提示词示例:

请基于当前 FastAPI 项目,为任务管理系统增加“按 completed 状态过滤任务”的功能。
要求:
1. GET /tasks 支持 completed 查询参数;
2. completed 可选;
3. 不传时返回全部任务;
4. 传 true 时返回已完成任务;
5. 传 false 时返回未完成任务;
6. 给出需要修改的文件和完整代码。

任务二:生成单元测试

提示词示例:

请为当前 FastAPI 任务管理系统生成 pytest 测试。
要求覆盖:
1. 创建任务;
2. 查询任务列表;
3. 查询单个任务;
4. 更新任务 completed 状态;
5. 删除任务;
6. 查询不存在任务返回 404。
请给出 tests/test_tasks.py 的完整代码。

任务三:解释错误日志

提示词示例:

运行 pytest 后出现以下错误,请分析原因并给出修复方案:
sqlalchemy.exc.OperationalError: no such table: tasks

任务四:重构 CRUD 层

提示词示例:

请重构 app/crud.py,使代码更清晰,并增加类型标注。
要求:
1. 不改变对外接口;
2. 保持现有测试通过;
3. 避免重复查询;
4. 返回值类型尽量明确。

任务五:生成代码规范检查命令

提示词示例:

请给出本项目的 Python 代码格式检查、静态检查和类型检查命令。
工具包括 ruff 和 mypy。

任务六:发现潜在工程问题

提示词示例:

请审查当前项目,指出可能存在的工程问题,包括数据库初始化、测试隔离、异常处理、依赖管理、配置管理等方面,并给出改进建议。

八、AI 编程表现测评

1. 需求理解能力:表现较好,但依赖提示词质量

在接口功能扩展类任务中,AI 的表现整体不错。对于“按 completed 状态过滤任务”这类需求,AI 基本能够理解可选查询参数的含义,也知道应该分别修改 API 层和 CRUD 层。

较好的输出通常会包括:

  • main.py 中增加 completed: bool | None = None
  • crud.py 中增加过滤逻辑;
  • 不传参数时返回全部;
  • 传入参数时使用 SQLAlchemy filter

但如果提示词较模糊,例如只说“增加过滤功能”,AI 可能会默认使用字符串参数,如 status=done,而不是基于现有字段 completed。这说明 AI 并不是天然理解你的业务模型,它更像一个高水平但需要明确上下文的开发同事。

结论: AI 的需求理解能力足以应对中低复杂度功能开发,但前提是需求描述清晰,并提供必要上下文。


2. 代码生成能力:速度快,但需要人工 Review

在生成 FastAPI 接口、SQLAlchemy 查询、Pydantic Schema 等常见代码时,AI 的效率非常高。它可以在几十秒内生成一组看起来完整的代码,并且语法通常正确。

不过,实测中也发现了一些常见问题:

问题一:依赖版本不匹配

例如 Pydantic v1 和 v2 的配置写法不同。AI 有时会生成:

class Config:
    orm_mode = True

但在 Pydantic v2 中,更推荐:

class Config:
    from_attributes = True

这类问题不一定会立刻导致项目无法运行,但会出现警告或兼容性隐患。

问题二:测试数据库污染

AI 初次生成测试时,可能直接使用正式的 tasks.db,导致测试数据污染开发数据。比较合理的做法是测试时使用独立数据库,例如 sqlite:///./test.db,并在每次测试前后清理表。

问题三:异常处理不完整

对于不存在的任务,AI 通常能处理 404;但对于非法输入、空标题、超长字段等边界条件,除非明确要求,否则 AI 不一定主动补充。

结论: AI 生成代码的可用性较高,但不能跳过人工审查。它适合作为初稿生成器,而不适合完全无人值守地合并到主分支。


九、测试代码生成结果

一个较合理的 tests/test_tasks.py 示例:

import os

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

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

TEST_DATABASE_URL = "sqlite:///./test.db"

engine = create_engine(
    TEST_DATABASE_URL,
    connect_args={"check_same_thread": False},
)

TestingSessionLocal = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine,
)


def override_get_db():
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()


@pytest.fixture(autouse=True)
def setup_database():
    Base.metadata.drop_all(bind=engine)
    Base.metadata.create_all(bind=engine)
    yield
    Base.metadata.drop_all(bind=engine)
    if os.path.exists("./test.db"):
        os.remove("./test.db")


app.dependency_overrides[get_db] = override_get_db

client = TestClient(app)


def test_create_task():
    response = client.post(
        "/tasks",
        json={
            "title": "Write evaluation report",
            "description": "Test AI coding capability",
        },
    )
    assert response.status_code == 200
    data = response.json()
    assert data["title"] == "Write evaluation report"
    assert data["completed"] is False
    assert "id" in data


def test_list_tasks():
    client.post("/tasks", json={"title": "Task 1"})
    client.post("/tasks", json={"title": "Task 2"})

    response = client.get("/tasks")

    assert response.status_code == 200
    data = response.json()
    assert len(data) == 2


def test_get_task():
    create_response = client.post("/tasks", json={"title": "Task detail"})
    task_id = create_response.json()["id"]

    response = client.get(f"/tasks/{task_id}")

    assert response.status_code == 200
    assert response.json()["title"] == "Task detail"


def test_update_task_completed():
    create_response = client.post("/tasks", json={"title": "Need complete"})
    task_id = create_response.json()["id"]

    response = client.patch(
        f"/tasks/{task_id}",
        json={"completed": True},
    )

    assert response.status_code == 200
    assert response.json()["completed"] is True


def test_filter_completed_tasks():
    client.post("/tasks", json={"title": "Done"})
    create_response = client.post("/tasks", json={"title": "Todo"})
    task_id = create_response.json()["id"]

    client.patch(f"/tasks/{task_id}", json={"completed": True})

    response = client.get("/tasks?completed=true")

    assert response.status_code == 200
    data = response.json()
    assert len(data) == 1
    assert data[0]["completed"] is True


def test_delete_task():
    create_response = client.post("/tasks", json={"title": "Delete me"})
    task_id = create_response.json()["id"]

    delete_response = client.delete(f"/tasks/{task_id}")
    get_response = client.get(f"/tasks/{task_id}")

    assert delete_response.status_code == 200
    assert delete_response.json() == {"deleted": True}
    assert get_response.status_code == 404


def test_get_not_found():
    response = client.get("/tasks/999")

    assert response.status_code == 404

运行测试:

pytest -q

如果想查看更详细输出:

pytest -vv

运行指定测试文件:

pytest tests/test_tasks.py -q

运行指定测试函数:

pytest tests/test_tasks.py::test_create_task -q

十、代码质量检查命令

使用 ruff 检查代码:

ruff check .

自动修复部分问题:

ruff check . --fix

格式化代码:

ruff format .

使用 mypy 做类型检查:

mypy app

如果希望同时运行测试和检查,可以执行:

ruff check .
ruff format --check .
mypy app
pytest -q

也可以写成一行:

ruff check . && ruff format --check . && mypy app && pytest -q

十一、AI 在调试任务中的表现

在调试方面,AI 的价值非常明显。例如遇到以下错误:

sqlalchemy.exc.OperationalError: no such table: tasks

AI 通常能够判断出原因可能是:

  1. 数据库表没有创建;
  2. 测试环境使用了新的 SQLite 数据库,但没有执行 Base.metadata.create_all()
  3. 应用启动时初始化的是正式数据库,而测试使用的是另一个数据库;
  4. 测试依赖覆盖不完整。

比较可靠的修复方案是,在测试 fixture 中显式执行:

Base.metadata.create_all(bind=engine)

并在测试结束后清理:

Base.metadata.drop_all(bind=engine)

这说明 AI 对常见框架错误具备较强的问题定位能力。尤其是当错误日志完整、项目上下文清晰时,它给出的方案往往能快速缩小排查范围。

但 AI 调试也有风险。它可能会给出多个可能原因,却没有区分优先级;有时也会建议“重新安装依赖”这类泛化方案,而不是针对问题本身。因此,开发者仍然需要具备基本判断力。

结论: AI 非常适合作为调试辅助工具,尤其适合解释报错、定位常见配置问题,但最终修复方案仍需实际运行验证。


十二、AI 在重构任务中的表现

让 AI 重构 crud.py 时,它通常能够完成以下改进:

  • 增加类型标注;
  • 拆分重复逻辑;
  • 改善变量命名;
  • 统一返回值;
  • 减少部分重复查询。

例如改进后的代码可能如下:

from sqlalchemy.orm import Session

from app import models, schemas


def create_task(db: Session, task: schemas.TaskCreate) -> models.Task:
    db_task = models.Task(
        title=task.title,
        description=task.description,
    )
    db.add(db_task)
    db.commit()
    db.refresh(db_task)
    return db_task


def list_tasks(
    db: Session,
    completed: bool | None = None,
) -> list[models.Task]:
    query = db.query(models.Task)
    if completed is not None:
        query = query.filter(models.Task.completed == completed)
    return list(query.all())


def get_task(db: Session, task_id: int) -> models.Task | None:
    return db.query(models.Task).filter(models.Task.id == task_id).first()


def update_task(
    db: Session,
    task_id: int,
    task: schemas.TaskUpdate,
) -> models.Task | None:
    db_task = get_task(db, task_id)
    if db_task is None:
        return None

    update_data = task.model_dump(exclude_unset=True)
    for field_name, field_value in update_data.items():
        setattr(db_task, field_name, field_value)

    db.commit()
    db.refresh(db_task)
    return db_task


def delete_task(db: Session, task_id: int) -> bool:
    db_task = get_task(db, task_id)
    if db_task is None:
        return False

    db.delete(db_task)
    db.commit()
    return True

这类重构基本安全,因为没有改变函数签名和外部行为。但如果让 AI 进行更大规模的架构重构,比如引入 Repository 层、Service 层、配置系统、迁移工具等,它有时会一次性改动太多文件,导致难以 Review。

建议: 使用 AI 重构时,应遵循“小步提交”的原则。每次只让 AI 改一个明确目标,例如“只增加类型标注”“只消除重复代码”“只拆分数据库配置”,不要一次性提出过大的重构需求。


十三、完整命令汇总

为了方便复现,下面整理本次测评中使用的完整命令。

1. 创建项目

mkdir ai-coding-evaluation
cd ai-coding-evaluation

2. 创建虚拟环境

python3 -m venv .venv
source .venv/bin/activate

Windows PowerShell 可使用:

python -m venv .venv
.venv\Scripts\Activate.ps1

3. 安装依赖

python -m pip install --upgrade pip
pip install fastapi uvicorn sqlalchemy pydantic pytest httpx ruff mypy

4. 创建目录和文件

mkdir app tests

touch app/__init__.py
touch app/main.py
touch app/database.py
touch app/models.py
touch app/schemas.py
touch app/crud.py
touch tests/test_tasks.py

5. 启动服务

uvicorn app.main:app --reload

6. 创建任务

curl -X POST "http://127.0.0.1:8000/tasks" \
  -H "Content-Type: application/json" \
  -d '{"title":"测试 AI 编程","description":"验证 AI 生成代码是否可用"}'

7. 查询任务

curl "http://127.0.0.1:8000/tasks"

8. 查询单个任务

curl "http://127.0.0.1:8000/tasks/1"

9. 更新任务

curl -X PATCH "http://127.0.0.1:8000/tasks/1" \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

10. 按状态过滤

curl "http://127.0.0.1:8000/tasks?completed=true"
curl "http://127.0.0.1:8000/tasks?completed=false"

11. 删除任务

curl -X DELETE "http://127.0.0.1:8000/tasks/1"

12. 运行测试

pytest -q
pytest -vv

13. 代码检查

ruff check .

14. 自动修复

ruff check . --fix

15. 代码格式化

ruff format .

16. 格式检查

ruff format --check .

17. 类型检查

mypy app

18. 一键执行全部检查

ruff check . && ruff format --check . && mypy app && pytest -q

十四、综合评分

基于本次测评结果,可以给出如下评分:

测评维度 评分 说明
需求理解 8/10 清晰需求下表现很好,模糊需求容易自行假设
代码生成 8/10 常见框架代码生成速度快,可用性较高
调试能力 8.5/10 对常见报错解释准确,能快速给出方向
测试生成 7.5/10 能覆盖主流程,但边界测试需人工补充
重构能力 7/10 小范围重构可靠,大范围重构需谨慎
工程化意识 6.5/10 对配置、迁移、环境隔离等问题不总是主动处理
安全可靠性 6/10 仍可能生成不严谨代码,必须 Review

综合来看,AI 编程工具在“提高初稿产出速度”和“辅助问题定位”方面价值明显,但在“业务正确性”“长期维护性”“工程安全性”方面仍不能替代开发者。


十五、最终结论

本次测评的核心结论是:AI 编程已经具备明显生产力价值,但更适合作为开发者的增强工具,而不是完全替代开发者。

它最适合以下场景:

  1. 快速生成样板代码;
  2. 编写 CRUD 接口;
  3. 生成基础单元测试;
  4. 解释错误日志;
  5. 编写脚本和命令;
  6. 小范围重构;
  7. 生成文档和注释;
  8. 辅助学习陌生框架。

它不太适合完全独立处理以下任务:

  1. 复杂业务规则设计;
  2. 高安全要求代码;
  3. 大规模架构决策;
  4. 性能敏感模块;
  5. 缺少上下文的历史项目维护;
  6. 无测试保障的自动修改。

对开发者来说,最合理的使用方式不是问 AI:“帮我完成整个项目”,而是把任务拆小,例如:

请只修改 app/crud.py,为 list_tasks 增加 completed 过滤参数,不要修改其他文件。

或者:

请根据下面的错误日志,只分析最可能的三个原因,并按优先级排序。

再或者:

请为当前接口补充 pytest 测试,只覆盖成功创建任务和查询不存在任务两个场景。

这样的提示更容易得到稳定结果,也更便于人工 Review。

总体而言,AI 编程的真正价值不是让开发者“少思考”,而是让开发者把更多精力放在需求判断、架构设计、代码审查和质量控制上。它可以显著减少机械重复劳动,但不能替代工程经验。未来的优秀开发者,很可能不是“会不会使用 AI”的区别,而是“能否正确指挥 AI、审查 AI、约束 AI”的区别。

目录结构
全文