skip to content
tlj 的工程笔记
← 项目

在线教育解题挑战赛:四道题全做

面试作品

一个在线教育平台的招聘挑战赛,四道独立的产品 PRD + 可运行代码题。我四道全做了,每道独立打包。最有意思的不是功能,是一个让会话永远卡死的状态机 bug,和一次'死按钮'其实是浏览器缓存的曲折排查。

时间
2026-05
角色
独立完成(产品 / 前后端 / 测试 / 部署)
成果
四题全做、各独立打包,约 134 单测;驯服状态机死锁、UI 自指泄漏、浏览器缓存假象等真实坑

某在线教育平台的招聘挑战赛:四道独立的「产品 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 上被批评的点

不只是 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 上)。