某在线教育平台的招聘挑战赛:四道独立的「产品 PRD + 可运行代码」题——AI 解题陪练、AI 错题本、AI 作业批改、智能提醒助手,每道要交 PRD + React 全栈可运行项目 + 本地数据库,各自独立打包成 zip。
第一个决策:四道全做
要不要四道全做?我一开始是反对的——代码不用人写不等于人力为零,四道的决策、review、测试加起来要二三十小时;而且评审看「四个粗糙」对「一个精致」,多半选后者。但最终判断是全做更值,关键在于把每道的工程亮点做扎实、质量不掉。事后证明四道全上线、质量没塌。
配套两个工程决策:模型用便宜的 Sonnet 干 90%、关键节点(部署/联调/修难 bug)才切 Opus,本质是省额度;部署用单子域 + 路径分发(/01//02//03//04/ 各反代到独立后端),不搞四个子域——多子域只是 DNS/证书的自我满足,路径分发已经证明了项目分离。骨架先做一道、再用脚本克隆成四份(改端口/db/BASE_PATH)。
一个让会话永远卡死的状态机 bug
解题陪练是个分步引导的状态机:学生没答出来就 GUIDING(启发式引导),接近答案就 NEAR_ANSWER,答出来就 DONE。bug 是:当学生一上来就说出完整解法,LLM 自报状态 DONE,但 GUIDING 的处理分支只检查了 NEAR_ANSWER、没有 DONE 分支——于是 session 永远卡在 GUIDING,后面的「举一反三」永远生成不出来。修复是把分支条件改成 (state==='NEAR_ANSWER' || state==='DONE') && progress>70。状态机这种东西,漏一个状态分支就是一个死循环,而且只在特定输入下触发。
「死按钮」其实是浏览器缓存
部署后用户反馈「前端很多按键是死的,根本不跳转」。排查很曲折:先怀疑 SPA fallback(curl 测深链接正常)→ 怀疑 React Router 的 basename(审了 build 后的 JS,basename 处理正确)→ 干脆装 puppeteer 无头浏览器实测,发现点击和路由完全正常。最后真因是——用户浏览器缓存了某次中间部署的旧 index.html。解法是 Layout 改用显式 Link + useLocation(去掉 NavLink 的隐式 className 函数这种防御性写法)、favicon 用 %BASE_URL%,再让用户硬刷。教训:报「点了没反应」的 bug,先排掉缓存这个最常见、也最容易被忽略的因素。
两个 UI 上被批评的点
- UI 泄漏了自指内容:前端卡片上出现了「关键决策提示 / 这是给评审看的不是给用户看的」这种话。被批评「能不能专业点,放文档里」。根因是把工程亮点直接写进了给评审看的产品 UI。解法:所有决策/算法解释移到仓库的 README/PRD,前端只留纯产品体感。
- 箭头按钮的视觉欺骗:有个「看漏斗 →」的按钮,箭头让人以为能跳转,实际只是切换卡片,而且整行已经可点击、按钮是冗余的。删掉冗余按钮,改成行点击后自动平滑滚动到目标区。教训:整行可点击时别再加「详情→」这种按钮。
不只是 prompt 工程
被质疑「这是纯提示词工程吗?一点代码逻辑没有?」,我逐题列了非 prompt 的代码量反驳——比如错题本里 162 个 K-12 知识点的分类体系(数学 63/语文 31/英语 32/物理 20/化学 16)、SM-2 简化版的间隔重复算法(mastery 映射 1-64 天)、错因归因的 5 类 taxonomy + zod 校验 + 重试兜底;批改题里客观题判分约 250 行、完全不调 LLM(数值规范化处理分数/小数/科学计数法等价、单位换算);提醒题里分群算法、48 桶时机预测、AB 框架、疲劳控制——Prompt 占比不到 5%。
工程纪律
四道累计约 134 个单元测试通过(题1 29、题2 35、题3 46、题4 若干)。题3 真调 DeepSeek 端到端批改 10 道题约 6 秒。我自己用 puppeteer 跑核心交互 + 截图自查,不让用户先发现 UI 瑕疵。交付的四个 zip 各含 PRD + README,都验证过不带 .env / node_modules / .db。zip 名严格保留了原题的错别字(题目里就那么写的),姓名前缀做成参数。
小结
这道挑战赛我最有体会的是那些「只在特定情况下才暴露」的坑——一上来就答对触发的状态机死锁、某次中间部署留下的浏览器缓存。功能都能做出来,真正考验的是把这些边角驯服干净,以及把工程亮点放对地方(文档里,不是用户 UI 上)。