痛点速写:毕设前两周的“死亡三连”
每年三月,实验室的空气里都飘着同一种焦虑的味道——选题定了,技术栈还没影;Git 仓库建了,目录只有main.py孤零零躺着;导师一句“下周给我看演示”,直接让心跳和 CPU 一起飙到 95℃。
我这次抽到的题目是「isac毕设选题:智能课程推荐系统」。听上去人畜无害,真动手才发现:
- 技术选型混乱:协同过滤、知识图谱、深度学习,论文里全是高大上名词,到底先搭哪个?
- 代码组织无序:写着写着就把推荐逻辑、数据库语句、Flask 路由全塞一个文件,import 一圈星号。
- 调试效率低:每次改完模型参数都要手动重启,打印日志淹没在 3000 行控制台里,找不到一条有用的。
于是我把希望押在 AI 辅助开发上——既然写不完,那就让机器先写个 60%,我再补 40%。
工具横评:Copilot vs 通义灵码 vs CodeWhisperer
我把同样一句 Prompt 喂给三款插件:
“用 Python 写一个基于 FastAPI 的 REST 接口,根据学生已修课程列表返回 Top-5 推荐课程,要求带输入校验、异常处理、接口文档。”
- GitHub Copilot:
生成速度最快,直接给出 Pydantic 模型、依赖注入、完整 docstring,但把数据库操作写在路由函数里,违反分层原则。 - 通义灵码:
中文注释极其友好,自动拆出service/repository/handler三层,还附带单元测试模板,不过默认用 SQLAlchemy 1.3 语法,需要手动升级到 2.0。 - CodeWhisperer:
安全扫描最积极,提示“未对输入列表做长度限制”,并给出限流代码段,但接口文档只生成了标题,细节要自己补。
结论:
- 想“秒出代码”选 Copilot;
- 想“结构清晰”选通义灵码;
- 想“少踩安全坑”选 CodeWhisperer。
我最后采用“通义灵码主生成 + Copilot 补细节 + CodeWhisperer 做扫描”的杂交方案。
实战:让 AI 写出能过 Clean Code 审查的推荐模块
1. 需求拆解与 Prompt 设计
先把业务拆成三张“小纸条”:
- 用户服务:注册、登录、返回已修课程 ID 列表
- 课程服务:课程元信息 CRUD
- 推荐服务:读取用户历史 → 模型推理 → 返回 Top-5
每张纸条再写一段“角色+任务+约束”Prompt,例如:
“你是一名对 PEP8 有洁癖的 Python 工程师,请用 FastAPI 编写‘推荐服务’,要求:
- 路由层只负责校验和转发;
- 业务逻辑放在 service 层,依赖抽象推荐接口;
- 数据访问层用 Repository 模式;
- 每个函数不超过 20 行;
- 必须写类型标注与 Google Style docstring。”
把 Prompt 保存成.prompt文件,Git 跟踪,方便回滚。
2. 目录模板一键生成
通义灵码读完上述 Prompt 后,30 秒吐出如下骨架:
isac-recommender/ ├── app/ │ ├── api/ │ │ └── v1/ │ │ └── recommender.py │ ├── core/ │ │ ├── config.py │ │ └── security.py │ ├── models/ │ │ ├── course.py │ │ └── user.py │ ├── repositories/ │ │ └── course_repo.py │ ├── services/ │ │ └── recommend_service.py │ └── main.py ├── tests/ │ └── test_recommend.py ├── Dockerfile └── requirements.txtAI 甚至把__init__.py里的相对导入都写对了,省掉我半小时手敲。
3. 关键代码片段与人工微调
AI 生成的recommend_service.py核心函数:
async def top5_courses(user_id: str) -> List[Course]: """ Returns the top-5 recommended courses for a given user. Args: user_id: UUID string from the request path. Returns: List of Course entities ordered by predicted score desc. Raises: UserNotFoundError: if the user does not exist. """ history = await user_repo.get_finished_courses(user_id) if not history: raise UserNotFoundError(f"user {user_id} has no course history") scores = await model.predict(history) # AI 默认写成了同步阻塞 return await course_repo.find_by_ids(scores.top5())问题一眼发现:model.predict是 CPU 密集型计算,直接放协程里会阻塞事件循环。手动加线程池:
loop = asyncio.get_event_loop() scores = await loop.run_in_executor(None, model.predict, history)再把异常细分,UserNotFoundError改写成RecommendationError并映射到 422,接口文档瞬间优雅。
4. 接口设计说明与注释规范
AI 喜欢在 docstring 里写“TODO: add more logic”。我的做法是:
- 用
ruff做强制检查,出现 TODO 就报错,逼自己在合并前清理; - 把业务语义写成
NOTE(bizhao): 采用协同过滤,冷启动用户走热门课程兜底,方便后人考古。
潜在风险:AI 代码不是“免维护”代码
- 幂等性:AI 默认把“写入浏览记录”放在推荐接口里,每次刷新都会插一条,导致重复推荐。解决:把写操作拆到独立
POST /browsing-history接口,推荐服务只读。 - 输入校验:Copilot 喜欢把
list[str]写成list,导致 FastAPI 只校验外层类型,元素不检查。解决:显式用conlist(CourseId, min_items=1)。 - 冷启动延迟:第一次调用模型会下载 200 MB 参数,AI 不会告诉你放
lifespan里预加载。解决:在main.py的startup事件中提前model.load(),并用/readyz探针挡住流量。
生产环境避坑指南
版本控制策略
- 所有
.prompt文件与代码同库管理,Prompt 变更=功能变更,走 MR 审查; - 用
git-notes把 AI 插件版本(Copilot 1.3.7)记录在提交里,方便回滚后复现。
人工审查 checklist
- 是否有同步 I/O 隐藏在协程里?
- 是否把密钥写死到代码?(AI 常把
API_KEY="sk-xxx"直接甩出来) - 是否出现硬编码路径如
/tmp/model.pkl? - 单元测试覆盖率到 80% 了吗?AI 生成的测试往往只覆盖主路径。
- 性能压测:用
locust打 200 并发,QPS 掉到 30% 以下就要重构。
性能压测示例
locust -f locustfile.py --host=http://127.0.0.1:8000 -u 200 -r 20 -t 5m指标:p99 latency < 400 ms,内存涨幅 < 20%,CPU 安全水位 70%。
结尾:把 60% 自动化,留 40% 给思考
两周下来,我用 AI 生成了 3200 行初始代码,自己改 1200 行,删掉 800 行“看起来有用却永远走不到的分支”,最终交付时 MR 统计“+insert 2.1k,-delete 1.3k”。时间节省一半以上,导师演示一次通过。
但最宝贵的收获是:AI 把“写”加速了,却把“想”留给了人类——
- 边界条件要不要兜底?
- 业务指标用准确率还是召回率?
- 模型漂移多久监控一次?
这些没人能代劳。
如果你也在为“isac毕设选题”熬夜,不妨先让 AI 搭好骨架,再亲手雕琢细节。写完把仓库地址贴给导师时,记得在 README 最后加一句:
“部分代码由 AI 生成,已通过人工审查与单元测试。”——这既是对工具的尊重,也是对自己思考的签名。